Skip to content

Stylesheet not linked in SSR HTML for components in a chunk without a facade module (inlineStyles: false) #36343

Description

@rhelling

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions