Environment
|
|
| Operating system |
macOS 25.6.0 |
| CPU |
Apple M5 Pro (18 cores) |
| Node.js version |
v22.19.0 |
| nuxt/cli version |
3.37.0 |
| Package manager |
pnpm@10.33.2 |
| Nuxt version |
4.4.2 |
| Nitro version |
2.13.4 |
| Builder |
vite@7.3.6 |
| Config |
compatibilityDate, features, vite |
| Modules |
- |
|
|
| Operating system |
macOS 25.6.0 |
| CPU |
Apple M5 Pro (18 cores) |
| Node.js version |
v22.19.0 |
| nuxt/cli version |
3.37.0 |
| Package manager |
pnpm@10.33.2 |
| Nuxt version |
4.5.2 |
| Nitro version |
2.13.4 |
| Builder |
vite@8.2.2 |
| Config |
compatibilityDate, features, vite |
| Modules |
- |
Reproduction
Nuxt 4.5: https://github.com/rhelling/nuxt-repro-manifest-css
Nuxt 4.4: https://github.com/rhelling/nuxt-repro-manifest-css-422
Initial steps
pnpm i && pnpm build && node .output/server/index.mjs
Compare page styles before and after hydration, note the missing stylesheets (they are preloaded though).
Expected
Every component that is server-rendered on a page has its stylesheet linked in the <head> of that page, regardless of how the bundler chunked it
Actual
Components that land in a chunk without a facade module render unstyled on first paint
Describe the bug
With features.inlineStyles: false, a page can server-render a component while its scoped CSS is not linked in the <head>. The element is in the HTML, but the stylesheet that styles it only appears as prefetch <link rel="prefetch" as="style">. The browser applies it once the client bundle loads the chunk, so the component is unstyled on first paint.
The two repositories with a reproduction contain two small cases that trigger this:
-
Nuxt 4.4.2, Rollup: the default layout calls a composable that is also used by a lazily loaded, client-only component inside the layout. The layout ends up in a chunk without a facade. The manifest has no layouts/default.vue key and the layout CSS is missing on every page.
-
Case B (Nuxt 4.5.2, rolldown): a component is rendered through <LazySuggestedProducts /> on one page and imported statically by a component on another page. It ends up in a shared chunk without a facade. The manifest has no components/suggested-products.vue key. On the page that renders it lazily the CSS is missing; on the page that imports it statically the CSS is linked.
Additional context
-
The issue happens when the bundler puts a component into a chunk that has no facade module. In the client manifest such a chunk is keyed by its file name (_<hash>.js) and has no src, so there is no manifest entry for the component's own id (for example layouts/default.vue or components/suggested-products.vue). The server renderer looks the component up by that id when it collects the stylesheets for the page, finds nothing, and skips the CSS
-
Whether a component gets its own manifest key depends on the whole import graph. Adding or removing a single shared import elsewhere in the app changes the chunking and makes the CSS appear or disappear, which made this look random
Logs
Environment
macOS 25.6.0Apple M5 Pro (18 cores)v22.19.03.37.0pnpm@10.33.24.4.22.13.4vite@7.3.6compatibilityDate,features,vite-macOS 25.6.0Apple M5 Pro (18 cores)v22.19.03.37.0pnpm@10.33.24.5.22.13.4vite@8.2.2compatibilityDate,features,vite-Reproduction
Nuxt 4.5: https://github.com/rhelling/nuxt-repro-manifest-css
Nuxt 4.4: https://github.com/rhelling/nuxt-repro-manifest-css-422
Initial steps
pnpm i && pnpm build && node .output/server/index.mjsCompare page styles before and after hydration, note the missing stylesheets (they are preloaded though).
Expected
Every component that is server-rendered on a page has its stylesheet linked in the
<head>of that page, regardless of how the bundler chunked itActual
Components that land in a chunk without a facade module render unstyled on first paint
Describe the bug
With
features.inlineStyles: false, a page can server-render a component while its scoped CSS is not linked in the<head>. The element is in the HTML, but the stylesheet that styles it only appears as prefetch<link rel="prefetch" as="style">. The browser applies it once the client bundle loads the chunk, so the component is unstyled on first paint.The two repositories with a reproduction contain two small cases that trigger this:
Nuxt 4.4.2, Rollup: the default layout calls a composable that is also used by a lazily loaded, client-only component inside the layout. The layout ends up in a chunk without a facade. The manifest has no
layouts/default.vuekey and the layout CSS is missing on every page.Case B (Nuxt 4.5.2, rolldown): a component is rendered through
<LazySuggestedProducts />on one page and imported statically by a component on another page. It ends up in a shared chunk without a facade. The manifest has nocomponents/suggested-products.vuekey. On the page that renders it lazily the CSS is missing; on the page that imports it statically the CSS is linked.Additional context
The issue happens when the bundler puts a component into a chunk that has no facade module. In the client manifest such a chunk is keyed by its file name (
_<hash>.js) and has nosrc, so there is no manifest entry for the component's own id (for examplelayouts/default.vueorcomponents/suggested-products.vue). The server renderer looks the component up by that id when it collects the stylesheets for the page, finds nothing, and skips the CSSWhether a component gets its own manifest key depends on the whole import graph. Adding or removing a single shared import elsewhere in the app changes the chunking and makes the CSS appear or disappear, which made this look random
Logs