Files
74cdcde87e feat(turbopack): add LocalPathOrProjectPath PostCSS config resolution (#91338)
### What?

Adds a new `experimental.turbopackLocalPostcssConfig` option and
corresponding `PostCssConfigLocation::LocalPathOrProjectPath` variant so
Turbopack can resolve `postcss.config.js` starting from the CSS file's
directory first, falling back to the project root.

Also fixes an issue that surfaces when multiple per-directory
`postcss.config.js` files are used:

**Evaluate pool asset name collisions** — output file names for the
Node.js evaluate pool are now derived from an xxh3 hash of the full
module ident rather than the bare file name, so multiple
`postcss.config.js` files in different directories no longer collide.

### Why?

Currently Turbopack uses `ProjectPathOrLocalPath` which checks the
project root first, then falls back to the local directory. This means
per-directory `postcss.config.js` files cannot override the root config
— the root config always wins. For projects that need different PostCSS
transforms in different directories (e.g. a monorepo with multiple apps
or style directories), the more specific config should take precedence.

### How?

**Turbopack PostCSS resolution**
(`turbopack/crates/turbopack-node/src/transforms/postcss.rs`):
- Added `PostCssConfigLocation::LocalPathOrProjectPath` enum variant
with doc comments on all variants
- Refactored `find_config_in_location()` to express the search order as
a `Vec<FileSystemPath>` built from a single `match`, making each
variant's strategy immediately clear

**Evaluate pool asset file naming**
(`turbopack/crates/turbopack-node/src/evaluate.rs`):
- Replaced file-name-based output path derivation with a deterministic
xxh3 hash of the full module ident
- Adds `turbo-tasks-hash` dependency to `turbopack-node`
- Eliminates name collisions when multiple entries share the same file
name (e.g. per-directory `postcss.config.js` files)

**Next.js config flag** (`packages/next/src/server/config-shared.ts`,
`config-schema.ts`):
- Added `experimental.turbopackLocalPostcssConfig: boolean` option
(opt-in, default `false`)
- Wired through `crates/next-core/src/next_config.rs` → both client and
server contexts (`next_client/context.rs`, `next_server/context.rs`)
- When `true`, uses `LocalPathOrProjectPath`; when `false`/unset, uses
the existing `ProjectPathOrLocalPath` (no behavior change by default)

**E2E test** (`test/e2e/app-dir/turbopack-postcss-multiple-configs/`):
- Runs in both **Turbopack dev and production modes** — skips webpack
only (webpack does not support function-valued PostCSS plugins and the
`turbopackLocalPostcssConfig` flag is Turbopack-specific)
- 5 style directories, each with its own `postcss.config.js` passing a
**unique color option** (`blue`, `purple`, `orange`, `cyan`, `magenta`)
to a shared PostCSS plugin, proving that per-directory config resolution
produces distinct results rather than all sharing the same transform
- Root `postcss.config.js` is a no-op — if only the root config were
used, CSS would remain unmodified
- Test validates all 15 elements (5 dirs × 3 files) render with CSS
module classes
- Test collects all CSS (inline `<style>` tags for dev, linked `.css`
files for production) and asserts each directory's expected color is
present and `color: red` is absent

**Documentation**
(`docs/01-app/03-api-reference/05-config/01-next-config-js/turbopackLocalPostcssConfig.mdx`,
`docs/01-app/03-api-reference/08-turbopack.mdx`):
- New dedicated reference page for
`experimental.turbopackLocalPostcssConfig` covering usage, behavior
table, and a directory-tree example
- Added to the experimental options table in the Turbopack API reference

## Test plan

- [x] `cargo check -p turbo-tasks-backend` passes
- [x] `cargo check -p turbopack-node` and `cargo check -p next-core`
pass
- [x] E2E test `turbopack-postcss-multiple-configs` validates
per-directory PostCSS config resolution across 5 directories with
distinct per-directory colors (Turbopack dev and production modes)
- [x] Test correctly skips in webpack mode and deployment
- [x] Documentation page and experimental options table updated

<!-- NEXT_JS_LLM_PR -->

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: Tobias Koppers <[email protected]>
2026-04-11 07:55:10 +02:00

64 lines
2.3 KiB
Plaintext

---
title: turbopackLocalPostcssConfig
description: Enable per-directory PostCSS config resolution in Turbopack so that the config closest to each CSS file takes precedence over the project root config.
---
The `turbopackLocalPostcssConfig` option changes how Turbopack resolves `postcss.config.js` files. When enabled, Turbopack searches for the config starting from the CSS file's own directory first, then falls back to the project root. By default, Turbopack checks the project root first, meaning a root-level `postcss.config.js` always takes precedence over configs in subdirectories.
This option is only relevant when using Turbopack (`next dev` or `next build`).
## Usage
```ts filename="next.config.ts" switcher
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
experimental: {
turbopackLocalPostcssConfig: true,
},
}
export default nextConfig
```
```js filename="next.config.js" switcher
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
turbopackLocalPostcssConfig: true,
},
}
module.exports = nextConfig
```
## Behavior
| Setting | Config resolution order |
| ----------------- | ----------------------------------- |
| `false` (default) | Project root → CSS file's directory |
| `true` | CSS file's directory → project root |
With the default behavior, a `postcss.config.js` at the project root is used for all CSS files, and per-directory configs are only applied if no root config exists. Enabling `turbopackLocalPostcssConfig` reverses this: per-directory configs take precedence, and the root config serves as the fallback.
## Example
This is useful for projects that need different PostCSS transforms in different directories, such as a monorepo with multiple apps or design system packages:
```
my-app/
├── postcss.config.js ← fallback (applied if no local config is found)
├── app/
│ └── page.module.css ← uses root config
└── packages/
└── ui/
├── postcss.config.js ← takes precedence for files in this directory
└── button.module.css ← uses packages/ui/postcss.config.js
```
## Version History
| Version | Changes |
| --------- | ----------------------------------------- |
| `v16.3.0` | `turbopackLocalPostcssConfig` introduced. |