Blog / Create a custom error page with Laravel and Inertia
4 min - 12 Aug 2024
TL;DR: How to display a custom error page with Inertia instead of Laravel’s default error pages.
A sample Laravel project can be found on this Github Repository. Find out more on Capsules, X or Bluesky.
In a previous article, the fourth article on this blog, the focus was on customizing the 502 Bad Gateway error page servec by Nginx. This page was an HTML file placed directly in the /public
folder. This article now focuses on customizing the error pages returned by Laravel in a VILT project.
To get a preview of the layout of Laravel’s default error pages, simply generate an error using the App::abort
facade or the abort
helper, followed by the desired error code.
routes/web.php
<?php
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\App;
Route::get( '/', fn() => App::abort( 503 ) );
Route::get( '/', fn() => App::abort( 419 ) );
Route::get( '/', fn() => App::abort( 404 ) );
By delving into the functionality of the abort
function, it becomes clear that Laravel generates an Exception
, which is then handled by the ExceptionHandler
created by the withException
method in the bootstrap/app.php
file.
vendor/laravel/framework/src/Illuminate/Foundation/Application.php
public function abort($code, $message = '', array $headers = [])
{
if ($code == 404) {
throw new NotFoundHttpException($message, null, 0, $headers);
}
throw new HttpException($code, $message, null, $headers);
}
vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php
protected function prepareResponse($request, Throwable $e)
{
if (! $this->isHttpException($e) && config('app.debug')) {
return $this->toIlluminateResponse($this->convertExceptionToResponse($e), $e)->prepare($request);
}
if (! $this->isHttpException($e)) {
$e = new HttpException(500, $e->getMessage(), $e);
}
return $this->toIlluminateResponse(
$this->renderHttpException($e), $e
)->prepare($request);
}
bootstrap/app.php
<?php
use Illuminate\Foundation\Application;
return Application::configure( basePath : dirname( __DIR__ ) )
->withRouting( web : base_path( "/routes/web.php" ) )
->withExceptions()
->create();
withExceptions
method is removed from the file, everything falls apart. Therefore, it is crucial not to overlook this method.To allow Inertia to intercept the various exceptions, it is necessary to modify the bootstrap/app.php
file. This is primarily where the magic happens.
More specifically, regarding the withExceptions
method : it handles exception interception. When the exception is an HttpException
, which is the focus of this article, the Inertia rendering is triggered. The method $exceptions->render( fn( HttpException $exception, ... ) )
allows filtering of exception types. If the exception is an HttpException
, the Inertia rendering process continues. Inertia then returns the Error page with the $request
response and its status code.
bootstrap/app.php
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Illuminate\Http\Request;
use Inertia\Inertia;
return Application::configure( basePath : dirname( __DIR__ ) )
->withRouting( web : base_path( "/routes/web.php" ) )
->withMiddleware()
->withExceptions( fn( Exceptions $exceptions ) =>
$exceptions->render( fn( HttpException $exception, Request $request ) =>
Inertia::render( 'Error', [ 'status' => $exception->getStatusCode() ] )
->toResponse( $request )
->setStatusCode( $exception->getStatusCode() )
)
)
->create();
It is then possible to customize the desired error pages by using a dedicated Vue component Error
for errors.
resources/js/pages/Error.vue
<script setup>
import { Head } from '@inertiajs/vue3'
import { useStatusName, useStatusMessage } from '~/composables/status';
import logotype from '/public/assets/capsules-logotype.svg';
const props = defineProps( { status : Number } );
</script>
<template>
<Head>
<title>{{ useStatusName( status ) }}</title>
</Head>
<div class="h-screen flex justify-center">
<div class="flex items-center space-x-10">
<img class="h-20 w-20" v-bind:src="logotype">
<div class="flex flex-col items-start space-y-2 font-mono text-primary-black">
<h1 class="text-2xl text-primary-blue" v-text="`${props.status} - ${useStatusName( status )}`" />
<p class="text-md" v-text="useStatusMessage( status )" />
</div>
</div>
</div>
</template>
Head
component allows for customizing the <head>
section of the HTML page. However, it is important not to forget to include @inertiaHead
in the app.blade.php
.As well as a status
composable that returns the desired error codes and messages.
resources/js/composables/status.js
export function useStatusName( status )
{
switch( status )
{
case 503 : return 'Service Unavailable';
case 500 : return 'Server Error';
case 419 : return 'Session Expired';
case 418 : return 'I\'m a teapot';
case 404 : return 'Page Not Found';
case 403 : return 'Forbidden';
default : return 'Error';
}
}
export function useStatusMessage( status )
{
switch( status )
{
case 503 : return 'Sorry, we are doing some maintenance. Please check back soon.';
case 500 : return 'Whoops, something went wrong on our servers.';
case 419 : return 'Sorry, your session expired.';
case 418 : return 'Sorry, I am not a coffee machine.';
case 404 : return 'Sorry, the page you are looking for could not be found.';
case 403 : return 'Sorry, you are forbidden from accessing this page.';
default : return 'Sorry, something went wrong unexpectedly.';
}
}
To test different exceptions directly from the browser :
routes/web.php
<?php
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\App;
Route::get( '/{status?}', fn( int | null $status = null ) => App::abort( $status ?? 418 ) );
herd link article
npm run dev
> http://article.test/503
> http://article.test/419
> http://article.test/404
Glad this helped.