Commit Graph

4 Commits

Author SHA1 Message Date
James Anderson 89036154cc fix(pages-router): unify client asset bootstrap (#2378)
* fix(pages-router): hydrate worker-backed dev pages

* refactor(pages-router): unify client asset bootstrap

* fix(app): preinitialize bootstrap dependencies

* test(app): distinguish preinit scripts from bootstrap

* fix(app): root-anchor bootstrap preinit modules

* test(app): cover bootstrap preinitialization in browsers

* fix(app): share client assets with nested SSR modules

* test(app): verify bootstrap preinit CSP nonces

* test(app): serve CSP nonce in worker fixture

* fix(test): read CSP script nonce property

* fix(cloudflare): emit client assets beside final workers

* fix(build): resolve pages client assets from source

* fix(app): register client assets in ssr graph

* fix(pages-router): harden client bootstrap resolution

* fix(build): emit client assets for node outputs

* fix(build): package client assets in standalone output

* test(app): reference worker nonce fixture explicitly

* fix(build): preserve client assets across environments

* refactor(build): unify client asset outputs

* fix(build): stabilize custom client asset outputs

* fix(build): finalize client assets after app builds

* fix(build): reuse client assets in hybrid builds

* fix(build): scope hybrid client assets metadata

* test(e2e): colocate Cloudflare worker fixture
2026-06-26 19:48:26 +01:00
James Anderson d504f022b0 fix(assets): default assetsDir to _next/static (Next.js parity) (#1411)
* fix(assets): default assetsDir to _next/static (Next.js parity)

Closes #1337. Supersedes #1383.

vinext's default `assetsDir` was Vite's historical `assets/`, so URL emission
(`/_next/static/...` via `resolveAssetUrlPrefix("")`) and on-disk layout
(`dist/client/assets/...`) disagreed in the empty-`assetPrefix` case. The
`build.assetsDir` and `experimental.renderBuiltUrl` Vite overrides only
applied when `assetPrefix` was configured, leaving the no-prefix branch
serving from a different path than the URL contract Next.js's client
runtime and test harness assert against.

This commit flips the default to Next.js's canonical layout:

- `resolveAssetsDir("")` returns `_next/static` (was `assets`)
- `build.assetsDir` is now set unconditionally from `resolveAssetsDir`
- Vite's default `base + assetsDir` composition produces correct URLs
  in the no-prefix case; `renderBuiltUrl` stays gated on `assetPrefix`
  because it's only needed for the configured cases
- Drops the legacy `/assets/` branches in prod-server (3 sites) and
  static-file-cache (hard cutover — see PR description for rationale)
- Updates `_headers` generation, precompress default, fonts plugin
  `DEFAULT_ASSETS_DIR` to match the new default

With the layout aligned, invalid `_next/static/*` requests naturally
return plain-text `404 + "Not Found"` from the static-file layer instead
of falling through to the page renderer (which would produce an HTML
404 with bootstrap scripts and CSS). This replaces #1383's parity
short-circuit with the natural code path:

- Node prod-server (App + Pages branches): missing asset under
  `resolveAppRouterAssetPath` returns `text/plain; charset=utf-8` 404
- Cloudflare worker entry: `isNextStaticPath` recognises asset-shape
  after ASSETS-binding misses; returns `notFoundStaticAssetResponse`

Mirrors Next.js: packages/next/src/server/lib/router-server.ts.

Adds `tests/invalid-static-asset-404.test.ts` (3 App Router + 3 Pages
Router cases — no prefix / basePath / assetPrefix) ported from Next.js
e2e suites. Updates 11 source files + 12 test files; 914+ tests pass
in the touched suites (asset-prefix, app-router, pages-router, deploy,
font-google, static-file-cache, serve-static, precompress, features,
routing, isr-cache, build-optimization, app-rsc/ssr, standalone,
middleware, shims).

Ported from Next.js:
- test/e2e/invalid-static-asset-404-app/*.test.ts
- test/e2e/invalid-static-asset-404-pages/*.test.ts

* fix(e2e): update cloudflare-pages-router hydration spec for _next/static

* PR #1411 approved. Clean fix, 2 nits.

Co-authored-by: james-elicx <james-elicx@users.noreply.github.com>

* refactor(assets): derive isHashed checks from ASSET_PREFIX_URL_DIR

Replace hardcoded '_next/static' string literals in runtime code paths
with the ASSET_PREFIX_URL_DIR constant so the asset URL convention has
a single source of truth.

- server/prod-server.ts isHashed: pathname.includes(`/${ASSET_PREFIX_URL_DIR}/`)
- server/prod-server.ts public-dir guard: startsWith(`/${ASSET_PREFIX_URL_DIR}/`)
- server/static-file-cache.ts isHashed: both startsWith + includes derived
- plugins/fonts.ts DEFAULT_ASSETS_DIR = ASSET_PREFIX_URL_DIR

Behavioural no-op — the constant is '_next/static' so the resolved
strings are identical. Centralises the URL contract so future changes
to ASSET_PREFIX_URL_DIR (or a refactor that derives it from build
config) flow through to every consumer.

Doc-comment references to '_next/static' left in place — they
describe the canonical value for human readers and are not code paths.

---------

Co-authored-by: ask-bonk[bot] <ask-bonk[bot]@users.noreply.github.com>
Co-authored-by: james-elicx <james-elicx@users.noreply.github.com>
2026-05-21 20:36:04 +00:00
Stephen Zhou bb6dda840c fix: standalone dependency resolution (#765)
* fix: standalone dependency resolution

* tweaks
2026-04-03 12:03:42 +01:00
Sankalp Mukim f6aedd3615 Add standalone self-host output and align init production scripts (#178)
* feat: add standalone self-host output for vinext build

Generate dist/standalone from output: 'standalone' with a runnable server entry and runtime deps, and align init scripts with vinext build/start for production self-host workflows.

* fmt

* Address bonk review: fix duplicate PHASE constant, complete lazy-chunks extraction, remove dead imports, add process.exit to standalone path

* Address bonk nits: document regex patterns, move PHASE re-exports to bottom, add assets field to BuildManifestChunk

* refactor(standalone): use Vite bundle graph for server externals instead of regex scan

Replace the fragile post-build regex scan of emitted JS files and the
package.json#dependencies over-seeding with a new
vinext:server-externals-manifest Vite plugin. The plugin collects
chunk.imports + chunk.dynamicImports from the SSR/RSC writeBundle hook
and writes dist/server/vinext-externals.json — the authoritative,
compiler-derived list of packages left external in the server bundle.

emitStandaloneOutput now reads this manifest as its sole seed for the
BFS node_modules copy, so standalone output only includes packages the
server actually imports at runtime. Removed collectServerExternalPackages,
walkFiles, the four regex constants, and the package.json deps seeding.

* fix(standalone): copy vinext runtime deps, add node_modules filter to cpSync, remove unused pendingWrites

* fix(standalone): address round-6 bonk nits — explicit root, manifest comment, pre-flight check, bare-specifier comment, export-from re-export

* fix(standalone): address round 7 bonk review comments

- harden outDir resolution in server-externals-manifest: replace fragile
  walk-up heuristic with direct basename check (ssr -> dirname, else dir),
  avoiding misfires when a user's project path contains a 'server' segment
- remove redundant 'chunk' alias in server-externals-manifest writeBundle;
  use 'item' directly after the type guard narrows it to OutputChunk
- add node_modules filter to vinext dist/ cpSync in standalone.ts, matching
  the same defensive filter used for app package copies
- rewrite standalone server.js as pure ESM using import.meta.dirname
  (Node >= 21.2, vinext requires >= 22); drop CJS require/__dirname and
  switch standalone package.json to 'type':'module'
- add explanatory comment for DEFAULT_PHASE constant in next-config.ts
- add comment in cli.ts noting pre-flight path and resolveVinextPackageRoot
  must stay in sync

* fix(standalone): address round 8 bonk review comments

- fix test import: use 'vite-plus/test' instead of 'vitest' in
  standalone-build.test.ts, matching the convention used by every other
  test file in the repo (documented in AGENTS.md)
- extract resolveVinextPackageRoot into utils/vinext-root.ts so cli.ts
  and standalone.ts share a single source of truth; removes the coupling
  comment added in round 7 and the duplicated path traversal logic
- add node_modules filter to dist/client and dist/server cpSync calls for
  defensive consistency with the vinext dist/ and package copies

* interface -> type

* fix(standalone): address round 9 bonk review comments

- add node_modules filter to public/ cpSync for defensive consistency
  with all other cpSync calls in emitStandaloneOutput
- warn on malformed vinext-externals.json instead of silently returning
  an empty list; includes the error string to aid debugging
- add doc comment on copyPackageAndRuntimeDeps clarifying that the return
  value is the full accumulated set (including pre-existing entries), not
  only packages copied by the current call
- document HOST (not HOSTNAME) env var in README standalone section, with
  a note explaining the difference from Next.js standalone behaviour

* fix(standalone): address round 10 bonk review comments

- add comment in writeStandaloneServerEntry explaining why the import of
  vinext/server/prod-server is intentional as a static import: it is a
  documented exports-map entry, always present in the copied dist tree,
  and a static import gives a clearer ERR_MODULE_NOT_FOUND at startup
- generalise outDir sub-directory detection in server-externals-manifest:
  replace basename === 'ssr' with basename !== 'server' so any future
  sub-directory environment (e.g. 'edge') is handled without code changes
- add JSDoc on runtimeDeps clarifying that it returns both dependencies
  and optionalDependencies

* fix(next-config): drop PHASE_DEVELOPMENT_SERVER from re-export

No external consumer imports it from next-config.ts — only
PHASE_PRODUCTION_BUILD is used externally (by cli.ts). Removing it
minimises the public surface area of the module.

---------

Co-authored-by: GPU VM <copilot-gpu-vm@example.com>
Co-authored-by: James <james@eli.cx>
2026-03-30 20:42:40 +01:00