Incompatibility with Inertia SSR Server
Is this bug related to Nuxt or Vue?
Vue (Inertia in particular)
Package
v4.x
Version
v4.0.1
Reproduction
The UI library is currently incompatible with the Inertia SSR server: https://inertiajs.com/server-side-rendering.
The compiled SSR file tries to alter the document.head which is not possible in SSR:
if (nuxtApp.isHydrating && !nuxtApp.payload.serverRendered) {
const style = document.createElement("style");
style.innerHTML = root.value;
style.setAttribute("data-nuxt-ui-colors", "");
document.head.appendChild(style);
headData.script = [{
innerHTML: "document.head.removeChild(document.querySelector('[data-nuxt-ui-colors]'))"
}];
}
It is currently trying to do that as the payload.serverRendered is set to false in the useNuxtApp() function in the compiled file:
function useNuxtApp() {
return {
isHydrating: true,
payload: { serverRendered: false },
hooks,
hook: hooks.hook
};
}
Changing the value manually to true seems to fix the issue, so I believe it is just a matter of detecting if it is being compiled to Inertia SSR or not.
I managed to automate this change by using this command on my deploy script:
sed -i -E 's/(serverRendered[[:space:]]*:[[:space:]]*)false/\1true/' bootstrap/ssr/ssr.js
And the combination of the following commands for testing:
npm run build && sed -i -E 's/(serverRendered[[:space:]]*:[[:space:]]*)false/\1true/' bootstrap/ssr/ssr.js && php artisan inertia:start-ssr
However the CSS of the SSR version is not correct as its missing many -ui- variables. It seems to be happening because the SSR doesn't have the style tag with the id nuxt-ui-colors with the missing variables.
Icons are also not present in the SSR version.
For the styles, the workaround I found is in ssr.ts:
import { createInertiaApp } from '@inertiajs/vue3';
import createServer from '@inertiajs/vue3/server';
import ui from '@nuxt/ui/vue-plugin';
import { createHead, renderSSRHead } from '@unhead/vue/server';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { createSSRApp, DefineComponent, h } from 'vue';
import { renderToString } from 'vue/server-renderer';
import PersistentLayout from './layouts/PersistentLayout.vue';
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 },
);
This solves the color issue and allows you to use Unhead in your project instead of Inertia's Head (Unhead is better).
For the icons, the only way I found is to use unplugin-icons and pass the icon component instead of its name everywhere it's needed in UIcon UButton UCard and so on... (see https://github.com/nuxt/ui/pull/4766)