Blog / Create a custom error page with Laravel and Inertia

Image used for article Create a custom error page with Laravel and Inertia

Create a custom error page with Laravel and Inertia




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 CapsulesX 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();

  • If the 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>
  • The 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.

v1.4.0

X IconGithub Icon