fix(inertia): set serverRendered dynamically to prevent SSR crash
๐ Linked issue
Resolves #5254
โ Type of change
- [ ] ๐ Documentation (updates to the documentation or readme)
- [x] ๐ Bug fix (a non-breaking change that fixes an issue)
- [ ] ๐ Enhancement (improving an existing functionality)
- [ ] โจ New feature (a non-breaking change that adds functionality)
- [ ] ๐งน Chore (updates to the build process or auxiliary tools and libraries)
- [ ] โ ๏ธ Breaking change (fix or feature that would cause existing functionality to change)
๐ Description
The Inertia stub currently hardcodes the payload.serverRendered value to false. This causes the application to crash during Server-Side Rendering (SSR). This change replaces the hardcoded value with a dynamic check (typeof window === "undefined") to accurately determine if the code is running on the server.
This solve SSR crashing with ReferenceError: document is not defined
๐ Checklist
- [x] I have linked an issue or discussion.
- [ ] I have updated the documentation accordingly.
https://github.com/nuxt/ui/pull/5347#issuecomment-3506599510 @Barbapapazes
https://github.com/nuxt/ui/pull/5347#issuecomment-3506599510
@Barbapapazes
Nice! I'll have a look.
Even with this fix, how do you manage the FOUC? (unhead does not inject the styles and I'm not understanding why, could be related to the head management of Inertia)
https://github.com/user-attachments/assets/1563829f-69aa-4ee4-8914-a436d88f8b67
source: https://github.com/nuxt-ui-templates/starter-laravel/pull/23
Hi,
The issue is that Inertia handles its own head management, so it doesn't pick up the tags generated by Nuxt UI's internal unhead instance during the SSR pass.
The fix is to create a dedicated unhead instance, let Nuxt UI populate it during the setup, and then asynchronously push its rendered tags into Inertia's head array just before the response is sent. This ensures Nuxt UI's styles are present in the initial HTML.
Here's my createServer in resources/js/ssr.ts:
createServer(
(page) => {
const head = createHead();
return createInertiaApp({
page,
render: renderToString,
resolve: (name) =>
resolvePageComponent(
`./pages/${name}.vue`,
import.meta.glob<DefineComponent>("./pages/**/*.vue")
),
setup: ({ App, props, plugin }) =>
createSSRApp({ render: () => h(App, props) })
.use(plugin)
.use(head)
.use(ui),
}).then(async (app) => {
const payload = await renderSSRHead(head);
app.head.push(payload.headTags);
return app;
});
},
{ cluster: true }
);
To prevent the theme from flashing on hydration, i use a small script in app.blade.php:
<script>
try {
const theme = localStorage.getItem('vueuse-color-scheme');
if (theme === 'dark' || (theme === null && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
} catch (e) { /* ignore */ }
</script>
On a related note, I saw the Laravel starter kit still uses Ziggy, while the official Laravel kits have moved on to Wayfinder(https://github.com/laravel/vue-starter-kit/pull/178). I'd be happy to open a separate PR to update it and include these SSR fixes. I've already implemented this in my own starter kit if you want to take a look: https://github.com/y-l-g/saasterkit. By the way https://saasterkit.com is server rendered.
Let me know if this approach works for you.
Thanks @y-l-g ๐ A PR on https://github.com/nuxt-ui-templates/starter-laravel would be greatly appreciated, I made this template based on the Laravel Kit back then but in all honesty I know nothing about Laravel ๐ฌ
@benjamincanac do you think a new documentation page called SSR for the Vue.js version would be good? In the "Integrations" section
@miguilimzero If it's short enough it could go in the Vue installation page otherwise we can add an Integration page yes!