nextjs - ✅(Solved) Fix CSS prioritization differs between next dev --turbopack and next build --turbopack [2 pull requests, 1 comments, 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#86704Fetched 2026-04-08 02:09:43
View on GitHub
Comments
1
Participants
1
Timeline
14
Reactions
0
Participants
Timeline (top)
subscribed ×4cross-referenced ×3mentioned ×2referenced ×2

Fix Action

Fixed

PR fix notes

PR #86775: fix: ensure consistent CSS ordering between dev and build with Turbopack

Description (problem / solution / changelog)

Fixes #86704

Problem

CSS prioritization differs between next dev --turbopack and next build --turbopack, causing layout breakage in production builds. Styles that load correctly in development mode are applied in a different order in production, breaking the visual appearance.

Root Cause

In flight-manifest-plugin.ts:297-318, CSS files are collected from webpack entrypoints using:

manifest.entryCSSFiles[chunkEntryName] = entrypoint
  .getFiles()
  .filter((f) => !f.startsWith('static/css/pages/') && f.endsWith('.css'))
  .map((file) => { /* ... */ })

## Changed files

- `packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts` (modified, +1/-0)


---

# PR #89615: Fix a css order violation

- Repository: vercel/next.js
- Author: lukesandberg
- State: open | merged: False
- Link: https://github.com/vercel/next.js/pull/89615

## 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

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.

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: 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)

CSS

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

next dev (local), 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

I’ve minimized the code a lot, so now it’s much easier to understand what’s going on. Please pay special attention to the page.

Reproduction link: http://localhost:3000/intensive

We are working with (pages).

  • If I delete the course page, the bug occurs.
  • If I move the <CourseProgramStudy /> block into the parent (page), the bug occurs.
  • Same thing with the intensive (page).
  • If I delete <WhoItSuitableFor />, the bug occurs.
  • If I move the content of <WhoItSuitableFor /> directly into page, the bug also occurs.
  • Additional

If I delete a component that is not used anywhere at all (<CourseProgramLessonCard />), the bug still occurs Something very strange is happening here.

<img width="1440" height="170" alt="Image" src="https://github.com/user-attachments/assets/825e2108-230c-4022-bb72-f5c0c86586a3" /> <img width="1481" height="183" alt="Image" src="https://github.com/user-attachments/assets/1c18ae79-989e-440c-b079-50f100325eaf" />

extent analysis

TL;DR

The most likely fix is to investigate and adjust the CSS bundling and prioritization in the next build --turbopack process to match the behavior in next dev --turbopack mode.

Guidance

  • Verify that the CSS files are being bundled and loaded in the same order in both development and production modes by checking the network requests and CSS file contents.
  • Investigate the next.config.js file for any configuration differences between development and production modes that could affect CSS bundling and prioritization.
  • Try to reproduce the issue with a minimal set of components and pages to isolate the root cause of the non-deterministic style bundling.
  • Check for any CSS imports or dependencies that could be causing the prioritization issues, and consider using a CSS reset or normalization to ensure consistent styling.

Example

No specific code example can be provided without further investigation, but checking the next.config.js file for configuration differences could involve looking for sections like:

module.exports = {
  //... other configurations...
  turbopack: {
    //... turbopack configurations...
  },
}

and comparing the configurations between development and production modes.

Notes

The issue seems to be related to the non-deterministic nature of the style bundling, which makes it challenging to reproduce and debug. Further investigation is needed to identify the root cause and provide a definitive fix.

Recommendation

Apply a workaround by adjusting the CSS bundling and prioritization in the next build --turbopack process to match the behavior in next dev --turbopack mode, as this is the most likely cause of the issue.

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 CSS prioritization differs between next dev --turbopack and next build --turbopack [2 pull requests, 1 comments, 1 participants]