Before we start its worth caveating this post with the message: It’s always more desirable and recommended to serve Laravel apps from bespoke hosting environment.
There are plenty to choose, from first party tools like Forge and Vapour to more general providers like Digital Ocean, AWS and GCP.
However as i find myself in a similar situation there may be times you have to deploy on a CPanel environment.
I am going to cover the usual quite simple redirect you see across the web, but more importantly a second step thats usually left out. This second step can have a massive impact on your SEO.
The Redirect
As you may of found elsewhere theres a simple redirect you can put in the root folder of your project:
# Internally rewrite all top-level requests to the public directory <IfModule mod_rewrite.c> RewriteEngine On RewriteRule ^(.*)$ public/$1 [L] </IfModule>
It’s very simple and just redirects everything from the root of your site public_html
down into the public
folder of your Laravel project.
From there the .htaccess
provided with Laravel will take over and route incoming requests.
Job done? Maybe not…
The SEO Problem
The problem with the above redirect is that it doesn’t prevent access to ALL of your urls with a /public/
prefix. So both /
and /public/
will server your applications homepage!
This is obviously bad for any SEO efforts your planning to make to the site as you now have duplicate pages and on top of that if someone stumbles upon those urls its not great for users.
I spent sometime trying out redirects to prevent this from the .htaccess
file itself but came up blank. In my particular case this is a CPanel server with Litespeed installed. And i know from experience its not quite 100% like for like with Apache.
The Solution
With the .htaccess
approach coming up blank i went with a Middleware solution. Again this isn’t ideal as its not really the applications responsibility but we are not in an ideal situation anyway.
It’s a pretty simple middleware, it looks for the public
prefix and returns a 301 redirect if found. This does mean you cannot have urls that start with public
but in our case this wasn’t a problem:
<?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; class RedirectPublicFolderRequests { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle(Request $request, Closure $next) { if (strpos($request->getRequestUri(), '/public') === 0) { return redirect() ->to($request->getSchemeAndHttpHost() . '/' . substr($request->getRequestUri(), 8)) ->setStatusCode(301); } return $next($request); } }
Once again this isn’t ideal but works for our purposes.
Add this to your global middleware stack before anything else.
Note how we use the $request->getRequestUri()
method and not simply $request->path()
. As the path will be effected by the servers rewrites.
Now any requests for public
will redirect with a 301 and you wont be serving duplicate pages.