mirror of
https://github.com/cloudflare/vinext.git
synced 2026-09-14 19:04:59 +08:00
vinext@0.1.8
282 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2cbd2a0356 |
fix(build): apply default server externals (#2257)
* fix(build): externalize Next.js default server packages * test(node): cover externalized shiki in standalone output |
||
|
|
a34c083e88 |
fix(app-router): match interception route precedence (#2231)
* fix(app-router): prioritize specific interception routes * fix(app-router): match interception route precedence * Reviewed PR #2231: LGTM, minor notes Co-authored-by: james-elicx <james-elicx@users.noreply.github.com> * fix(app-router): align interception route tiebreaks --------- Co-authored-by: ask-bonk[bot] <ask-bonk[bot]@users.noreply.github.com> Co-authored-by: james-elicx <james-elicx@users.noreply.github.com> |
||
|
|
d65dd4c601 |
fix(router): ignore stale initial popstate with i18n (#2230)
* fix(router): ignore stale initial popstate with i18n * Review done: no issues found Co-authored-by: james-elicx <james-elicx@users.noreply.github.com> * test(router): run i18n popstate coverage in existing project --------- Co-authored-by: ask-bonk[bot] <ask-bonk[bot]@users.noreply.github.com> Co-authored-by: james-elicx <james-elicx@users.noreply.github.com> |
||
|
|
6e998ec114 |
fix(middleware): run on missing build assets (#2242)
* fix(middleware): run on missing build assets * fix(middleware): preserve missing asset rewrites on workers |
||
|
|
7b21c429c0 |
fix(app-router): preserve global error details (#2249)
* fix(app-router): preserve global error details * fix(app-router): preserve default global error semantics |
||
|
|
fbf68d588e |
fix(app-router): restore cache and parallel route parity (#2261)
* fix(app-router): preserve searchParams cache variants * test(app-router): cover RSC metadata cache variants * fix(app-router): isolate metadata searchParams observation * fix(app-router): preserve static ISR cache proofs * fix(app-router): limit queryless cache probes * fix(app-router): skip redundant cache proof probes * fix(app-router): harden incomplete cache proofs * fix(app-router): observe loading page execution * fix(app-router): preserve exotic page exports * fix(app-router): preserve client page ISR * fix(app-router): avoid duplicate queryless page execution * fix(app-router): observe viewport search params * fix(app-router): clear force-static search params * fix(app-router): isolate intercept static context * fix(app-router): preserve intercept request context * fix(app-router): isolate action static context * fix(app-router): match static request API semantics * fix(app-router): preserve dynamic segment constraints * fix(app-router): apply slot-only segment config * fix(app-router): preserve draft mode reads in static errors * fix(app-router): include nested slot constraints * fix(app-router): refresh slot layout constraints * fix(app-router): render nested slot layouts * fix(app-router): preserve nested slot params * fix(app-router): preserve intercepted slot head * fix(app-router): scope parallel layout params * fix(app-router): preserve intercept layout parity * fix(app-router): preserve intercept ancestor params * fix(app-router): scope intercept source params * fix(app-router): preserve intercept source route params * test(app-router): stabilize parity validation |
||
|
|
656a381de9 |
fix(og): constrain inlined assets to trusted roots (#2172)
* fix(og): constrain inlined assets to trusted roots * fix(og): canonicalize asset package boundaries * test(og): cover canonical boundary intersections * fix(og): support file-symlinked dependency modules * docs(og): clarify logical asset boundary resolution * fix(og): pin symlinked modules to matching packages * fix(og): keep linked package boundaries narrow * fix(og): reject workspace-root asset boundaries * fix(og): name-pin external package boundaries * fix(og): validate linked package ownership * fix(og): track linked package ownership * fix(og): harden linked package provenance * fix(og): confine captured alias packages * fix(og): pin captured directory aliases * fix(og): require alias package ownership * fix(og): support single-segment aliases * perf(og): preserve alias lookup precedence * refactor(og): extract asset ownership policy * test(og): cover linked package fonts in e2e * fix(og): recognize resolved package aliases * fix(og): support regex package aliases * test(og): isolate linked font fixture * docs(test): clarify linked font coverage |
||
|
|
301418a3f1 |
perf(app-router): defer layout, template and boundary modules to first request (#2145)
* perf(app-router): defer layout, template and boundary modules to first request The generated RSC entry statically imported every route's layout, template, loading/error/not-found/forbidden/unauthorized boundary and parallel-slot module at the top level. On a cold isolate — and on the dev server's first request — that eagerly evaluated the whole app's segment tree, including routes never visited in that lifetime. Page and route-handler modules were already lazy; the surrounding segment modules were not. Emit those modules as lazy `() => import()` loaders paired with `null` placeholders in the manifest, and hydrate them onto the route's synchronous fields in `ensureAppRouteModulesLoaded` before any read (extended from page/handler to layouts, templates, boundaries and parallel-slot modules). Intercept layouts hydrate via the dedicated `loadAppInterceptLayouts` because they live on the match object, not the route. Every reader already runs behind `ensureAppRouteModulesLoaded` (handler dispatch and `buildPageElements`) or the intercept resolvers, so no synchronous module field is read before hydration. The shared intercept `interceptLayouts` arrays keep their `readonly` public types; the loader is their sole writer and fills slots through one boundary-local cast, so route metadata stays immutable to callers. Dev cold start is unchanged on page-heavy apps — pages were already lazy — but on a boundary-heavy app (80 independent sections, each with layout+template+error+loading+not-found) first-request module compile drops 704ms -> 534ms (~24%) and time-to-first-200 1917ms -> 1665ms. The saving scales with the number of unrelated segments deferred. * test(app-router): localize react max header font fixture * fix(app-router): dedupe lazy intercept module loads * fix(app-router): share intercept load state across matches * docs(app-router): explain root layout loader reuse --------- Co-authored-by: James <james@eli.cx> |
||
|
|
42f8700f15 |
fix(router): enhance navigation with as mask for URL handling (#2076)
* fix(router): enhance navigation with `as` mask for URL handling * fix(router): implement bracket-pattern interpolation for dynamic routes in Pages Router * fix(router): preserve masked state in beforePopState * fix(router): cover basepath navigation in ci * docs(router): clarify navigation state invariants * refactor(router): require complete history state * refactor(router): share dynamic href interpolation * test(router): cover localized masked navigation * fix(router): keep interpolation internals private --------- Co-authored-by: James <james@eli.cx> |
||
|
|
b2ea860284 |
fix(proxy): match Next.js file conventions (#2155)
* fix(proxy): match Next.js file conventions * test(proxy): keep convention fixture out of workspace * test(proxy): provide middleware path in handler fixture |
||
|
|
12f04d6d63 |
fix(app-router): block dangerous RSC redirect URLs (#2154)
* fix(app-router): block dangerous RSC redirect URLs * refactor(app-router): reuse blocked navigation reporter * style(app-router): format redirect guard import |
||
|
|
2f8b0ef78b |
fix(build): preserve lazy RSC framework chunks (#2074)
* fix(build): preserve lazy RSC framework chunks * test(image): wait for completed watch output * test(build): isolate RSC startup regression * fix(build): preserve lazy framework chunks on Vite 7 * test(build): cover transitive framework reachability * test(build): harden framework chunk regressions |
||
|
|
78bb36a656 | fix(app-router): preserve dynamic interception source routes (#2042) | ||
|
|
48f4df28cb | fix(pages-router): cancel deduped GSSP data waits (#2038) | ||
|
|
68414e68a5 |
fix(router): re-enter filesystem routes after rewrites (#2032)
* fix(router): re-enter static files after i18n rewrites * fix(router): re-enter filesystem routes for all rewrites |
||
|
|
b91d5d1396 |
fix(pages): interpolate same-segment rewrite params (#2030)
* fix(pages): interpolate same-segment rewrite params * test(pages): fix same-segment navigation CI |
||
|
|
c3dcdba1fe |
fix(pages): honor basePath in dev module loaders (#2033)
* fix(pages): honor basePath in dev module loaders * fix(pages): preserve basePath in ISR loaders * style(pages): format dev module loader changes * fix(pages): preserve dynamic route module paths * test(pages): run basePath dev coverage in CI |
||
|
|
9958567949 | fix(pages): route hybrid form data requests (#2029) | ||
|
|
866c71809e |
fix(pages): preserve rewritten URLs during navigation (#2028)
* fix(router): preserve rewritten pages query navigation * fix(router): align pages url object locale handling * fix(router): preserve raw pages url search * fix(router): escape fragments in raw pages search * fix(router): preserve localized rewritten pages urls * test(router): fix pages navigation CI failures * fix(router): preserve Next search fragment formatting |
||
|
|
8b5390504e |
fix(actions): return body-limit errors without closing responses (#2026)
* fix(actions): preserve overflow error responses * fix(actions): backpressure body overflow handling * fix(server): own Node request stream chunks * fix(server): preserve cloned request bodies |
||
|
|
5146b1f47b | fix(router): render slots beside grouped children (#2022) | ||
|
|
fc8e6f0e86 | fix(app-router): settle superseded link navigations (#2025) | ||
|
|
b67a8ddd1e |
fix(prerender): deduplicate generateStaticParams entries (#2031)
* fix(prerender): deduplicate generateStaticParams entries to prevent duplicate routes in prerendering * docs(prerender): simplify deduplication comment for generateStaticParams entries |
||
|
|
a3d2f92152 |
fix(router): honor hybrid pages route priority (#1997)
* fix(router): honor hybrid pages route priority * fix(router): share hybrid owner decision with client navigation PR #1997 fixed the server-side route ownership for direct document loads, but the same invariant broke for client-side soft navigations and the matching prefetch path: - navigateClientSide() always delegated to the App runtime's RSC fetch, even when the Pages route had higher priority. renderPagesFallback() short-circuits RSC requests with null, so the App catch-all won. - prefetchUrl() prefetched an RSC stream for any URL that matched an App route, again ignoring Pages ownership. - The Pages entry was loaded on every hybrid request, even when a static App route had already matched (Pages can never win in that case). Expose the Pages route manifest on the client via a new __VINEXT_PAGES_LINK_PREFETCH_ROUTES__ window global emitted by both the App and Pages browser entries. The link shim consults a shared resolveHybridClientRouteOwner helper that mirrors the server-side pagesRouteHasPriorityOverAppRoute comparison. When Pages owns the URL, the click handler issues a window.location navigation and the prefetch path returns early. Gate the renderPagesFallback call behind a static-App-route check in handleAppRscRequest: when a static App route matches, the bridge cannot win, so skip the eager Pages entry load. Centralise the comparison in a new resolveHybridRouteOwner helper so server and client reach the same answer for the same (URL, route pair). Adds the missing client-navigation coverage to the use-params e2e fixture (Link from /app/ to /pages-dir/foobar) and unit tests for the shared owner decision. * fix(router): mirror server hybrid owner decision on the client PR review flagged two split-brain bugs in the previous hybrid ownership fix: a hand-copied client comparator and a Link-only ownership gate that left programmatic App Router navigations on the wrong path. The hand-copied routePrecedence in hybrid-client-route-owner.ts omitted the static-prefix reduction that lives in routing/utils.ts#routePrecedence, and used a strict-less-than comparison that returned App for identical dynamic patterns. The server returns Pages for both cases (Pages providers sort ahead of App providers, and routePrecedence subtracts 50 per static prefix segment). The split produced a real ownership disagreement on overlapping patterns like /_sites/:slug* (Pages) vs /:slug* (App). Add a shared compareHybridRoutePatterns to routing/utils.ts as the single source of truth: the static/dynamic short-circuits plus a sortRoutes call (which carries the static-prefix reduction and the Pages-first equal-pattern tiebreak). The server pagesRouteHasPriorityOverAppRoute and the client resolveHybridClientRouteOwner both delegate to it, so they cannot diverge. Drop the hand-copied routePrecedence entirely. Wire the ownership check at the App navigation runtime boundary so useRouter().push, useRouter().replace, gesturePush, and form submits all get the same hard-nav contract as the Link click handler. Specifically: - navigateClientSide: after same-origin normalization, if Pages owns the URL, hard-navigate via window.location and return (matching the existing external-URL branch). - _appRouter.prefetch: short-circuit RSC URL construction for Pages-owned targets so we do not warm an unusable cache entry. Centralise the existing two inline hard-nav branches in navigateClientSide into a hardNavigateTo helper for clarity. Tests: - Direct unit tests for compareHybridRoutePatterns covering identical-dynamic tiebreak, static-prefix dynamic overlap, static-prefix catch-all overlap, and infix-static bonus. - Direct unit tests for resolveHybridClientRouteOwner mirroring the server assertions plus a basePath-stripping test. - e2e: useRouter().push('/pages-dir/foobar') from an App page resolves to the Pages document. - e2e: useRouter().prefetch('/pages-dir/foobar') issues zero RSC requests for the target URL. * docs(router): fix stale score in static-prefix catch-all test comment The hand-copied comparator note quoted the dynamic-segment scores (51 / 1000) for the optional-catch-all example (/_sites/:slug* vs /:slug*). The current optional-catch-all scoring actually produces 1951 vs 2000. The test assertion was correct; only the comment was stale. * test(use-params): fix direct-load single dynamic param to use /a instead of /a/b * fix(router): compare hybrid routes structurally * fix(router): reject hybrid route conflicts * fix(router): refresh hybrid route ownership * fix(router): preserve hybrid routing lifecycle * fix(router): recheck pages routes after rewrites * fix(router): preserve rewritten pages queries * fix(router): preserve rewritten route ownership * fix(router): resolve client rewrites sequentially * fix(router): apply rewrite phases sequentially * fix(router): preserve rewrite params and endpoints * fix(router): hand off endpoint navigations * fix(router): preserve rewrite fragment params * test(e2e): avoid hybrid fixture route conflicts * test(pages): await async config validation * test(hybrid): avoid duplicate page fixtures * chore(router): clarify hybrid priority semantics * refactor(router): remove duplicate app route matcher * docs(router): clarify client hybrid comparator --------- Co-authored-by: James <james@eli.cx> |
||
|
|
10b5086f4d |
fix(app-router): align router autoscroll with Next (#2004)
* fix(app-router): align router autoscroll with Next App Router navigation could diverge from Next.js router autoscroll semantics around hydration timing, loading-shell same-page search navigations, and routes whose first committed node is React-hoisted into head. That broke the upstream router-autoscroll deploy-suite because the client marked hydration too early, static loading-shell templates were not usable for navigation, and the fallback scroll path masked the old-handler hoisted-head behavior. The fix moves the Next-compatible hydration marker into a passive effect, wires production hydrateRoot error callbacks to match Next's implicit root-boundary and recoverable-error handling, lets authoritative loading-shell templates drive static route optimistic payloads, and skips fallback document scrolling when the committed document only exposes a React-hoisted head resource. The ported tests cover the upstream router-autoscroll behavior plus focused unit boundaries for prefetch mode, optimistic routing, head-resource detection, and hydrateRoot callbacks. * fix(app-router): hoisted-head scroll fallback is per-intent, not global head scan * test(app-router): add full-chain scroll intent integration test * fix(app-router): match legacy scroll focus parity * fix(app-router): preserve latest scroll navigation intent * fix(app-router): scope scroll and error recovery * fix(app-router): gate navigation failure recovery * fix(app-router): match navigation failure handling * fix(app-router): align scroll completion parity * fix(app-router): retain latest failure target * fix(app-router): preserve route error boundaries * fix(app-router): clear discarded failure targets * fix(app-router): align failure recovery boundaries * fix(app-router): disarm refused recovery targets * fix(app-router): preserve failure target ownership * fix(app-router): match boundary recovery parity --------- Co-authored-by: James <james@eli.cx> |
||
|
|
b7ef466742 |
fix(pages): preserve app props for GSSP requests (#1996)
* fix(pages): preserve app props for gssp requests
* docs(pages): update _next/data comment to reflect full props envelope
* fix(pages-router): preserve _app.getInitialProps during stale ISR regeneration
Stale ISR background regeneration called getStaticProps directly and rendered
HTML and __NEXT_DATA__ with only the page's pageProps, dropping app-level
props from _app.getInitialProps. The foreground path already loads
_app.getInitialProps first and merges its props, but the background
regeneration path skipped that step.
Extract a shared loadPagesAppInitialRenderProps helper from the production
foreground path and reuse it during background regeneration in both
pages-page-data.ts and dev-server.ts. This ensures regenerated pages carry the
same full props envelope as the original render.
Add a regression test in tests/pages-page-data.test.ts.
* fix(pages-router): skip _app.getInitialProps on ISR cache hits
The foreground render path ran _app.getInitialProps before consulting the
ISR cache, so fresh and stale cache hits executed userland App data code
even though the cached HTML was returned verbatim. That wasted work on the
hot path and ran side-effecting App code whose result the cached response
never reflected.
The eager App initial-props load happened up front, ahead of the HIT and
STALE short-circuits. Defer it until the request will actually render:
load it lazily inside the getServerSideProps / getStaticProps /
getInitialProps branches, after the cache short-circuits return.
Stale-while-revalidate already re-runs it inside the regeneration closure,
so regenerated HTML still carries app-level props. The dev SSR handler and
the production page-data resolver are updated in lockstep to preserve
dev/prod parity.
Tests cover the fresh-HIT skip and the stale foreground-skip vs
regeneration-run split at the page-data resolver boundary.
* refactor(pages-router): extract dev App getInitialProps loader into a tested helper
The dev SSR handler ran _app.getInitialProps inside an inline closure that
mutated captured pageProps/renderProps and read res directly, so its lazy
load-only-on-render behavior could only be covered by a heavyweight
integration test that vi.doMock'd the isr-cache and file-matcher modules —
a mock cascade with little signal.
Move the decision into loadDevAppInitialProps in pages-get-initial-props.ts.
It takes the App component, req/res, and an injected AppTree builder (keeping
React out of the module) and returns a discriminated result —
skip / response-sent / render — that the caller applies. The dev-server
closure is now thin glue: build the React tree, delegate, apply the result.
Adds focused unit tests that drive the helper through its real boundary
(genuine getInitialProps functions, plain req/res inputs) — no module mocks:
the skip path, App/page prop merging, pageProps normalization, the
response-sent short-circuit, and the router/ctx wiring passed to userland.
* fix(pages): preserve app props across data requests
* fix(pages): preserve fallback cache expiry
* fix(pages): preserve dev app context during ISR
* fix(pages): hydrate middleware rewrite fallbacks
* fix(pages): retarget middleware data rewrites
* fix(pages): preserve rewrite query on navigation
* fix(pages): finalize rewrite navigation state
* fix(pages): resolve rewrite data targets consistently
* fix(pages): await static props during regeneration
* fix(pages): preserve original query in resolved url
* Revert "fix(pages): await static props during regeneration"
This reverts commit
|
||
|
|
930e03c449 |
fix(ppr): gate fallback shells until request-time resume is supported (#1716)
* fix(app-router): prerender cacheComponents root-param fallback shells PR 1 of 4: core model + build integration. Model: app-ppr-fallback-shell, pregenerated-concrete-paths, prerender-manifest Build: prerender/run-prerender fallback shell artifact generation Tests: createAppPprFallbackShells, pregenerated-concrete-paths core * feat(ppr): add fallback shell payload identity * feat(ppr): safely serve fallback shell cache entries * refactor(ppr): extract fallback shell render pipeline and dedupe getViteMajorVersion * fix(ppr): restore safe-serving CI checks The safe-serving branch failed CI because a client-side server-only guard rejected valid 'use server' action modules, a fallback-shell dependency cast no longer matched its helper type, apps/web lost the Cloudflare cache adapter source path during typecheck, and Knip could not resolve the documented Cloudflare cache adapter subpaths. The server-only guard now reads the directive prologue and skips top-level 'use server' modules, fallback-shell regeneration passes an explicit typed dependency object, apps/web gets the workspace Cloudflare cache path, the Cloudflare package exports explicit adapter subpaths, Knip uses a relative source import in the dispatch unit test, and the readiness test awaits the actual cache-ready promise. * fix(ppr): guard fallback shell reads for queries * fix(ppr): normalize pregenerated concrete paths * fix(ppr): address review comments on fallback shell serving and react runtime check * fix(ci): restore knip ignores for unlisted binaries and prune dependencies * fix(ppr): restrict bracket check in fallback detection to legacy manifests * fix(ppr): prefer stable ESM react-dom/static.edge before CJS fallback The loadStaticPrerender dev-mode path hard-codes react-dom's internal CJS file layout (cjs/react-dom-server.edge.development.js). A React upgrade could silently break fallback-shell rendering in dev without affecting production because the function never tried the stable ESM entry in development. Change the order so that react-dom/static.edge is attempted first in all environments. Only fall back to the CJS path when the ESM entry does not export prerender() and we are in a development runtime. This lets future React dev builds that expose prerender() from the ESM entry work automatically, and it surfaces the CJS-path failure only when the stable path is genuinely unavailable. Also handle the CJS default-export interop shape that Node.js ESM produces when dynamically importing a CommonJS module. * refactor(ppr): address four review cleanup items 1. Remove redundant double-normalization in seed-cache.ts addPregeneratedConcretePath already normalizes internally, so the call-site normalizePregeneratedPathname() was redundant. Dropping it gives a single source of truth for pathname normalization. 2. Lazily construct FallbackShellRenderDeps in tryServePprFallbackShell Extract the large FallbackShellRenderDeps object into a top-level buildFallbackShellRenderDeps helper. The closure in the probe loop now just delegates, so the heavy object is only created when a STALE entry actually triggers regeneration, not on every HIT probe. 3. Add dev-only warning for legacy bracket heuristic When isFallbackShellArtifactPath falls back to the bracket substring scan (route.fallback === undefined), emit a console.warn in dev so developers know a legacy manifest is being used and concrete URLs with literal brackets may be misclassified. 4. Update pregenerated-concrete-paths.test.ts to colon notation The production manifest writer uses colon notation (/:slug) for the route field, but the test fixtures used bracket notation (/[slug]). Update the fixtures so the test format matches production, exercising the real key format that the safe-serving guard depends on. * test(ppr): compact fallback shell dispatch fixtures * refactor(ppr): name fallback shell render phases * refactor(app-router): deduplicate PPR fallback shell and app page cache render paths * fix(ppr): avoid serving incomplete fallback shells * chore: drop redundant cloudflare cache aliases * fix(ppr): track fallback navigation hooks * test(ppr): render navigation hook probes * test(ppr): type navigation hook probes * refactor(ppr): remove unused shell regeneration path * refactor(ppr): remove unused shell result type * fix(ppr): safely gate fallback shell prerendering * fix(ppr): keep fallback shell gate internal * fix(ppr): mark generated dynamic fallback shells * test(ppr): align fallback fixture and search params --------- Co-authored-by: James <james@eli.cx> |
||
|
|
4baf85ad5e |
fix(router): restore pages scroll traversal state (#1999)
* fix(router): restore pages scroll traversal state * fix(router): preserve pages component identity across HTML fallback Pages Router HTML fallback could import the module URL from fetched __NEXT_DATA__ even when the generated page loader already existed. In Vite chunk graphs that can evaluate a duplicate page or router module, same-route param navigations remounted the page and lost listener state before routeChangeComplete. Store the router wrapper component types on the shared browser runtime singleton, prefer registered page loaders on the HTML fallback path, and resolve navigation at the root commit boundary that Next.js awaits instead of waiting for passive effects. Non-DOM document mocks still resolve immediately so unit harnesses do not masquerade as real browser commits. Validated with the upstream reload-scroll-backforward-restoration deploy suite and the local router-events and scroll-restoration regressions. * test(router): simulate pages router commits in shims tests * fix(router): move Pages Router readiness into shared runtime state Move the pagesRouterReady bit into PagesRouterRuntimeState so duplicated next/router module instances (client entry + page chunks) observe the same readiness value. The initial value is still computed from shouldDeferInitialPagesRouterReady() once per document. Also tighten the scroll-restoration e2e fixture so routeChangeComplete assertions cannot pass from stale page state, and add a regression test that simulates two evaluated router instances on a deferred-ready page. * fix(router): early-exit scroll restoration when target unreachable, document useRouter fallback Addresses ask-bonk review feedback on PR #1999: - restorePagesRouterScrollPosition now breaks early when window.scrollY stops changing across frames, avoiding the full 60-frame budget when the saved scroll position exceeds the rendered page height. - useRouter() fallback path now has an explanatory comment documenting the non-reactive singleton return is intentional for split-chunk compat. * fix(router): harden scroll restoration settling --------- Co-authored-by: James <james@eli.cx> |
||
|
|
3253aafd77 |
fix(pages-router): pass rewrite URL to edge API requests (#1998)
* fix(pages-router): pass rewrite URL to edge API requests * fix(pages-router): preserve edge API request URL parity * fix(pages-router): configure edge API NextRequest * fix(pages-router): preserve edge API locale config * fix(pages-router): preserve edge request metadata * fix(pages-router): align edge API URL formatting * fix(pages-router): match API locale casing * fix(pages-router): match domain locales by locale * docs(pages-router): clarify edge API basePath parity * test(deploy): expect metadata-preserving URL clone * docs(pages-router): note basePath rewrite parity --------- Co-authored-by: James <james@eli.cx> |
||
|
|
70ee909adf |
fix(app-router): support extensionless variable imports (#1958)
* fix(app-router): support extensionless variable imports * fix(build): harden extensionless dynamic imports * fix(build): align variable import resolution * fix(build): cover dynamic import contexts * fix(build): scope variable import contexts * fix(build): avoid comment parser backtracking |
||
|
|
075919357e |
fix(router): hard navigate Pages links to App routes (#1960)
* fix(router): hard navigate pages links to app routes * fix(router): normalize localized app route detection |
||
|
|
e018a08708 |
fix(pages-router): restore scroll across reload history traversal (#1905)
* fix(pages-router): restore scroll across reload history traversal
Pages Router pages with experimental.scrollRestoration currently expose window.next.router before the popstate runtime is installed and only keep Vinext scroll coordinates in the in-memory history state. After a reload, a back or forward traversal can run before the runtime is ready and has no durable scroll snapshot for the target history entry.
The violated invariant is that the Pages Router owns browser scroll restoration whenever the Next.js scroll restoration flag is enabled, with one stable history key per entry and routeChangeComplete emitted after the new page commits.
Resolve the Next.js config flag into process.env.__NEXT_SCROLL_RESTORATION, install the Pages Router runtime during next/router evaluation, persist Next-style sessionStorage scroll snapshots per history key, and render navigations through a stable commit boundary before emitting routeChangeComplete.
Regression coverage ports Next.js reload-scroll-backforward-restoration and verifies manual restoration plus exact scroll positions across reload, back, and forward traversal.
* fix(pages-router): sequence routeChangeComplete after scroll restoration and settle on render failures
* fix(router): guard against missing window.history in stampInitialHistoryState
* fix(router): defer hash scroll until after routeChangeComplete
Move hash scrolling out of renderPagesRouterElement's commit callback
and into the post-routeChangeComplete path, matching Next.js ordering:
x/y restoration happens during render commit, hash scrolling happens
after the completion event.
Also fix E2E test expectRouteChangeComplete to use one-shot event
listeners instead of checking for stale text, and add listener cleanup
to the fixture to prevent accumulation.
* fix(test): remove post-push deadlock and throw on missing events in E2E helper
expectRouteChangeComplete() now throws if window.next.router.events is
absent instead of silently resolving. Remove the redundant
expectRouteChangeComplete call after pushWithPagesRouter in the third
test — the awaited router.push already resolves after routeChangeComplete,
so registering a listener after it would deadlock.
* fix(test): replace as any casts with type-safe window.next narrowing in E2E scroll tests
* fix(router): narrow renderPagesRouterElement scroll param, drop dead hash branch
* fix(router): reset x/y scroll on hash routes before routeChangeComplete and cancel pending render on supersede
- Pass {x:0, y:0} through commit boundary for all scrollable
navigations, not just hash-less ones (hash scroll deferred to after
routeChangeComplete as before).
- Cancel pending render-commit Promise when a newer navigation starts,
preventing hang when the first root.render() commit is dropped by
React re-reconciliation.
- Add regression tests for both edge cases.
* fix(router): use SSR-safe layout effect in commit boundary and pin shallow scroll ordering
Address bonk review on #1905:
- swap the commit boundary's raw useLayoutEffect for the SSR-safe
useNonWarningLayoutEffect pattern from shims/image.tsx so Pages Router
SSR renders no longer log React's useLayoutEffect warning
- document that the default-noop commit boundary intentionally also wraps
SSR and initial hydration
- document the intentional shallow-scroll-before-routeChangeComplete
ordering and add a unit test pinning it
* fix(router): keep history-state scroll restore on popstate when scrollRestoration flag is off
Without experimental.scrollRestoration, popstate must still honour the
__vinext_scrollX/Y saved into history state on push, since a soft popstate
re-renders content the browser's native restoration can't position. Also
document why the popstate-time sessionStorage snapshot depends on
history.scrollRestoration = "manual".
* fix(router): guard render-commit canceller against late commits from superseded trees
* fix(router): defer current-history-key commit until beforePopState allows navigation
When beforePopState cancels a popstate the app stays on the previous
history entry, but _currentHistoryKey had already been updated to the
target key, skewing subsequent scroll-position bookkeeping. Move the
key commit below the beforePopState early-return, matching the
deliberate deferral of _lastPathnameAndSearch.
* chore(router): note intentional eager scroll snapshot; fix fixture link increment
- Document that the outgoing-entry scroll snapshot is deliberately written
before the beforePopState gate: on cancellation the app stays on
currentKey, so the saved value remains that entry's live scroll.
- Coerce id to a number in the pages-scroll-restoration fixture Link so
the href increments numerically instead of string-concatenating.
* fix(router): keep manual scroll restoration opt-in Pages-Router-only
Gate installManualScrollRestoration on the document not being an App
Router one (window.next.appDir === true): with the now-global
__NEXT_SCROLL_RESTORATION define, an App Router app that value-imports
next/router for compat would otherwise have its native browser scroll
restoration disabled at module eval. Upstream only flips
history.scrollRestoration inside the Pages Router constructor, which
never runs on App Router documents.
Also document the hash-only popstate path discarding the per-entry
__next_scroll_<key> snapshot as intentional upstream parity (change()'s
onlyAHashChange branch passes null to this.set and only scrollToHash
runs), and add unit coverage for both behaviors.
* ci: run pages-scroll-restoration E2E project in the CI matrix
Add the missing E2E matrix entry so the ported Next.js regression spec
(reload-scroll-backforward-restoration.spec.ts) actually executes in CI,
matching the shape of the sibling single-shard Pages Router entries.
Also document why the soft-popstate scroll fallback differs between
manual restoration on ({ x: 0, y: 0 }) and off (null -> native auto
restoration handles unstamped entries), per review.
* refactor(router): fold App Router gate into manualScrollRestoration constant
The popstate handler's sessionStorage save/read branches gated on the
module-level constant, which lacked the window.next?.appDir check that
installManualScrollRestoration used, leaving those branches relying
solely on the __N state filter to stay inert on App Router documents.
Fold the appDir check into the constant so every consumer is
Pages-Router-only by the same gate; the App Router bootstrap stamps
window.next.appDir before any client module evaluates, so the
module-eval-time check is reliable.
* test(pages-scroll-restoration): default getServerSideProps id to string "0"
Route params always arrive as strings, so the numeric 0 fallback was
inconsistent with the string values the component relies on
(Number(id) + 1 href, id === "error" check). Default to "0" for type
consistency.
* test(pages-scroll-restoration): align fixture deps and vinext import with pages-basic
Import vinext via the workspace package name instead of reaching into
dist by relative path, and declare the same react/react-dom/vinext
dependencies as the other fixtures so the fixture is self-describing
and the pnpm-lock.yaml importer entry is populated.
* fix(router): snapshot outgoing scroll on hash-only pushes
Upstream Next.js snapshots the outgoing entry's scroll into
__next_scroll_<key> inside Router.push() itself, before change()'s
onlyAHashChange short-circuit, so hash-only pushes still capture the
departed entry's position. Our hash-only push branch returned before
saveScrollPosition() ran, so a later back-popstate to that entry
restored {x: 0, y: 0} instead of the position the user had reached.
Save the outgoing scroll before updateHistory mints the new key,
matching the full-navigation push path and upstream ordering.
* docs(router): clarify manual scroll-target fallback chain precedence
* test(shims): tag scrollRestoration App Router test as eval-order guard
* docs(router): note missing-snapshot fallback intent at forcedScroll default
---------
Co-authored-by: James <james@eli.cx>
|
||
|
|
fd1023321b |
fix(router): support experimental gesture push (#1909)
* fix(router): support experimental gesture push Vinext did not expose useRouter().experimental_gesturePush when Next.js apps enabled experimental.gestureTransition. Upstream gesture-transition fixtures call the method inside a held async transition, so the target route never became visible before the canonical router.push completed. The fix resolves the config flag into the same __NEXT_GESTURE_TRANSITION define used by Next.js, exposes the optional router method only under that flag, and lets gesture navigation use the existing RSC navigation pipeline with an immediate approved visible commit. The ported e2e fixture preserves the upstream held-transition flow and target/static/dynamic assertions. * test(router): update navigate mock assertions to expect default transition commit mode * fix(router): address ask-bonk review comments on gesture push - track scheduled app router navigation in experimental_gesturePush for parity with push/replace, so a refresh() issued in the same task skips its redundant re-fetch - document the synchronous visible-commit mode's null pendingRouterState invariant in the browser navigation controller - add a unit test asserting experimental_gesturePush is only attached when the __NEXT_GESTURE_TRANSITION define is set * style(tests): fix formatting in shims.test.ts * docs(router): clarify gesture push normalization and flushSync commit paths * test(router): fix unbound-method lint in shims test and document gesture spec coverage * docs(router): clarify gesture define boolean form and spec held-state rationale * docs(test): note gestureTransition is fixture-wide for the gesture spec only * fix(router): no-op experimental_gesturePush before the navigation runtime is ready Upstream gesturePush early-returns when getCurrentAppRouterState() is null, so a gesture dispatched before hydration is a no-op. Previously vinext fell through navigateClientSide's non-runtime branch and performed a real history push. Guard on the runtime's navigate function (the same readiness check navigateClientSide uses) and document that the un-awaited void navigateClientSide(...) deliberately matches push/replace's fire-and-forget rejection shape. * docs(router): note gesturePush guard/dispatch runtime reads have no TOCTOU window * docs(test): document gesture fixture's double-navigation divergence from upstream --------- Co-authored-by: James <james@eli.cx> |
||
|
|
dea4472c2f |
fix(app-router): include parallel slot params in useParams (#1904)
* fix(app-router): include parallel slot params in useParams App Router navigation state previously used only the primary route match params when setting request navigation context, RSC param headers, render observations, and optimistic navigation payloads. That lost catch-all params from active parallel slot branches, so useParams() diverged from the slot page's params prop after SSR and client navigations. Next.js derives params from the selected router tree across parallel route branches. Resolve active slot params from route metadata, merge them into the navigation params boundary, and feed that selected param set through server render and optimistic navigation payloads. Port the upstream parallel-route-navigations fixture and cover the route graph, selected params helper, and optimistic payload boundary. * fix(app-router): preserve canonical pathname in navigation context after middleware rewrite * fix(app-router): resolve parallel slot params for static, revalidation, and action paths * chore: retrigger CI * fix(app-router): address ask-bonk review comments on parallel slot params - Remove unused parameter from - Add clarifying comment on routeParams fallback being deliberate no-op - Use (full matched params) instead of narrowed as base for slot param merge, preserving interception-specific extras on ISR regeneration path * fix(app-router): use full navigationParams in server-action re-render path The server-action re-render path was passing narrowed actionRerenderTarget.params to resolveAppPageNavigationParams, dropping interception-specific extras from source-route intercepts. Use actionRerenderTarget.navigationParams (full matched params) instead, matching the pattern used in the dispatch ISR path at app-page-dispatch.ts:602. Adds unit tests for the intercepted-slot and mixed intercepted + non-intercepted slot branches of resolveAppPageNavigationParams. * docs(router): clarify decode invariant and type cast rationale in parallel-slot params - app-optimistic-routing.ts: add comment explaining the pre-existing double-decode asymmetry between route params and slot params, noting TODO to converge decode passes - app-server-action-execution.ts: expand the comment on the interceptOpts cast to document why it's safe and what a follow-up would look like * fix(app-router): merge slot params for fresh intercepts --------- Co-authored-by: James <james@eli.cx> |
||
|
|
f5bee1a26f |
fix(app-router): preserve unstable_catchError boundaries (#1906)
* fix(app-router): preserve unstable_catchError boundaries
* test(e2e): add Next-parity test for shouldRerenderAppPageWithGlobalError (#1906)
Add a test proving that for uncaught post-shell RSC errors (with no
client boundary), the server preserves the original HTML stream and the
client renders global-error. This is the Next.js behavior that justifies
keeping shouldRerenderAppPageWithGlobalError unconditional at false.
The server cannot detect userland client boundaries like
unstable_catchError from the route manifest, so the only safe way to
get a global-error rerender is to make a targeted error-boundary test.
The test uses a loading.tsx boundary so the error is truly post-shell,
not caught by the pre-shell error-recovery catch block.
* fix(error): restore sentinel wrapper for falsy thrown values in unstable_catchError
Restores the _CatchErrorState sentinel wrapper ({ thrownValue: unknown }) so
falsy thrown values (null, undefined, 0, "") are routed to the fallback
instead of being treated as no error. Also restores ErrorInfo.error as unknown.
Adds back unit tests for falsy values, class-component lifecycle, and
unstable_retry behavior parity with the component-rendered fallback shape.
Refs: #1906
* fix(error): align ambient ErrorInfo.error type with runtime implementation
* refactor(app-router): remove shouldRerenderAppPageWithGlobalError dead code
The function always returned false and its _options parameter and
ShouldRerenderAppPageWithGlobalErrorOptions type were vestigial.
The call site at app-page-render.ts:892-904 was therefore never taken.
Remove the function, type, import, call site, and the now-redundant
unit test, leaving the shell-level recovery path in
renderAppPageHtmlStreamWithRecovery as the sole error recovery seam.
Addresses ask-bonk review comment on #1906.
* fix(test): restore typed globalAny view and document Fallback as any intent
Addresses two non-blocking review findings from ask-bonk on PR #1906:
- Restored the typed `globalThis as unknown as { window?: unknown }`
pattern for window stubs in the unstable_catchError test section,
replacing the `(globalThis as any).window` pattern that was a type-safety
regression from the prior rewrite.
- Added an inline comment explaining why `Fallback as any` is necessary
in the class-component lifecycle test: the test probes the inner
_CatchError class directly, where the fallback function is used as a
React component type (receiving a single `{ props, errorInfo }` object)
rather than the public API signature `(props, errorInfo)`.
* chore: trigger CI
* fix(test): rename forwardedProps to props in unstable-catchError test
Addresses ask-bonk review finding on PR #1906: the test was seeding
the inner _CatchError class with the stale key name forwardedProps,
but the production wrapper now passes props (and isPagesRouter).
Since this test only exercises the App Router unstable_retry path
(which reads neither key), behavior was unaffected, but renaming
aligns the test with the current boundary shape for future readers.
* refactor(server): remove dead routeHasLocalBoundary field
Removing shouldRerenderAppPageWithGlobalError (commit
|
||
|
|
23dd15f763 |
fix(prod-server): stop double-evaluating the server bundle when chunks import the entry back (#1924)
* fix(prod-server): stop double-evaluating the server bundle when chunks import the entry back The production server imported the built server entry with a ?t=<mtime> query string. Node keys its ESM cache on the full URL including the query, so any code-split chunk that resolves the same entry file by its plain URL evaluated the entire server bundle a second time. Default Vite builds on both supported majors (Rollup on Vite 7, Rolldown on Vite 8) hoist modules shared between the entry's static graph and lazy route chunks into the entry chunk, and the chunks import them back as ../../index.js; realpath- canonicalized chunk ids also reach the entry via its real path when the server was started through a symlinked path. Module-level singletons then diverged between the two copies: boot-time initialization ran on the instance the server imported while route handlers read the never- initialized duplicate. Import the entry by its bare canonical (realpathed) file:// URL on first use so all importers converge on one module instance, and only append the ?t= cache-buster when the same path is imported again with a different mtime — the same-process rebuild scenario the query existed for. * docs(prod-server): clarify rebuild-branch tradeoff and probe constraints (#1924) - note in resolveServerEntryImportUrl's JSDoc that the ?t= rebuild branch trades back the single-instance guarantee (bare chunk back-imports keep resolving to the first build); freshness + single instance only hold together on the first import of a path - clarify the realpath try/catch only covers realpathSync.native failures, not missing entry paths (those still throw at statSync, as before) - document that the prod-singleton-state fixture probe is only valid on a freshly built output path |
||
|
|
d2ffc83670 |
fix(config): define cache components flag as boolean (#1903)
* fix(config): define cache components flag as boolean Disabled cache components currently compile to a truthy "false" string. That diverges from Next.js and makes bundled user code that checks !!process.env.__NEXT_CACHE_COMPONENTS select the cache-components path even when next.config rewrites select the legacy destination. Mirror Next.js by defining process.env.__NEXT_CACHE_COMPONENTS as a boolean expression, and keep the local slot shim tolerant of both boolean defines and string env values. Add a faithful revalidatePath-with-rewrites production regression that proves rewritten static and dynamic routes invalidate after revalidating the resolved destination path. * test(prod-server): cover cacheComponents enabled revalidatePath with rewrites * test(prod-server): skip dist/ copy in cacheComponents fs.cpSync filter Addresses review feedback: fs.cpSync(APP_FIXTURE_DIR, fixtureRoot) copies the earlier-built dist/ directory into the temp fixture before it is immediately deleted. Adding a filter skips the unnecessary copy. |
||
|
|
835e6b9943 |
fix(app-router): suppress redirect console errors in production onCaughtError (root-layout-redirect) (#1878)
* fix(app-router): suppress redirect console errors in production onCaughtError (#1830) When redirect() is called from a 'use client' root layout (or any client component), React catches the NEXT_REDIRECT sentinel via RedirectErrorBoundary and then calls the hydrateRoot onCaughtError callback. In dev mode vinext already filtered these via devOnCaughtError, but the production path had no onCaughtError at all — React's built-in fallback calls console.error, producing a spurious browser console error that fails the Next.js compat test assertion `expect(foundErrors).toBe(false)`. Fix: add prodOnCaughtError to app-browser-error.ts that silently drops navigation signal errors (NEXT_REDIRECT, NEXT_NOT_FOUND, NEXT_HTTP_ERROR_FALLBACK) and logs all other caught errors, preserving React's default behavior. Add fixture tests/fixtures/root-layout-redirect/ (dedicated 'use client' root layout with redirect()) and Playwright project root-layout-redirect (port 4184) with spec porting the Next.js root-layout-redirect > should work using browser. Part of #1830 * fix: include result/ page in root-layout-redirect fixture * fix: add lockfile entry for root-layout-redirect fixture, use prod server, import isNavigationSignalError Three fixes to unblock catastrophically-failing CI: 1. pnpm-lock.yaml: Add missing importer entry for tests/fixtures/root-layout-redirect. The fixture was added without updating the lockfile, causing ERR_PNPM_OUTDATED_LOCKFILE on every CI job during `vp install`. 2. playwright.config.ts: Switch the root-layout-redirect server from `npx vp dev` (dev mode) to `vinext build + vinext start` (production mode). The fix under test — prodOnCaughtError — is only active in production builds; running in dev mode exercised the pre-existing devOnCaughtError path and never touched the new code. 3. app-browser-error.ts: Replace the inline isNavigationSignalDigest helper with an import of isNavigationSignalError from utils/navigation-signal.ts. The module is dependency-free and already in the production browser bundle; inlining it reintroduced the drift risk that navigation-signal.ts was created to prevent. Addresses ask-bonk findings: blocking (test ran in dev mode) and maintainability (inline classifier duplication). * test: add prodOnCaughtError unit tests, run root-layout-redirect e2e in CI, scope gitignore negation |
||
|
|
9194cb55c6 |
fix(pages-router): hard-navigate to App Router destinations from Pages Router links (#1879)
* fix(pages-router): hard-navigate to App Router destinations from Pages Router links
When a Pages Router <Link> targets an App Router route (detected via the
prefetch manifest and recorded as `components[path] = { __appRouter: true }`),
`performNavigation` now checks for that marker and immediately calls
`window.location.assign/replace` instead of attempting a Pages Router SPA
fetch cycle that cannot succeed.
Mirrors Next.js router.ts:1448-1453. Fixes the deploy-suite test
`pages-to-app-routing > should work using browser`.
- packages/vinext/src/shims/router.ts: add __appRouter hard-nav guard
- tests/fixtures/app-basic/app/about/page.tsx: add id="app-page" to <h1>
- tests/fixtures/app-basic/pages/pages-to-app/[slug].tsx: new GSSP fixture page
- tests/e2e/app-router/pages-to-app-routing.spec.ts: new Playwright regression test
* fix(pages-router): replace no-explicit-any cast with in-operator type narrowing
`(getPagesRouterComponentsMap()[_appPath] as any)?.__appRouter` triggered
the `no-explicit-any` lint rule. Replace with a two-step lookup that uses
the `in` operator so TypeScript narrows the union type
`{ __appRouter: true } | Record<string, unknown>` without an `any` cast.
* fix(pages-router): normalize trailing slash in __appRouter marker key, rename locals, pin fast-path test
- Strip trailing slash on both the write side (markAppRouteDetectedOnPrefetch)
and the read side (performNavigation) so the marker key agrees when
trailingSlash:true adds a slash to the prefetch URL but not to the nav URL
(or vice-versa via router.prefetch with a raw un-normalised path).
Mirrors Next.js: removeTrailingSlash(removeBasePath(pathname)) at router.ts:1442.
- Rename _appPath/_appPathEntry to appPath/appPathNorm/appPathEntry (they are
used, not ignored; the underscore prefix was misleading).
- Add two new unit assertions in pages-router-app-prefetch-detection.test.ts:
one pins that the marker key is always stored without trailing slash, and one
pins that performNavigation calls window.location.assign (the fast-path) when
the __appRouter marker is present, so a regression in the new code path cannot
be silently masked by the pre-existing fallback.
* test(pages-router): address review nits in pages-to-app navigation tests
- e2e: wait for App Router hydration after the hard navigation so the
test asserts the App Router landing explicitly instead of only
matching the new DOM
- unit: drop the unnecessary microtask tick before asserting
window.location.assign — the fast path has no await before it, so
asserting synchronously also pins that the hard navigation cannot
lose a race against the SPA path
|
||
|
|
08504a7745 |
fix(app-router): preload next/dynamic chunks with CSP nonce (#1594)
* fix(app-router): preload next/dynamic chunks with CSP nonce
Rendered next/dynamic boundaries produced no SSR preload links for their client chunks. That diverged from Next.js and broke nonce-based CSP tests that inspect preload tags before hydration.
The runtime assumed React.lazy alone was enough for dynamic imports. It never carried the dynamic module IDs from the source call into SSR, and client JS chunk URLs did not use Next.js's static/chunks path shape.
Add a focused dynamic metadata transform, resolve boundary files from the client build manifest, emit nonce-bearing preload hints during SSR, and keep client JS assets under _next/static/chunks. Ported regression coverage verifies the nonce and the emitted path shape.
* fix: honor assetPrefix for dynamic preload globals
* test: cover asset-prefixed dynamic preload nonces
App Router production only covered the default dynamic preload URL path. That left assetPrefix regressions at the manifest-to-runtime emission boundary unguarded.
Add a startProdServer regression that builds app-basic with assetPrefix "/cdn" and asserts nonce-bearing next/dynamic chunk preloads render under /cdn/_next/static/chunks/.
* test: isolate asset-prefixed prod server globals
The assetPrefix regression starts a second production server in the same Vitest process. startProdServer and the generated RSC runtime install global manifest and module-loader state, so leaving the temporary build's globals in place breaks later production requests in the shared server.
Snapshot and restore the relevant globals around the temporary /cdn server while keeping the integration assertion at the real runtime boundary.
* refactor: reuse shared record guard in dynamic preload metadata
The dynamic preload metadata transform carried a local object guard even though the codebase already has a shared record predicate. That made the AST helper drift from the existing non-array record invariant used elsewhere.
Reuse isUnknownRecord from utils/record and keep the plugin-specific AST alias local to the transform.
* fix: make dynamic preload metadata binding-aware
The next/dynamic metadata transform matched calls by imported identifier text. Shadowed parameters and block bindings with the same name were therefore mutated even when they no longer referred to the next/dynamic import.
Walk the parsed module with lexical binding state so shadowed names are excluded, and collect object-form imports only from loader/modules rather than every property on the options object.
* fix: model switch and class scopes in dynamic preload transform
The dynamic preload metadata transform still treated imported next/dynamic names as active inside switch case lexical scopes and named class expression bodies. That could inject loadableGenerated metadata into calls that resolve to local bindings rather than the import.
The scope walker now removes switch case-block bindings while traversing case tests and consequents, leaves the switch discriminant in the outer scope, and scopes named class declarations and expressions through their children. Regression tests cover switch case shadowing and named class expressions.
* fix: address PR review feedback for next/dynamic CSP nonce
- Drop `as="style"` from `<link rel="stylesheet">` — `as` is a
preload attribute per the HTML spec, semantically incorrect on
stylesheet links. Browsers ignore it but it produces bad HTML.
- Remove brittle `import(` string gate from the transform — it could
miss `import ("./x")` (whitespace between import and paren). The
Vite filter already gates on "next/dynamic" and the AST parse is the
real validation.
- Add comment explaining MagicString mutation safety in Promise.all.
- Add comment explaining AST end-exclusive assumption on closeParen.
- Add test for whitespace-separated dynamic import syntax.
* fix: update global.d.ts comment to mention asset prefix, fix formatting
* refactor: extract computeClientRuntimeMetadata shared helper
Extract the duplicated client-manifest runtime metadata computation
(lazy chunks, dynamic preloads, client entry) into a single shared
helper in utils/client-runtime-metadata.ts.
Both prod-server.ts (Pages Router Node.js production server) and
index.ts (Cloudflare closeBundle hook) now call the same helper,
eliminating the highest-risk seam in the PR where basePath/
assetPrefix handling produced bugs.
* test: add TSX-generic and object-form metadata regression tests
- Add post-type-strip generic call test verifying multi-line
dynamic() calls work after TS/JSX stripping by esbuild.
- Add object-form existing loadableGenerated preservation test
to guard against duplicate injection in the object overload.
* chore: remove unused ClientRuntimeMetadata type export
* revert: discard unrelated lockfile change from previous commit
* test: replace deploy test simulators with computeClientRuntimeMetadata
simulateCloseBundleAppRouter and simulateCloseBundlePagesRouter
no longer hand-roll lazy chunk and dynamic preload computation.
They call the shared computeClientRuntimeMetadata helper directly,
matching production wiring.
Also:
- Update helper doc comment to mention Node server startup
- Remove unused imports (manifestFileWithAssetPrefix,
manifestFileWithBase, computeDynamicImportPreloads,
dynamicImportPreloadsWithBase) from deploy.test.ts
* test: add absolute assetPrefix CSP nonce tests and clean up redundant globals
* fix(app-router): keep CSP nonce on next/dynamic preloads from Server Component call sites
DynamicPreloadChunks rendered in the environment of the dynamic() call site. For a Server Component call site it ran in the RSC environment, where the script-nonce React context is unavailable, so the emitted dynamic preload <link>s dropped the request nonce — a CSP violation under `script-src 'nonce-…' 'strict-dynamic'`. The existing tests missed this because every fixture called dynamic() from "use client" modules, which render in the SSR pass where the nonce is present.
Extract DynamicPreloadChunks into its own "use client" module (mirroring Next.js's PreloadChunks and vinext's next/script shim) so it always renders in the SSR pass where withScriptNonce() installs the provider, regardless of whether dynamic() is called from a Server or Client Component.
Add fixture regression tests: server-call-site JS and CSS preloads carry the nonce, an ssr:false boundary emits no server preload (Next.js loadable.tsx parity), and preloads are still emitted without a nonce when no CSP is set.
* test(app-router): add browser CSP-nonce e2e for Server Component next/dynamic call site
The existing csp.spec.ts browser test only exercises the client call-site page (/nextjs-compat/dynamic). Add a Playwright test for the Server Component call site (/nextjs-compat/dynamic/rsc-imports-client) asserting the lazily-loaded client widget hydrates under script-src 'nonce-…' 'strict-dynamic' with no CSP violations in the console.
* fix(app-router): correct next/dynamic preload key-spaces and harden the transform
Addresses review findings on the next/dynamic CSP-nonce feature.
- Lazy chunks must stay in the SSR-manifest key-space (basePath only); only dynamic preloads take the assetPrefix. Applying an absolute-URL assetPrefix to lazy chunks broke the Pages Router modulepreload-exclusion membership test and leaked lazy chunks into <link rel=modulepreload>.
- Resolve symlinks on both sides when computing a dynamic boundary's module ID (reusing relativeWithinRoot/tryRealpathSync), so the key matches the client manifest under pnpm/Cloudflare symlinked layouts instead of silently dropping the preload.
- Throw on dynamic() calls with >2 args (Next.js react-loadable parity).
- Insert the generated options arg right after the first arg, preserving comments/whitespace (the previous substring-comma scan ate commas inside comments).
- Recurse into _next/static/chunks/ in the on-disk client-entry fallback (client JS now lands under chunks/).
- Add typeof-window guard to DynamicPreloadChunks and document the deliberate as="style" omission for stylesheet links.
- Extract buildRuntimeGlobalsScript so the Cloudflare closeBundle injection shares one source of truth with the deploy tests (which now mirror includeClientEntry + the mixed app+pages branch).
Tests: lazy/dynamic key-space split incl. absolute-assetPrefix leak regression and a realistic already-prefixed manifest; mixed app+pages client-entry injection; symlinked module-id resolution; nested dynamic() calls; >2-arg throw; comment-safe insertion; ssr:false positive content assertion; honest dev-mode E2E naming (preload-nonce coverage lives in the prod-server Vitest suite).
* fix(app-router): fix parenthesized next/dynamic loader corruption + harden re-review findings
Follow-up to the re-review of
|
||
|
|
de33c3e5a0 |
fix(build): inline ../-relative font assets in OG routes (#1866)
* fix(build): inline ../-relative font assets in OG routes
The vinext:og-inline-fetch-assets plugin only matched paths starting
with "./" (e.g. "./font.ttf"), but Next.js test fixtures for OG custom
fonts use "../"-relative paths like "../../../assets/typewr__.ttf".
Without the inline, at runtime:
- Cloudflare Workers: import.meta.url is "worker" (not a URL), so
new URL("../../../assets/...", import.meta.url) throws TypeError.
- Node.js: fetch() does not support file:// URLs, so the inlined
file:// URL produced by the import-meta-url plugin fails.
Fix: relax the regex from `\.\/[^"']+` to `\.[^"']+` so any
dot-relative path (./x, ../x, ../../x, etc.) is inlined as base64.
Apply the same fix to the readFileSync(fileURLToPath(...)) pattern.
Failing tests addressed:
- test/e2e/og-routes-custom-font: should render og with custom font
for app routes (edge runtime fetch + Node.js fs.promises.readFile)
- test/e2e/app-dir/metadata-font: should handle custom fonts in both
edge and nodejs runtime
* fix(build): inline OG font assets when a formatter adds a trailing comma
The vinext:og-inline-fetch-assets regex only matched the single-line,
comma-less form of `fetch(new URL(...)).then((res) => res.arrayBuffer())`.
When a formatter (Prettier `trailingComma: "all"`, oxfmt) wraps the call
across lines it appends a trailing comma:
.then((res) =>
res.arrayBuffer(),
)
which no longer matched, so the font was left as a runtime fetch. On
Cloudflare Workers `import.meta.url` is "worker", so
`new URL("../...", "worker")` throws "TypeError: Invalid URL" and the OG
route returns 500 — even though the ../-relative path itself was already
supported. Relax both `.then(...)` alternatives with an optional `,?`.
Add real e2e coverage (`/api/og-custom-font`) mirroring Next.js'
og-routes-custom-font fixture: an edge OG route that loads
assets/noto-sans.ttf via
`fetch(new URL("../../../assets/noto-sans.ttf", import.meta.url))`. It
runs in tests/e2e/og-image.spec.ts across the app-router (Node dev),
cloudflare-dev and cloudflare-workers (workerd) projects, with the route
and font added to both the app-basic fixture and the app-router-cloudflare
example. Also adds a unit test for the formatted/trailing-comma shape in
tests/og-inline.test.ts.
* fix(build): inline OG font assets with a semicolon-terminated block body
Follow-up from the /bigbonk review on PR #1866: the block-body alternative
of the og-inline-fetch-assets regex ended `…\.arrayBuffer\(\)\s*\}?\s*,?\s*\)`
with no allowance for a `;` before `}`, so formatter output such as
.then((res) => {
return res.arrayBuffer();
})
.then(function (res) { return res.arrayBuffer(); })
was left as a runtime fetch — which throws "Invalid URL" on Workers
(import.meta.url === "worker"). Add `;?` before the block-body `}` and unit
tests for the arrow- and function-expression block-body forms.
|
||
|
|
3a1e56748f |
fix(app-router): match Pages navigation params in hybrid builds (#1741)
* fix(app-router): emit pages client entry for hybrid builds * fix pages router navigation params readiness * fix(pages-router): track hashed fallback client entry * fix(pages-router): honor navigation readiness in hooks * fix(pages-router): derive server router.isReady from SSR readiness Address review feedback on #1741: - getRouterSnapshot() now reads navigationIsReady from the SSR context on the server instead of unconditionally returning true. For pre-ready Pages routes (auto-export dynamic / query-string / rewrite-capable builds) this keeps useRouter().isReady consistent between SSR and client hydration, avoiding a hydration mismatch for components that read it in JSX. Mirrors Next.js render.tsx's server readiness rule. - Drop the redundant guard branch in getClientParamsSnapshot. - Add a regression test asserting server useRouter().isReady reflects the SSR navigationIsReady context. * fix(pages-router): align param resolution and dev/prod readiness parity Address review feedback (ask-bonk + self-review) on #1741: - Align client Pages navigation params to query-first, matching the server snapshot and Next.js's adaptForPathParams (derives path params from router.query). __NEXT_DATA__.query is kept current on client navigation, so the two snapshots are now provably identical and avoid a latent post- hydration mismatch. - Populate window.__VINEXT_PAGE_PATTERNS__ in the dev server (own script tag) so the next/navigation compat hooks can resolve a dynamic pattern from a resolved path in dev exactly as in production. Closes a dev/prod parity gap (prod set this via the client entry; dev never did). - Drop the redundant pre-readiness setSSRContext in dev; the single post- module-load call now publishes navigationIsReady before rendering, matching the prod handler. - Document that installPagesClientAssetGlobals is intentionally inert for App-only builds, and that getReadonlyPagesSearchParams' string-keyed cache is safe to share across concurrent SSR requests (immutable wrappers). - Add a dev regression test asserting __VINEXT_PAGE_PATTERNS__ exposure. * fix(pages-router): prefer URL params for client snapshots * fix(app-router): gate pages asset globals * test(app-router): add production test for hybrid basePath + assetPrefix * fix(navigation): widen usePathname return type to string | null * refactor(pages-router): dedupe navigation readiness modeling and cache pattern scan Addresses code-review cleanup findings on the Pages Router navigation readiness work: - Extract buildPagesReadinessNextData() so the dev SSR handler and the production Pages page handler derive the gssp/gsp/gip/appGip/autoExport __NEXT_DATA__ readiness flags from one place, instead of two hand-kept copies that could drift and produce a dev/prod hydration mismatch. - Extract resolvePagesNavigationPathname() shared by the client and SSR branches of getPagesNavigationContext, so the pathname null-ness rule (fallback / pre-ready auto-export dynamic) cannot diverge between them. - Memoize resolvePagesRoutePatternForPath()'s pattern scan, which ran an uncached O(routes) loop on every useSyncExternalStore snapshot read. - In getServerSearchParamsSnapshot, serialize the source only when the object-identity fast path misses, avoiding a toString() on every read. * refactor(pages-router): consistent ISR readiness data + robustness cleanups Addresses further code-review findings: - Thread the readiness flags (gssp/gsp/gip/appGip/autoExport) through the ISR background-regeneration path: renderPagesIsrHtml now serializes the same __NEXT_DATA__ readiness state the initial render emits, so a regenerated, re-cached page hydrates with the identical initial router.isReady the server computed. Previously these flags were dropped on that path — benign today (ISR-eligible getStaticProps routes never trigger the flag-gated early-ready branches) but a latent dev/prod hydration-readiness drift. Adds a shared PagesNextDataExtras type so every render path stays in lockstep. - readSsrManifest: degrade a malformed/unparseable SSR manifest to "no SSR manifest" with a warning instead of throwing and aborting server startup, restoring the previous tolerant behavior. - pages-readiness: project PagesReadinessNextData from the canonical VinextNextData and accept PagesPageModule, instead of bespoke types that could drift from the __NEXT_DATA__ shape. - getReadonlyPagesSearchParams: drop the redundant string-keyed cache slot; the per-object WeakMap is authoritative and concurrency-safe on its own. - isPagesRouterReady: drop the redundant server-side window check. * fix(pages-router): restore string-keyed readonly search-params cache Reverts the cache simplification from the previous commit. The string-keyed slot is load-bearing, not redundant: across the Pages Router pre-ready → ready transition the navigation context swaps in a new URLSearchParams instance even when the query string is unchanged, so the per-object WeakMap alone returns a fresh ReadonlyURLSearchParams wrapper and useSearchParams() loses Object.is stability — re-firing a [searchParams] effect. Caught by the rewrite-driven pre-ready static page e2e (search-params-snapshots saw ['',''] not ['']). Keeps the improved comment explaining why both cache levels are needed. --------- Co-authored-by: James <james@eli.cx> |
||
|
|
9596b72495 |
fix(pages-router): pass revalidateReason "on-demand" to gsp/gssp (#1462) (#1856)
* fix(pages-router): pass revalidateReason "on-demand" to getStaticProps (#1462) Implements res.revalidate() for Pages Router API routes and wires the on-demand revalidation trigger through to getStaticProps/getServerSideProps context as revalidateReason: "on-demand". res.revalidate(urlPath) issues an internal HEAD request to the target path carrying the x-prerender-revalidate header (mirroring Next.js's api-resolver). The Pages render path (dev-server and prod pages-page-data) detects that header, bypasses the fresh/stale cache-hit short-circuits, and regenerates the entry synchronously with reason "on-demand". The build and stale reasons already worked; this finishes the on-demand case. Closes #1462 * fix(pages-router): authenticate on-demand revalidation with a secret (#1462) The on-demand `revalidateReason` detection treated `x-prerender-revalidate` as an unauthenticated presence check and sent the literal value "1". Any external client could send `x-prerender-revalidate: 1` to force synchronous regeneration of any ISR page, bypassing the fresh/stale cache short-circuits — a cache-stampede/DoS vector. Mirror Next.js's `checkIsOnDemandRevalidate`: a request is only treated as on-demand revalidation when the `x-prerender-revalidate` header value EQUALS a secret (the vinext analog of `previewModeId`). Introduce a per-process revalidate secret (`getRevalidateSecret`, 256-bit random hex via Web Crypto) that `res.revalidate()` attaches to its internal loopback request, and a constant-time `isOnDemandRevalidateRequest` equality check on the receiving dev-server and Pages page handler. Header presence alone is never honored. Also fix the test's HTML-reason extraction to avoid CodeQL's incomplete-sanitization / bad-HTML-regex findings, and add a negative test asserting forged `x-prerender-revalidate` values ("1", "", a guess) are rejected while the real secret is accepted. * refactor(pages-router): share res.revalidate() impl; support unstable_onlyGenerated (#1462) Address non-blocking bonk review findings: - Extract the two near-identical `res.revalidate()` implementations (`api-handler.ts`, `pages-node-compat.ts`) into a shared `performOnDemandRevalidate` helper so the secret wiring and success detection cannot drift between dev and prod. - Align success detection with Next.js's api-resolver: accept `x-nextjs-cache: REVALIDATED`, status 200, or a 404 when the caller passed `unstable_onlyGenerated`. Add `unstable_onlyGenerated` support (`x-prerender-revalidate-if-generated` header). * docs(pages-router): note x-nextjs-cache REVALIDATED branch is parity-only (#1462) * fix(pages-router): make on-demand revalidate secret build-time (shared across isolates) The on-demand ISR revalidation secret was a per-process random value. On Cloudflare Workers, res.revalidate()'s loopback fetch() can land on a different isolate than the sender, so a per-process secret mismatches across isolates and false-rejects legitimate revalidations. Mirror Next.js's previewModeId: generate one 256-bit secret at build time (in the vinext build CLI, once per build, shared across plugin instances like __VINEXT_SHARED_BUILD_ID) and bake it SERVER-ONLY into every server bundle via a Vite define (__VINEXT_REVALIDATE_SECRET). It is byte-for-byte identical in every isolate and never reaches the client bundle. getRevalidateSecret() reads the baked constant at runtime and falls back to a process-shared random secret in dev (single-process, no regression). Also persist revalidateSecret in vinext-server.json for diagnostics/tooling parity with prerenderSecret. Constant-time safeEqual validation and reject-arrays/empty/absent behavior are unchanged. Refs #1462 * test(pages-router): cover server-only revalidate secret; drop dead manifest reader Address ask-bonk review of the build-time revalidate secret: - Add tests pinning the security-critical invariant that the __VINEXT_REVALIDATE_SECRET define is baked into server environments (rsc/ssr) but NEVER into the client bundle (the client env returns null outright). A leak would ship the secret to every browser and re-open the cache-stampede vector the equality check prevents. Also harden the existing "no-ops when defineServer unset" test to explicitly clear the env var instead of relying on it happening to be unset. - Drop the unused readRevalidateSecret export and the orphaned revalidateSecret manifest field — the runtime authenticates against the baked define (both Workers isolates and the Node prod-server run the bundle), so the on-disk copy had no consumer (knip-flagged dead code). - Hoist the client early-return in vinext:compiler-define-server so the server-only guarantee is structural (removes the redundant name check), and reword the dev-fallback comments ("absent unless set during vinext build"). Refs #1462 |
||
|
|
c669930f56 |
fix(build): correct CSS ordering for global-not-found (#1549) (#1858)
* fix(build): correct CSS ordering for global-not-found (#1549) * test(build): assert global-not-found CSS ordering in production (#1549) Adds a production-build regression test for #1549: builds the global-not-found-css-order fixture, serves it via the Vite preview server, and asserts the route-miss 404 document serves global-not-found's own CSS (red wins) without leaking the root layout's stylesheet (green), while matched routes still serve the layout's CSS. Verified the test fails without the framework-chunk split in client-build-config.ts (the 404 leaks the layout's green) and passes with it. * chore: update lockfile for global-not-found-css-order test fixture (#1549) * refactor(build): migrate RSC framework chunk to codeSplitting; add unit tests Address bonk review on #1858 (issue #1549): - Replace the deprecated rolldown `advancedChunks` output config with the non-deprecated `codeSplitting` form for Vite 8+, mirroring the client build. Stops the per-prod-build deprecation warning. - Derive both RSC_FRAMEWORK_CHUNK_TEST (regex) and isRscFrameworkModule (predicate) from a single FRAMEWORK_PACKAGES list so they can't drift. - Add focused unit tests for createRscFrameworkChunkOutputConfig (both the Vite 7 manualChunks and Vite 8+ codeSplitting branches) and for the framework package matching (matches react/react-dom/scheduler/ react-server-dom-webpack, excludes react-icons / @react-aria/*). |
||
|
|
29a040dd21 |
fix(app-router): include route pattern in repeated-slash Link warning (#1554) (#1855)
* fix(app-router): include route pattern in repeated-slash Link warning (#1554) The repeated forward-slashes Link warning added in #1563 fired with the wrong "in page" segment: it used `window.location.pathname` (or `"/"` during SSR) instead of Next.js's route pattern (`router.pathname`, e.g. `/my/path/[name]`). The Next.js compat test `repeated-forward-slashes-error` asserts the *full* message including that route pattern, so it kept failing in the deploy suite despite #1563 — the `/Invalid href/` regex matched but the `toContain` of the exact message did not. Source the page segment from the current render's route pattern via a new `getCurrentRoutePathnameForWarning()` helper: the Pages Router SSR context's route pattern on the server, `window.location.pathname` on the client, falling back to `"/"`. Normal Link behaviour is unchanged. Add an SSR-level compat test (app + pages router) that renders a Link with a repeated-slash href and asserts the exact warning, plus the upstream dynamic-route fixture `pages/my/path/[name]`. Finishes #1554 (the warning wasn't firing with the right page name despite #1563). Closes #1554 * fix(app-router): read route pattern via side-effect-free accessor The previous commit imported `router.ts` into `link.tsx` to source the route pattern for the repeated-slash warning. But `router.ts` has a browser-only top-level side effect (`installWindowNext({ router })`), so pulling it into the Link shim — which is in the App Router client bundle too — clobbered `window.next.router` with the Pages Router instance, breaking App Router client navigation (useParams/prefetch/interception E2E timeouts). This is exactly why `navigation.ts` deliberately avoids importing `router.ts` and reads the Pages nav context through a `Symbol.for` handle. Mirror that pattern: add a side-effect-free `route-pattern-for-warning` module that reads the SSR route pattern through a `Symbol.for` accessor published by the server-only `router-state.ts`. The Link shim imports only this tiny module, so no Pages Router side effect leaks into the App Router bundle. Behaviour is unchanged: pages-router SSR reports `in page: '/my/path/[name]'`, client/App Router fall back to `window.location.pathname` then `"/"`. |
||
|
|
dd3fb05977 |
test(pages-router): cover javascript: URL blocking (#1576) (#1854)
* fix(pages-router): emit console.error for javascript: URL navigation (#1576) Finishes the Pages Router half of #1576 (the App Router warnings already land). Adds the four Pages Router scenarios from the upstream test/e2e/app-dir/javascript-urls suite — Link `href`, Link `as`, router.push, and router.replace — as e2e fixtures + a Playwright spec, plus unit coverage for the shared Link shim's dangerous-click handler. No production change is required: the Link shim already emits the canonical "Next.js has blocked a javascript: URL as a security precaution." console.error on click for both `href` and `as`, and the Pages Router router.push/replace synchronous guard from #1575 already does the same. This PR adds the missing Pages Router coverage that proves all four scenarios. Closes #1576 * test(pages-router): suppress dev error overlay before safe navigation The router.push/replace javascript: scenarios throw synchronously (the #1575 behaviour that surfaces the console.error), which trips vinext's dev error overlay. Its backdrop intercepted the follow-up safe-link click, timing out the test even though the security block already passed. Suppress the overlay via the existing disableDevErrorOverlay helper after asserting the block, matching how other pages-router/app-router specs handle intentional dev-time errors. * test(pages-router): cover javascript: URL blocking in production build The Pages Router javascript: URL guard (Link dangerous-click handler in shims/link.tsx + router.push/replace synchronous guard in shims/router.ts, both via shims/url-safety.ts) is already correct on main. The existing e2e spec, however, only runs against the dev server (vp dev), while the Next.js deploy suite exercises these scenarios against a production build where the dev error overlay is absent and the client bundle is minified. Add a pages-router-prod Playwright spec that re-runs all four upstream scenarios (Link href, Link as, router.push, router.replace) against vinext build + vinext start, so a prod-only regression is caught here. Refs #1576 |
||
|
|
ca690db954 |
test(actions): cover server action POST under node-runtime middleware (#1480) (#1847)
* test(actions): cover server action POST under node-runtime middleware (#1480) Adds regression coverage for cloudflare/vinext#1480: a server-action POST must still be intercepted and executed when a node-runtime middleware matches the action path and consumes the request body before falling through with NextResponse.next(). The app-basic fixture's middleware now matches `/nextjs-compat/action-node-mw` and reads the POST body (mirroring Next.js' `middleware-node.js`), with a bound form server action on that page. Two tests exercise the scenario: - tests/nextjs-compat/action-node-middleware.test.ts (dev inline-middleware path) - tests/app-router-production-server.test.ts (production RSC entry path, the layer the upstream deploy suite exercises) vinext already isolates the middleware's body branch from the downstream request (applyAppMiddleware / createNextRequest clone the body) and treats node-runtime middleware identically to edge middleware, so the action POST is dispatched correctly. These tests lock that behavior in across the dev and production request paths. Closes #1480 * style(test): apply formatter to action-node-mw prod test * test(actions): declare runtime:nodejs on fixture middleware for #1480 Address review: make the app-basic fixture middleware faithfully mirror Next.js' middleware-node.js by exporting config.runtime = 'nodejs'. vinext runs all middleware on Node regardless, so this is a behavioural no-op for the other matcher entries, but it makes the action-node-mw regression a true guard for the named node-runtime scenario from the issue title. * docs(test): point #1480 fixture comment at middleware-runtime config logic Address review nit: the config-reading logic (matcher-only, no runtime branch) lives in server/middleware-runtime.ts, so reference that file where a future runtime branch would land. |
||
|
|
49d753217b |
fix(app-router): propagate "use cache" tags to route-handler ISR entries (#1453) (#1848)
* fix(app-router): propagate "use cache" tags to route-handler ISR entries (#1453) * fix(app-router): bubble nested use-cache tags to parent on data cache HIT (#1453) |
||
|
|
600ba69b4d |
fix(routing): propagate middleware draft cookie to pages/api fallback (#1520) (#1845)
* fix(routing): propagate middleware draft cookie to pages/api fallback (#1520)
In a hybrid app (app/ + pages/api/*), requests with no matching App Router
route fall through to the Pages Router via renderPagesFallback. The bridge
forwarded middleware-mutated request headers and response headers, but dropped
the __prerender_bypass Set-Cookie emitted by draftMode().enable()/disable()
inside middleware — that cookie lives in a request-scoped side channel that
App Router route handlers and page renders read via getDraftModeCookieHeader(),
but the Pages fallback path never did.
Thread getDraftModeCookieHeader through the bridge and append the draft cookie
to the Pages Router fallback response, matching how App Router responses surface
it. Closes the draft-mode flow called out in #1520.
Closes #1520
* test(routing): cover draft-cookie fallback in app-pages-bridge unit test
Add getDraftModeCookieHeader to the renderPagesFallback dependency stub and
assert the draft cookie is appended (API + renderPage branches) and omitted
when middleware did not enable draft mode. Keeps the unit test in sync with
the new required dependency.
* docs(routing): document hybrid-dev draft-mode limitation in pages bridge
Address review feedback on #1845: clarify in app-pages-bridge that the
draft-cookie fallback covers production (inline RSC handler), while hybrid dev
runs middleware in a separate Vite Pages SSR runner where draftMode() inside
middleware is not yet permitted — a pre-existing limitation tracked separately
from #1520. The unit-test regression flagged by review was already fixed in
|
||
|
|
6958dfd0b4 |
fix(build): expand require.context into import.meta.glob map (#1501) (#1836)
* fix(build): expand require.context into import.meta.glob map (#1501) * style: format require-context fixture page * fix(build): strip g/y regex flags and default require.context recursive to true Address ask-bonk review on #1836: - Strip global/sticky flags when reconstructing the filter regexp; a stateful g/y RegExp.test() drops every other matching module. - Default the omitted recursive arg to true to match Webpack's useSubdirectories default (was silently false). - Add a g-flagged fixture/test case locking in the flag-stripping fix. - Clarify the misleading descent comment in collectRequireContextCalls. * test(require-context): cover ctx(key) retrieval and MODULE_NOT_FOUND Address ask-bonk follow-up: lock in the runtime contract beyond .keys(). - Assert ctx('./parent/file1.js').default resolves the module namespace. - Assert an unknown key throws with code 'MODULE_NOT_FOUND'. * docs(require-context): document extensionless no-regex parity caveat Address ask-bonk note: with no filter regexp, import.meta.glob only surfaces resolvable modules, so extensionless keys Webpack would include can be dropped. Documented as a low-risk divergence (upstream Next.js's extensionless test is disabled/Turbopack-pending). |