v6-docs icon indicating copy to clipboard operation
v6-docs copied to clipboard

Inertia - Missing information for React SSR

Open advename opened this issue 11 months ago • 2 comments

Issue 1: Missing Hydration Step for React SSR

Relevant Docs: AdonisJS SSR Guide

The documentation currently demonstrates how to enable SSR for Inertia by creating the inertia/app/ssr.ts file. However, when using React, it omits an important step: updating inertia/app/app.ts to use hydrateRoot instead of createRoot.

This behavior is clearly documented in the official Inertia.js docs:
https://inertiajs.com/server-side-rendering#client-side-hydration

It is also correctly implemented in the AdonisJS + React + Inertia + SSR boilerplate.

Suggestion: Please add a snippet showing how hydrateRoot should be used in app.ts when SSR is enabled.


Issue 2: SSR Allowlist and Conditional Hydration

Relevant Docs: SSR Allowlist in AdonisJS

The current guide explains the SSR Allowlist feature, which allows rendering some pages with SSR and others with CSR. However, when mixing SSR and CSR pages, developers must conditionally hydrate the app using hydrateRoot only when SSR was used — otherwise, hydration mismatch errors occur.

This subtle but critical detail is currently missing from the documentation and has led to confusion in the community, as seen in:

  • Inertia Discussion #1429 — using import.meta.env.SSR inside the if statement doesn't seem to actually work since it always returns false in the browser, meaning this is a faulty implementation
  • Intent.UI— same approach as above, ineffective.
  • Stack Overflow Answer — same approach as above, ineffective.
  • Inertia Discussion #2138 — proposes suffixing SSR pages with .ssr.tsx and using that to determine hydration method.

Recommended Solution

A more simple way is to check if the element has already been rendered server-side:

// inertia/app/app.tsx

setup({ el, App, props }) {
  const isSSR = el.childElementCount > 0

  if (isSSR) {
    hydrateRoot(
      el,
        <App {...props} />
    )
  } else {
    createRoot(el).render(
        <App {...props} />
    )
  }
}

Since only SSR-rendered pages will have child nodes, el.childElementCount > 0 provides a straightforward way to detect SSR and avoid hydration mismatches.

Alternatively, we have to add a more robust SSR flag from Inertia/AdonisJS.

Final Thoughts

I spent an entire day debugging hydration mismatch issues due to this missing detail. For developers coming from frameworks like Next.js, where SSR/CSR handling is built-in, this can be a frustrating experience.

advename avatar May 12 '25 07:05 advename

Happy to accept a PR for that We can add new steps here: https://docs.adonisjs.com/guides/views-and-templates/inertia#ssr

Also, your suggested fix might not work in all situations. people often add extra elements inside the root container, like a loading spinner for example. For example: https://sergiocarracedo.es/spa-vue-react-angular-app-loader/

So I would suggest just adding a quick note mentioning that you should conditionally call either hydrate or createRoot depending on the page you are rendering

Julien-R44 avatar May 12 '25 08:05 Julien-R44

@Julien-R44 I found, after further research, this InertiaJS issue asking for a SSR flag.

Also, your suggested fix might not work in all situations. people often add extra elements inside the root container, like a loading spinner for example. For example: https://sergiocarracedo.es/spa-vue-react-angular-app-loader/

I'm not sure if this concerns an AdonisJS project:

  1. In the link you shared, the author is switching between two HTML's to achieve what they want, which can also be achieved in AdonisJS by switching between two edge views.
  2. How would you add conditional HTML inside the @inertia() edge tag? This tag mounts the <div id="app">...</app> for React applications, which in turn is the el element in el.childElementCount > 0.

advename avatar May 14 '25 08:05 advename