Support the ongoing development of Laravel.io →

Create a custom error page with Laravel and Inertia

13 Aug, 2024 5 min read

How to display a custom error page with Inertia instead of Laravel’s default error pages.

Capsules Error Image 0

A sample Laravel project can be found on this Github Repository. Find out more on Capsules or X.

In a previous article, 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 ) );

Capsules Error Image 1

Route::get( '/', fn() => App::abort( 419 ) );

Capsules Error Image 2

Route::get( '/', fn() => App::abort( 404 ) );

Capsules Error Image 3

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

Capsules Error Image 4

> http://article.test/419

Capsules Error Image 5

> http://article.test/404

Capsules Error Image 6

Glad this helped.

Last updated 4 weeks ago.

driesvints liked this article

1
Like this article? Let the author know and give them a clap!
mho (MHO) Full time side project full stack web developer | designer Work @ http://capsules.codes

Other articles you might like

April 17th 2024

Using the "Conditionable" Trait In Laravel

Introduction Conditions are an integral part of any codebase. Without being able to conditionally ex...

Read article
March 11th 2024

How to get your Laravel app from 0 to 9 with Larastan

Finding bugs in your Laravel app before it's even executed is possible, thanks to Larastan, which is...

Read article
August 21st 2024

Find Outdated Composer Dependencies Using "composer outdated"

Introduction When building your PHP web applications, it's important to keep your dependencies up-to...

Read article

We'd like to thank these amazing companies for supporting us

Your logo here?

Laravel.io

The Laravel portal for problem solving, knowledge sharing and community building.

© 2024 Laravel.io - All rights reserved.