cloudflare / cloudflare/vinext
RSC assets manifest emits modulepreload URLs for JS chunks that are never built (CSS-only chunks)
- Dominant language
- TypeScript
- Stars
- 8.8k
- Forks
- 406
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 120
Description
### What happened
After `vinext build`, `dist/server/__vite_rsc_assets_manifest.js` contains `` URLs for JS chunks that were **never emitted**. Every one of them 404s on each page load.
The CSS side is fine — the CSS asset exists and is served correctly. Only the JS URL derived for the same chunk is phantom.
The trigger is a plugin that puts CSS into its own chunk. In our case `@devup-ui/vite-plugin` (a build-time CSS-in-JS extractor) groups its generated `devup-ui*.css` modules into dedicated chunks via Rolldown `output.codeSplitting.groups`. Those chunks contain **only CSS and no JS**, so no `.js` file is produced — but the RSC assets manifest still emits a `.js` URL for them.
### Measurements
Next.js 16.3.2 vs vinext 1.0.0-beta.8, same source, Node target, `output: 'standalone'`:
| app | routes | `modulepreload` in `/ko/` | broken |
| --- | --- | --- | --- |
| marketing site | 13 | 102 | **33** |
| admin console | 110 | — | **286 phantom refs in manifest** |
Verified per reference:
- Vite client manifest (`dist/client/.vite/manifest.json`) contains **only** the CSS asset for these modules — correct.
- RSC assets manifest contains 33 (resp. 286) `/_next/static/chunks/devup-ui*.js` URLs; **none of the files exist on disk**.
- Same probe against a Next.js build of the same app: **0 broken references**.
Example names (note the `.css-` infix, i.e. the chunk is the CSS group):
```
/_next/static/chunks/devup-ui.css-cWLjIIuA.js
/_next/static/chunks/devup-ui-15.css-C8WMy7Jo.js
/_next/static/chunks/devup-ui-23.css-CqAP8F3Z.js
```
### Impact
Low severity, but real:
- Each 404 body is 9 bytes, so **~0.3 KB total** — not a size problem.
- They are `modulepreload` **hints**, so the browser ignores the failures. Rendering, hydration and interaction are unaffected (our a11y and e2e suites pass on vinext).
- The cost is **33 wasted HTTP requests per page load** (and considerably more on the larger app), plus noise for anyone whose CI asserts "no failed requests".
We only noticed because our smoke suite fails a route if any same-origin request fails. That check had to be taught to treat preload hints separately.
### Reproduction
1. Take an App Router project using a Vite plugin that emits CSS-only chunks (e.g. `@devup-ui/vite-plugin` >= 1.0.64, which uses `build.rolldownOptions.output.codeSplitting.groups`).
2. `vinext build` with `output: 'standalone'`.
3. Compare `dist/client/.vite/manifest.json` (CSS present, no JS entry) with `dist/server/__vite_rsc_assets_manifest.js` (JS URLs present).
4. Check the filesystem: the referenced `.js` files do not exist.
5. Load any page — the browser requests each of them and receives 404.
### Expected
A chunk that emits no JS should not contribute a JS URL to the RSC assets manifest. Only its CSS asset should be referenced.
### Environment
- vinext 1.0.0-beta.8, Vite 8.2.2 (Rolldown)
- Next.js 16.3.2 API surface, App Router, RSC
- `--platform=node`, `output: 'standalone'`
- Windows dev machine; production target Linux/Docker
Happy to provide the full manifest dumps or test against a patch.
Contributor guide
Research direction
Run vinext build with a CSS-only chunk and compare dist/client/.vite/manifest.json with dist/server/__vite_rsc_assets_manifest.js. Start from the manifest generation path and trace how chunk assets become modulepreload URLs. Done means CSS assets remain referenced, while CSS-only chunks contribute no phantom .js URLs and the referenced files all exist on disk.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript, vite
- Domain
- build-system
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100