AI & Agent Dev Bug Sandbox logo
AI & Agent Dev Bug Sandbox
Back to Radar

Turbopack Fails To SSR Link Nested Next/Dynamic CSS, Causing FOUC/CLS In App Router

In Next.js 16 with Turbopack, when a `next/dynamic` component is nested inside another `next/dynamic` component (both with `ssr: true` and CSS imports), the server-rendered HTML omits the `<link rel="stylesheet">` for the deeper component's CSS. This causes a flash of unstyled content (FOUC) and significant layout shift (CLS 0.4157) in production on slow connections. The issue is a regression introduced in 16.1.0 and persists through 16.4.0-canary.34. Webpack builds do not exhibit the problem.

highConfidence 85%Next.jsAffected V16.1.0Affected V16.1.7Affected V16.2.12Affected V16.3.4Affected V16.3.5Affected V16.4.0-Canary.34

Origin Analysis

Turbopack's module graph traversal (crates/next-api/src/module_graph.rs) records dynamic entries but then returns `GraphTraversalAction::Skip` for them. This prevents the traversal from descending into the dynamically imported module's own dynamic imports, so nested `next/dynamic` components are never collected into the React loadable manifest. Consequently, the `PreloadChunks` component cannot find the CSS chunks for those nested components and fails to render the SSR stylesheet link.
1. Create a Next.js 16 app with App Router and Turbopack enabled (default). 2. Define a server component `app/page.js` that statically imports a client component `components/Resolver.js`. 3. In `Resolver.js`, use `dynamic(() => import('./WidgetX'), { ssr: true })` (level 1). 4. Inside `WidgetX`, use another `dynamic(() => import('./VariantY'), { ssr: true })` (level 2). 5. Ensure `WidgetX` and `VariantY` each import their own CSS module. 6. Run `npm run build` and `npm run start`. 7. View the page source or use a script to list `<link rel="stylesheet">` tags. The level-1 CSS link is present, but the level-2 CSS link is missing. 8. Throttle network to Slow 3G and hard reload to observe the layout shift when the level-2 stylesheet loads after hydration.

Fixing Code Block

Edge Case Audit

This patch is unverified and based on analysis of the reported symptoms and suspected code location. Continuing traversal for dynamic entries may increase build time and memory usage, especially for large applications with many dynamic imports, because the traversal will now explore deeper portions of the module graph that were previously skipped. It could also lead to duplicate processing or infinite loops if the graph contains cycles involving dynamic entries; a visited-check or depth limit should be added if such issues arise during testing. Before deploying, run the full Next.js test suite and benchmark build performance. Rollback is straightforward by reverting the single-line change. No runtime code changes are required, but after applying the patch, rebuild the application and verify that all expected CSS links appear in SSR HTML.

Ecosystem Topology