I'm building out a Shedquarters in my backyard! Check out the ever-evolving post + pictures here →

Using Ziggy with Inertia Server-Side Rendering

June 4, 2021

InertiaJS has recently opened up early support for server-side rendering. Currently it is in a beta release for GitHub supporters of the project, to be opened up to the public after things are ironed out.

While it's still early, the results are extremely promising. It's wicked fast and pretty easy to set up. There are a few gotchas that the team are still working out, one of which is popular Javascript libraries that rely on the window object, which is obviously not available when running on the server.

Ziggy

One of those popular libraries that people are having trouble with is Ziggy, which allows you use your Laravel routes in JavaScript.

It's an extremely helpful package that brings a Laravel-esque route helper to the frontend, but unfortunately it relies on the window object and a global Ziggy object as well. Fortunately it's a relatively easy fix.

Sending the Routes Over

The first thing we need to do is get all the routes into Ziggy. In a standard Laravel app this is done with the @routes Blade tag, which puts everything on the page. When using Inertia SSR you'll be rendering a Vue component, therefore the Blade tag isn't a viable option.

Ziggy exposes a couple of other options to get a hold of the routes, which is what we'll be using. We'll share it with Inertia using the Inertia::share functionality in HandleInertiaRequests middleware:

app\Http\Middleware\HandleInertiaRequests.php

1<?php
2
3namespace App\Http\Middleware;
4
5use Illuminate\Http\Request;
6use Inertia\Middleware;
7use Tightenco\Ziggy\Ziggy;
8
9class HandleInertiaRequests extends Middleware
10{
11 ...
12 /**
13 * The root template that's loaded on the first page visit.
14 *
15 * @see https://inertiajs.com/server-side-setup#root-template
16 * @var string
17 */
18 protected $rootView = 'app';
19
20 /**
21 * Determines the current asset version.
22 *
23 * @see https://inertiajs.com/asset-versioning
24 * @param \Illuminate\Http\Request $request
25 * @return string|null
26 */
27 public function version(Request $request)
28 {
29 return parent::version($request);
30 }
31
32
33 /**
34 * Defines the props that are shared by default.
35 *
36 * @see https://inertiajs.com/shared-data
37 * @param \Illuminate\Http\Request $request
38 * @return array
39 */
40 public function share(Request $request)
41 {
42 return array_merge(parent::share($request), [
43 // Add in Ziggy routes for SSR
44 'ziggy' => (new Ziggy)->toArray()
45 ]);
46 }
47}
Code highlighting powered by torchlight.dev.

Inertia will now add a ziggy key to your props, which will be passed to your SSR function.

Adding the Route Function

Like I mentioned above, the default Laravel + Ziggy route function relies on some global variables, which we don't have. We'll need to do a bit of tweaking to get the route helper to work again.

Inertia's SSR docs guide you through how to set up an ssr.js, which I won't cover here, but we will be modifying it a bit to include the new route helper.

Here's a basic SSR.js setup, with some of the not-yet-released SSR stuff omitted.

ssr.js

1return await createInertiaApp({
2 // Some SSR stuff omitted...
3 setup({app, props, plugin}) {
4 return createSSRApp({
5 render: () => h(app, props),
6 })
7 },
8});

Now let's add in the modified route helper:

1import Router from 'ziggy/Router';
2
3return await createInertiaApp({
4 // Some SSR stuff omitted...
5 setup({app, props, plugin}) {
6 return createSSRApp({
7 render: () => h(app, props),
8 }).mixin({
9 methods: {
10 // Pull the Ziggy config off of the props.
11 route: (name, params, absolute, config = request.body.props.ziggy) => {
12 const router = new Router(name, params, absolute, config);
13 return name ? router.toString() : router;
14 },
15 },
16 })
17 },
18});

This is basically the route method directly from ziggy: github.com/tighten/ziggy/src/js/index.js.

For the config, we pass in a default of the request.body.props.ziggy, which is the Ziggy array that we set up on the Laravel side.

Webpack Setup

Before this can work, you have to make one tweak to your webpack config (because of course you do, it's webpack.)

You need to tell webpack where to find the 'ziggy' import, because it's not a traditional NPM module, it's installed via composer.

webpack.ssr.mix.js

1mix.webpackConfig({
2 resolve: {
3 alias: {
4 ziggy: path.resolve('vendor/tightenco/ziggy/src/js'),
5 },
6 }
7})

Solving for the Current Route

As of this writing, Ziggy relies solely on the window to pull the current URL, which obviously doesn't work in Node. There is a pull request open to fix that, but it is not yet merged. I will update this post if it does get merged!

To get around the global window problem, we have to do something that is both very clever and very disgusting. We're going to overwrite global.window.

1return await createInertiaApp({
2 // Some SSR stuff omitted...
3 setup({app, props, plugin}) {
4 return createSSRApp({
5 render: () => h(app, props),
6 }).mixin({
7 methods: {
8 // Pull the Ziggy config off of the props.
9 route: (name, params, absolute, config = request.body.props.ziggy) => {
10 const router = new Router(name, params, absolute, config);
11
12 // Store a reference to the original function.
13 let original = router.current;
14
15 router.current = function (name, params) {
16 // Cache the window as it is, in case someone
17 // else is faking it too.
18 let oldWindow = global.window;
19
20 // Set the window, but only with the location.
21 global.window = {
22 // Create a full "location" object by using
23 // the URL object, passing through the URL
24 // from the Inertia payload.
25 location: new URL(request.body.props.ziggy.url)
26 }
27
28 // Call the original while the window is overwritten.
29 let result = original.call(this, name, params);
30
31 // Undo our overwrite.
32 global.window = oldWindow;
33
34 return result;
35 }
36
37 return name ? router.toString() : router;
38 },
39 },
40 })
41 },
42});

That's "all" you have to do to get Ziggy fully working with server-side rendering. It's admittedly not pretty, but it does work. I'll update this post as Ziggy potentially gets that PR merged.

The final thing that I'd like to tackle is compiling the routes with the SSR build for production. In development it's probably fine, but some apps have a lot of routes, and sending those over the wire every time doesn't make a lot of sense. In production, it would make more sense to back the routes into the JS from the start.

I'll update this post once I get that sorted! Reach out to me at twitter.com/aarondfrancis if you have any thoughts!

Thanks for reading! My name is Aaron and I'm currently working at small property tax firm in Texas called Resolute Property Tax Solutions, where I serve in dual roles as COO & CTO.

I work on a lot of projects. I'm building a shedquarters. I currently do a podcast, and I used to do a different podcast.

If you ever have any questions or want to chat, I'm always on Twitter
Copyright 2013 - 2021, Aaron Francis.