Back to News & Insights
SEO September 19, 2026 · 6 min read

Your Inertia SSR server is down and your site still returns 200

The site looked fine. Every page loaded, every blog post rendered, the styling was right, nothing in...

Your Inertia SSR server is down and your site still returns 200

The site looked fine. Every page loaded, every blog post rendered, the styling was right, nothing in the logs. What was wrong was that Search Console had crawled two pages out of the entire sitemap, and the rest sat in Discovered – currently not indexed. Dozens of posts, almost nothing indexed, and no broken page anywhere to point at.

The cause turned out to be a process that had stopped running. Not crashed loudly — just stopped. And because of how Inertia handles that case, the only clients that noticed were the ones we could not see.

Disclosure: I work on Bunfolio, a free portfolio-site builder for freelancers. This happened to us, and the diagnosis is the useful part.

The stack is Laravel 12, Inertia.js and React 19, with server-side rendering enabled. In that setup, php artisan inertia:start-ssr runs a small Node server (port 13714 by default) that loads your built server bundle, bootstrap/ssr/ssr.js. On each request Laravel POSTs the page object to it, gets back HTML, and the @inertia Blade directive prints that HTML inside .

When that Node process is not answering, Laravel does not fail the request. It falls back to client-side rendering: it emits an empty with the page props serialised into the attribute, and ships the response with a 200. Your browser downloads the JavaScript, reads data-page, renders the app, and everything looks completely normal.

So the failure is invisible to exactly the people checking for it, and visible only to clients that do not execute JavaScript. That is a decent chunk of the things you care about: crawlers that do not render, link unfurlers, anything reading your page with an HTTP library. Google does render JavaScript, but rendering is queued and budgeted separately from crawling, and on a new site with essentially no inbound links you should assume that budget is close to zero. An empty #app is a page with no prose in it, and a page with no prose in it is not a page worth indexing.

The part that makes this a genuine trap rather than an ordinary outage: you cannot detect it by looking at the site. Uptime checks pass. Status codes are 200. Screenshots are perfect. The failure only exists in the response body, and only in the part of it that a browser immediately overwrites.

It was never supervised. Someone started inertia:start-ssr by hand over SSH to test it. It works, the page renders, everyone moves on. The shell closes, the process dies with it, and the app keeps serving 200s.

The server bundle throws at render time. Our own version of this: twenty-two components called a bare global route(), which the @routes Blade directive defines in the browser. Node has no such global, so every server render died with route is not defined — and Inertia treated that the same way it treats a dead process, by falling back to the client. The fix was a shim at the top of ssr.jsx:

Taking the host from APPURL at run time rather than from the value baked into ziggy.js at build time matters too, otherwise the markup React hydrates against does not match what the client would have produced.

It is running the wrong code. More on that below — it is the second trap and it is worse than the first.

But be clear about what it proves. It opens a connection to the configured SSR URL and confirms something answers. It does not prove your public site is using that process, that the bundle it loaded renders your pages without throwing, or that the HTML reaching a crawler contains anything. A green check-ssr with an empty #app in production is entirely possible, and is precisely the state we were in.

The real test is to be the crawler. Fetch the page over HTTP, find , strip the scripts and tags out of everything after it, and count what is left:

Every line should report thousands of characters. A 0 means SSR is not reaching that page, and the deploy has failed for search purposes even though the site works perfectly in a browser.

Stripping blocks before counting is the load-bearing detail. Without it you are counting the serialised data-page JSON, which is large and present in both the working and the broken case — so the check would pass either way. That is the same reason "view source and eyeball it" does not work: the broken response is not empty, it is full of JSON that looks reassuringly like your content.

For the authoritative version, use Test live URL in Search Console and open View tested page → HTML. That is Google telling you what Google got.

inertia:start-ssr reads bootstrap/ssr/ssr.js once, at boot. If you rebuild assets and do not restart the process, it carries on rendering the previous bundle indefinitely. Your browser fetches the new client build and hydrates over the old server HTML, so the page you are looking at is correct and current. Meanwhile curl — and every crawler — gets markup from whatever your code looked like at the last restart.

The page is not broken. It is just old. You can stare at a diff for an hour wondering why a change you can see in your browser is not in the HTML, and there is no error anywhere to find. Restart the SSR process after every build, locally as well as in production.

Want to discuss this further?

Book a free strategy call with our team to see how these insights apply to your specific business goals.

Book a consultation