nextjs - ✅(Solved) Fix [Turbopack]: CSS prioritization differs between next dev --turbopack and next build --turbopack [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vercel/next.js#87321Fetched 2026-04-08 02:07:14
View on GitHub
Comments
0
Participants
1
Timeline
5
Reactions
0
Participants
Timeline (top)
cross-referenced ×2labeled ×2issue_type_added ×1

Fix Action

Fixed

PR fix notes

PR #89615: Fix a css order violation

Description (problem / solution / changelog)

What

Fix CSS cascade order violations in production builds when shared CSS chunks are emitted in the wrong position relative to non-shared chunks.

When a component imports and overrides styles from another component, the CSS cascade order could be violated in production builds. For example:

CardWrapper.module.css (sets background: purple)
  └── imports Card.module.css (sets background: white)

The expected behavior is that CardWrapper's purple background overrides Card's white background. However, in production builds, the shared chunk containing Card's CSS could be served before or after the wrong items, causing incorrect cascade order.

Why

The style chunking algorithm (style_groups.rs) groups CSS modules into shared batches to reduce HTTP requests. The chunk emission code (style_production.rs) must then emit these batches in the correct order relative to non-shared items.

The previous approach was incorrect, there we emitted the shared batch when the first of its members appeared in DFS post-order. This could emit the batch too early, before items that depend on later batch members. The alternate approach of 'emit on last member' also doesn't work, since we can emit the batch too late, after non-shared items that should have come after the batch.

The core issue is that DFS post-order is an over-specification: it produces a single linear ordering of all CSS modules, but a shared batch groups members whose positions may interleave with unrelated non-shared items. No fixed "emit at position X" strategy works, because the correct position depends on the actual dependency relationships between emission units (batches and individual items). Even dynamically detecting this doesn't work for the simple reason that we can batch modules in a way that will always violate a given routes order.

Concrete example

DFS post-order for an endpoint: [A, B, C, D, E] where {A, C} form a shared batch and B, D, E are non-shared.

  • If A must precede B in the cascade, the batch must come before B: [{A,C}, B, D, E]
  • Emit-on-first gives: [{A,C}, B, D, E] — correct by accident
  • Emit-on-last gives: [B, {A,C}, D, E]wrong, B loads before A

But in a different scenario where C depends on B:

  • Emit-on-first gives: [{A,C}, B, D, E]wrong, C loads before B

Neither heuristic is generally correct. The ordering must be computed from actual dependency edges.

How

style_groups.rs

  • Expose module_dependents: The compute_style_groups function already computed a module_dependents map (module X → set of modules that must come after X), but discarded it. Now it's returned as a field on StyleGroups so the emission code can use it.
  • FxIndexMap for chunk_group_indices: Changed from FxHashMap to FxIndexMap for deterministic iteration order. Since chunk groups are processed sequentially, insertion order equals sorted order. Non-deterministic iteration caused CSS ordering bugs (see #89523).
  • Collect dependents from all chunk groups: The previous approach found the shortest chunk group and only looked for dependents there (as an optimization). This was incorrect — it could miss dependents that only appear in other chunk groups. Now we union potential dependents from all chunk groups.
  • Use FxHashSet for dependents: Changed from Vec to FxHashSet for deduplication across multiple groups.
  • Contiguity check for batch candidates: When considering a candidate module for a shared batch, verify that in every route containing both the candidate and existing batch members, no unprocessed non-batch module exists in the gap between them. Without this, the batching algorithm could group modules that are non-contiguous in some routes' orderings (e.g. shared1 and shared2 when a route-unique module sits between them), creating emission units where the intervening module can't be correctly placed.

style_production.rs

Replaced the countdown-based emission with a topological sort over emission units:

  1. Flatten all ChunkItemWithAsyncModuleInfo for this endpoint and assign each to an EmissionUnit (either a shared Batch or a non-shared Item).
  2. Build a dependency graph over emission units by translating the module-level edges from module_dependents. Edges within the same unit are dropped (intra-batch ordering is handled by the batch itself).
  3. Kahn's algorithm topological sort with a min-heap using DFS post-order position as a tiebreaker for determinism among units with no dependency relationship.
  4. Emit chunks in the topologically sorted order via make_chunk.

This guarantees that if module X must precede module Y in the cascade, then X's emission unit is always emitted before Y's, regardless of whether they're in shared batches, non-shared items, or a mix.

Test plan

  • Existing test/e2e/app-dir/css-order/ tests cover many CSS ordering scenarios across pages
  • Existing test/e2e/app-dir/initial-css-order/ tests verify correct cascade winner
  • Snapshot tests in turbopack/crates/turbopack-tests/tests/snapshot/css/split-shared/ exercise the shared batching path
  • New test/e2e/app-dir/css-order-shared-chunks/ regression test: two routes share CSS modules that interleave with route-specific modules, verifying the shared batch is emitted in the correct position relative to non-shared items so the CSS cascade order is preserved.

Fixes #89523 Fixes #83941 Fixes #86704 Fixes #88604 Fixes #87321 Fixes #86459

Changed files

  • test/e2e/app-dir/css-order-shared-chunks/app/a/page.tsx (added, +19/-0)
  • test/e2e/app-dir/css-order-shared-chunks/app/b/page.tsx (added, +19/-0)
  • test/e2e/app-dir/css-order-shared-chunks/app/layout.tsx (added, +11/-0)
  • test/e2e/app-dir/css-order-shared-chunks/app/page.tsx (added, +3/-0)
  • test/e2e/app-dir/css-order-shared-chunks/app/styles/shared1.module.css (added, +3/-0)
  • test/e2e/app-dir/css-order-shared-chunks/app/styles/shared2.module.css (added, +3/-0)
  • test/e2e/app-dir/css-order-shared-chunks/app/styles/unique-a-final.module.css (added, +3/-0)
  • test/e2e/app-dir/css-order-shared-chunks/app/styles/unique-a.module.css (added, +3/-0)
  • test/e2e/app-dir/css-order-shared-chunks/app/styles/unique-b-final.module.css (added, +3/-0)
  • test/e2e/app-dir/css-order-shared-chunks/app/styles/unique-b.module.css (added, +3/-0)
  • test/e2e/app-dir/css-order-shared-chunks/css-order-shared-chunks.test.ts (added, +84/-0)
  • test/e2e/app-dir/css-order-shared-chunks/next.config.js (added, +3/-0)
  • turbopack/crates/turbopack-core/src/module_graph/style_groups.rs (modified, +833/-172)

Code Example

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.6.0
Binaries:
  Node: 20.9.0
  npm: 10.8.2
Relevant Packages:
  next: 15.6.0-canary.12
Next.js Config:
  output: N/A
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

https://github.com/GlebKodrik/turbopack-style/

To Reproduce

Use route my project with problem : http://localhost:3000/intensive/31BoMCUUsroffEMtAmd6ALUmZ0y or https://turbopack-style.vercel.app/intensive/31BoMCUUsroffEMtAmd6ALUmZ0y

To Reproduce

Run the app in development with next dev --turbopack → Styles load correctly, no issues observed.

Build the app with next build --turbopack and then start with next start → Styles are applied in a different priority/order compared to dev mode.

Notes:

The issue reproduces 100% on our current setup. Sometimes deleting the pages folder or a random component in entities makes the bug disappear, which makes the behavior unpredictable.

Current vs. Expected behavior

Current vs. Expected behavior

Current: In production (next build --turbopack + next start), CSS order/priority differs from development, breaking the layout. Expected: Style prioritization should be consistent between dev and build modes when using Turbopack.

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.6.0
Binaries:
  Node: 20.9.0
  npm: 10.8.2
Relevant Packages:
  next: 15.6.0-canary.12
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

Turbopack, CSS

Which stage(s) are affected? (Select all that apply)

next build (local)

Additional context

Additional context Happens locally with next build --turbopack → next start. Cannot reproduce when running next dev --turbopack. Issue sometimes disappears after removing a random folder/component, which suggests non-deterministic style bundling.

1 image - next dev --turbopack 2 image next build --turbopack → next start

Give me some kind of answer already!

<img width="1440" height="170" alt="Image" src="https://github.com/user-attachments/assets/2715b5f5-4c1e-4f07-af90-55310fd00274" /> <img width="1481" height="183" alt="Image" src="https://github.com/user-attachments/assets/1cbc6a82-f010-45bd-a9fc-936a072babcf" />

extent analysis

TL;DR

  • The most likely fix involves ensuring consistent style prioritization between development and build modes when using Turbopack, potentially by adjusting the Next.js configuration or Turbopack settings.

Guidance

  • Verify that the issue persists across different environments and Next.js versions to rule out version-specific bugs.
  • Investigate the Turbopack configuration and Next.js settings for any discrepancies between development and build modes that could cause the style prioritization difference.
  • Consider temporarily removing or reordering CSS files to identify if a specific file or set of files is causing the prioritization issue.
  • Review the Next.js documentation and Turbopack configuration options for any settings related to CSS handling and prioritization.

Example

No specific code example can be provided without further details on the project's configuration and codebase.

Notes

The issue's non-deterministic nature and resolution upon removing random components suggest a complex interaction between Turbopack, Next.js, and the project's CSS files. Further investigation into the project's specific configuration and code is necessary.

Recommendation

Apply a workaround by adjusting the Next.js configuration or Turbopack settings to ensure consistent style handling between development and build modes, as the root cause of the issue is likely related to the differences in how styles are processed in these environments.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

nextjs - ✅(Solved) Fix [Turbopack]: CSS prioritization differs between next dev --turbopack and next build --turbopack [1 pull requests, 1 participants]