mirror of
https://github.com/cloudflare/vinext.git
synced 2026-09-14 19:04:59 +08:00
cf689f2e43
* fix(pages): run middleware before image endpoint * fix(pages): preserve image rewrite query
pages-router-complex
A deliberately convoluted Pages Router app. It exists as a compatibility
target for vinext: the patterns here are the kind that only surface in big,
old, enterprise pages-router codebases. It is set up for Cloudflare the way
vinext init scaffolds it (vinext + cloudflare Vite plugins, KV data cache,
Workers CDN cache, Images optimizer, wrangler.jsonc with the
vinext/server/fetch-handler worker entry).
It is expected to run correctly under real Next.js (pnpm dev:next).
Running it under vinext (pnpm dev) is the goal, not (yet) the guarantee —
the e2e suite in tests/e2e/pages-router-complex/ documents the target
behaviour and may fail under vinext today.
The patterns being exercised
pageExtensions: ["page.tsx", "page.ts"]— every routable file uses the suffix, non-page helpers live alongside pages (pages/shell-initial-props.ts), andmiddleware.page.ts/instrumentation.page.tscarry the suffix too.- App-shell
getInitialProps(disables automatic static optimisation) that fetches masthead/baseboard chrome through an in-memory TTL memoiser, skips the fetch entirely for embedded-shell (cookie-flagged) requests, bypasses the memo in draft mode, reads env-based settings, and emits a beacon when it re-runs client-side on navigations. - Extra named exports from special files:
_appre-exports a conditional top-level-awaitoutboundStubpromise;middleware.page.tsexports an unrelated async loader alongsidemiddleware+config. - Class-based
_documentwith its owngetInitialProps(palette derived fromctx.req.url,<html lang>from the zone),beforeInteractivescripts (external +dangerouslySetInnerHTMLbootstrap), raw inline<script>tags, and data attributes on<body>. - Middleware pipeline (regex
matcherform): CDN-prefix rewrite (/atlas/cdn/_next/*→/_next/*), a 403 for/_next/image, a JSONhardNavToresponse for raw/_next/datarequests (skipMiddlewareUrlNormalize), editor-draft redirect on a request header, draft-cookie scrubbing for API routes viaNextResponse.next({ request }), then a single-segment zone rewrite/redirect with anx-zoneresponse header. - A
[zone]route dimension hidden from public URLs for the home zone, feeding a real i18next/react-i18next runtime (one instance per tree, synchronous init for SSR, per-zone bundles with fallback) and a zone-awarenext/linkwrapper for all chrome links. - Catch-all + siblings:
gallery/[...facets]with static siblings that must win precedence (gallery/curated/first,gallery/directory/a-z), facet-dedupe redirects, character-scrub redirects, permanent deep-trail collapses, cacheable 404s, and per-wall surrogate TTL overrides. - A dynamic/static/dynamic route sandwich (
[collection]/item/[assetId]) whose page-data function branches into three templates off what the catalogue record says the asset is (withdrawn records and malformed ids 404 first). getServerSidePropswrapped in a metering HOF on every page, custom CDN cache headers (Surrogate-Control/Surrogate-Key), a conditional CSP response-header side effect, and one page with no data-fetching function at all (detail-tools/client-flags).- Server-snapshot data layer: gSSP runs ops through a server handle that
records a snapshot, the snapshot rides page props, and the browser handle
is seeded with it so hydration reads from memory (
useGraphOp). - Client-side routing machinery: shallow
router.pushwith the internal dynamic-route pattern aspathnameand the public URL asas;router.eventsdriving a transition overlay with a failsafe timeout; a router-agnostic reset hook onnext/compat/router+usePathname. - App Router hooks in Pages Router pages:
useSearchParamsseeding initial state andusePathname, combined with rawwindow.history.replaceStatequery updates that bypass the router. next/imagewith a custom loader infillmode + css-module class on the fault screens — the framework optimizer endpoint is never used, which is what makes the middleware's/_next/image403 safe.- API routes: draft-mode gateway with landing-path resolution, a
bearer-gated memo purge endpoint, a cookie-reflecting upstream relay proxy
in promise-chain style, a legacy path rewritten in
afterFiles, and a 204-with-cache-headers type-ahead shim. next.config.js(CJS) exporting the function form — an async function of(phase, context)built by a decorator composer — with a throwinggenerateBuildId, prod-onlyassetPrefix, env-conditionalfallbackrewrites, and a webpack hook (workspace alias + test-module replacement viaNormalModuleReplacementPlugin).instrumentation.page.tsgated onNEXT_RUNTIME === "nodejs", with its effect observable through/api/status.
Running
pnpm dev # vinext dev server (needs RELEASE_TAG for now, see below)
pnpm dev:next # real Next.js dev server (ground truth; --webpack, see below)
pnpm build # vinext build (RELEASE_TAG is required and set inline)
pnpm build:next # next build
# The behaviour suite (server starts under vinext automatically):
PLAYWRIGHT_PROJECT=pages-router-complex pnpm run test:e2e
Known findings
- Next.js 16.2.7 + Turbopack fails to compile: the global-CSS-in-_app
validation false-positives when
pageExtensionsrenames_appto_app.page.tsx. Thedev:next/build:nextscripts pass--webpack. - TypeScript 7 (native preview): Next's own tsconfig-paths support
degrades under it, so the
@atlas/*alias is wired into both bundlers explicitly (webpack hook + Viteresolve.alias); tsconfigpaths(without the removedbaseUrl) stays authoritative for the type checker. - vinext dev (Cloudflare plugin): 65/73 specs pass; the 8 known gaps are
marked
test.fixmeso the passing surface runs in CI. Known gaps:generateBuildIdis invoked at dev startup (Next.js only calls it at build time — the e2e server exportsRELEASE_TAGto compensate), shallow routingrouter.events(including a hydration knock-on that breaks page interactivity), thenext/imagecustom-loader/fillpath, thehistory.replaceState-beside-the-router hybrid, and the purge endpoint's unauthorized-POST response.
- The pinned workerd binary caps
compatibility_dateat 2026-04-08, sowrangler.jsoncpins 2026-04-01 rather than the scaffold's "today".
Layout
lib/— the app's internal platform libraries, consumed through tsconfig path aliases (@atlas/*) the way a monorepo app consumes shared packages: zones (zone routing + i18next runtime), edge-policy (CDN headers), memo (TTL result cache), beacon (metrics/RUM), graph-handle (data layer), chrome (masthead/baseboard), wiring + blocks (the provider pyramid and frame), trials, draft, and so on.helpers/,surfaces/— app-level page-data helpers and page templates.pages/— the route tree described above.