Better 404 responses using Laravel 5.5

Laravel 5.5.10 is shipped with two useful router methods that'll help us present better 404 pages for our users. Currently when a 404 exception is thrown Laravel displays a nice 404.blade.php view file, you can customize how it looks like for your end user but inside the view itself you don't have access to sessions, cookies, auth, etc...

In laravel 5.5.10 we have a new Route::fallback() method, it can be used to define a route that Laravel fallbacks to when no other route is matched with the request.

Route::fallback(function(){
    return 'Sorry '. auth()->user()->name . '! This page does not exist.';
});

So now instead of showing a plain 404 view, we can show our normal application logged-in area layout with headers and footers and include a nice message to the user.

Route::fallback(function(){
    return response()->view('notFound', [], 404);
});
@extends('layout.app')

@section('content')
    <h3>Sorry! this page doesn't exist.</h3>
@stop

When laravel is rendering this fallback route it's going to run all the middleware assigned to it, so if you define your fallback route in your web.php routes file all middleware under the web middleware group will run and thus why we have access to sessions.

A note about API routes

Now if you hit /non-existing-page you'll see the fallback route displayed for you, even if you hit /api/non-existing-endpoint, which is something you don't want, instead you want api fallback route to respond with JSON, let's then define another fallback route inside our api.php route file:

Route::fallback(function(){
    return response()->json(['message' => 'Not Found!'], 404);
});

Now since the api middleware group has a prefix /api, all not found routes prefixed with /api will hit the fallback route inside our api.php file not the one we specify in web.php.

Using abort(404) and ModelNotFound exceptions

When we use abort(404) a NotFoundHttpException is thrown, the handler at this point will render our 404.blade.php plain view, same will happen when a ModelNotFoundException exception is thrown, so how do we render one of our fallback routes instead of this plain view?

class Handler extends ExceptionHandler
{
    public function render($request, Exception $exception)
    {
        if ($exception instanceof NotFoundHttpException) {
            return Route::respondWithRoute('fallback');
        }

        if ($exception instanceof ModelNotFoundException) {
            return Route::respondWithRoute('fallback');
        }

        return parent::render($request, $exception);
    }
}

Route::respondWithRoute('fallback') simply runs the route called fallback, we can name our fallback routes by the way:

Route::fallback(function(){
    return response()->view('notFound', [], 404);
})->name('fallback');

You can take it further and use a resource-specific fallback route:

if ($exception instanceof ModelNotFoundException) {
    return $exception->getModel() == Server::class
                ? Route::respondWithRoute('serverFallback') 
                : Route::respondWithRoute('fallback');
}

And we define this in our routes file:

Route::fallback(function(){
    return 'We could not find this server, there are other '. auth()->user()->servers()->count() . ' under your account ......';
})->name('serverFallback');