Adding a cache layer to Inertia SSR in Laravel

Inertia’s SSR server (which runs in a NodeJS process) is pretty quick, but sometimes you’re trying to shave every millisecond possible off your render time. A common question we’ve encountered is whether you can simply cache the server response and speed things up even more. Good news: you can.

What makes this possible is a line in the ServiceProvider in the Inertia package inside the register method:

$this->app->bind(Gateway::class, HttpGateway::class);

This binding is telling your app that when the Gateway contract is called, use the HttpGateway class. You can read up on the Laravel service container if you want to learn more about this behaviour, but for now, all you need to know is that you can override this binding and provide your own class to handle Inertia SSR HTTP requests. So let’s create our custom class.

Creating our custom implementation

First, let’s go to our app\Http directory and create a new file called InertiaHttpGateway.php. This is going to be based on the original Inertia file you can find at vendor/inertiajs/inertia-laravel/src/Ssr/HttpGateway.php in your project. We only need to alter a few lines inside our try block:

1<?php
2
3namespace App\Http;
4
5use Exception;
6use Inertia\Ssr\Gateway;
7use Inertia\Ssr\Response;
8use Inertia\Ssr\BundleDetector;
9use Illuminate\Support\Facades\Http;
10use Illuminate\Support\Facades\Cache;
11
12class InertiaHttpGateway implements Gateway
13{
14 /**
15 * Dispatch the Inertia page to the Server Side Rendering engine.
16 */
17 public function dispatch(array $page): ?Response
18 {
19 if (!config('inertia.ssr.enabled', true) || !(new BundleDetector())->detect()) {
20 return null;
21 }
22
23 $url = str_replace('/render', '', config('inertia.ssr.url', 'http://127.0.0.1:13714')) . '/render';
24
25 try {
26 $cacheKey = md5(serialize($page));
27 $response = Cache::remember($cacheKey, 60, fn () => Http::post($url, $page)->throw()->json());
28 } catch (Exception $e) {
29 return null;
30 }
31
32 if (is_null($response)) {
33 return null;
34 }
35
36 return new Response(
37 implode("\n", $response['head']),
38 $response['body']
39 );
40 }
41}
42

All we’re doing here is creating a hash of our $page props (see line 26). This enables us to create a new cache entry for every individual request. You may want to check if a user is logged in here too and skip the cache layer (since a render with user data is likely highly variable) but for this example, we’ll keep it simple.

Next we use Laravel’s built-in caching facade to remember the response to the request (line 27). If we get the exact same request again, we can use the exact same response.

Binding our class to the service container

Finally, we need to tell our Laravel app to use our custom implementation instead of the default Inertia one. So find the register function in your AppServiceProvider and add the following line:

class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->bind(Gateway::class, InertiaHttpGateway::class);
}
}

Et voilla. Because we have our own class implementation, we can add logic to the caching layer precisely tailored to our needs. We may want to cache some pages longer than others, or only cache specific pages. Anything is possible.