Localized routes issue with Laravel Inertia SSR apps

I want to have a seemingly simple outcome:

  • website in two languages with URL structure like /en/blog/article1 and /de/blog/seite1
  • content of the page is visible to search engines for SEO purposes

It’s not much, but it turns out to be incredibly difficult to achieve.

Problem

  1. I’m getting urls like this in SSR (Server-side rendering) output: /blog and /blog/article1
  2. When react starts working in the browser, those URLs are corrected into /de/blog and /de/blog/setie1. So to users and to me during development it looks like everything is fine
  3. However, since google sees /blog and /blog/article1, it tries to visit those
  4. /blog is redirected to /de/blog which is problematic but works. The real issue is with /blog/article1 which is redirected to default locale /de/blog/article1 and throws 404, since in German the correct URL is /de/blog/seite1 (article1 post doesn’t exist there).

Stack

Stack is Laravel 11 + Inertia React with SSR enabled. This setup works without issues until you start with localization.

For localization I’ve installed https://github.com/mcamara/laravel-localization.

Hacky fix

I found that incorrect URLs come from this route function definition in ssr.jsx:

import { Ziggy } from '@/ziggy';
global.route = (name, params, absolute, config = Ziggy) 
    => route(name, params, absolute, config);

After days of trying to sort it out, I managed to pull localized URLs into SSR by pulling them from the props where they are put from HandleInertiaRequest middleware:

$ziggy = new Ziggy($group = null, $request->url());
return [
    // Add in Ziggy routes for SSR
    'ziggy' => $ziggy->toArray(),
    ...
]

And in ssr.jsx:

const ziggyConfig = { ...props.initialPage.props.ziggy };
global.route = (name, params, absolute) 
    => route(name, params, absolute, ziggyConfig);

This made URLs localized, but only worked properly on the home page, because all URLs suddenly became relative. Even setting absolute to true did not help. URLs became e.g. /en/contact/en/blog if I was looking at the blog link while being on the /en/contact page.

So to make URLs absolute I introduced this hack – I’m overwriting the “current URL” in ziggy to / to make URLs absolute:

const ziggyConfig = { ...props.initialPage.props.ziggy };
ziggyConfig.url = ziggyConfig.url.split('/').slice(0, 3).join('/');
global.route = (name, params) => route(name, params, true, ziggyConfig);

Which is obviously a dangerous hack and will probably have side effects. However, this currently works and all URLs seem to be correct.

Questions

The main question is how do I make this correctly without this hack?

My other problem is that I can’t really trust the SSR output anymore. What I see on the page locally during development and what I see on production is NOT the same that search engines see. This is really dangerous. How can one test this SSR setup? How can I trust that the website is working if I can’t even see how search engines see it? Any advice here would be super helpful.

I have considered not using react for “public” pages, but would like to avoid this if possible, since that would lead to having to duplicate layout/components.