The default error pages (like a 404) are great for development, but when it's time to push your app to production, don't leave your error pages as the defaults.
Let's look at how to add a custom 404 page to Laravel, and then we'll dive into how this works behind the scenes.
To add a custom 404 (and any other supported status code) page to Laravel, just create a file in your views
directory under an errors
directory.
Here's an example.
resources/views/errors/404.blade.php
Oops, page not found
Pretty simple content, but when hitting a page that doesn't exist, you'll see 'Oops, page not found'.
The same principle applies here. Just take the status code of the error (which will be displayed on the default Laravel error page) and create a new file.
I do this for CSRF errors (419) since it's helpful to let the user know what went wrong and what they need to do.
Once again, here's an example.
resources/views/errors/419.blade.php
The page expired while you were trying to perform that action. Go back, refresh, a try again.
I'm sure you get the idea now. For any error you'd like to customise, take the HTTP status code and create a corresponding Blade file with whatever you want to display instead.
As a bonus, let's source dive to see how Laravel handles this.
Under the framework's base Handler
class, there's a method that handles HTTP exceptions.
class Handler implements ExceptionHandlerContract {
protected function renderHttpException(HttpExceptionInterface $e)
{
$this->registerErrorViewPaths();
//...
}
//...
}
The registerErrorViewPaths
method looks like this.
protected function registerErrorViewPaths()
{
(new RegisterErrorViewPaths)();
}
There is not much going on here, but if we look at what happens when the RegisterErrorViewPaths
class is invoked, we see this.
public function __invoke()
{
View::replaceNamespace('errors', collect(config('view.paths'))->map(function ($path) {
return "{$path}/errors";
})->push(__DIR__.'/views')->all());
}
While this registers the errors
namespace for views, it doesn't take care of actually rendering them. Now that Laravel has a namespace for errors, though, here's where the magic happens, again, inside the Handler
class.
protected function getHttpExceptionView(HttpExceptionInterface $e)
{
$view = 'errors::'.$e->getStatusCode();
if (view()->exists($view)) {
return $view;
}
//...
return null;
}
The getHttpExceptionView
method grabs any views you've created under the new errors
namespace.
Finally, here's how it's actually rendered.
protected function renderHttpException(HttpExceptionInterface $e)
{
$this->registerErrorViewPaths();
if ($view = $this->getHttpExceptionView($e)) {
try {
return response()->view($view, [
'errors' => new ViewErrorBag,
'exception' => $e,
], $e->getStatusCode(), $e->getHeaders());
} catch (Throwable $t) {
config('app.debug') && throw $t;
$this->report($t);
}
}
return $this->convertExceptionToResponse($e);
}
Take a good look at this method; you can see how everything we've discussed comes together to render the view.
While this might seem unnecessarily broken up, it's for a good reason — we can override any logic within the Handler
class in the framework skeleton!