diff --git a/apps/web/content/blog/vinext-1-2.md b/apps/web/content/blog/vinext-1-2.md new file mode 100644 index 0000000000..6f8a613654 --- /dev/null +++ b/apps/web/content/blog/vinext-1-2.md @@ -0,0 +1,112 @@ +--- +title: "Vinext 1.2: middleware for public files, stricter caching, and broader ecosystem compatibility" +description: "Middleware runs for public/ files on Workers, cache keys that can't be shared between different requests, full-document Pages ISR regeneration, and next/dynamic parity." +date: "2026-10-14" +authors: + - name: James Anderson + url: https://github.com/james-elicx +tags: + - release + - Next.js + - Cloudflare Workers + - middleware + - caching + - next/dynamic +--- + +To upgrade: + +```sh +npm install vinext@1.2.0 @vinext/cloudflare@1.1.1 @vitejs/plugin-rsc@^0.5.37 +``` + +App Router projects now need `@vitejs/plugin-rsc` 0.5.37 or newer, and stop with an error on older versions. The reason is under [Server Actions](#server-actions). Projects created with `vinext init` have it as a dev dependency, so upgrade it alongside Vinext. + +Most of this release is fixes. Many came from checking Vinext's behaviour against Next.js 16, from the nightly Next.js deploy test suite, and from running next-intl's own tests against Vinext. + +## Middleware and public files on Workers + +In Next.js, middleware runs for every path its `matcher` matches, including files in `public/`. On Workers, Static Assets serves those files before the Worker runs, so middleware never saw them. + +`vinext init` now generates a `runWorkerFirst` setting that sends everything except the hashed `/_next/static` build output through the Worker: + +```ts +assets: { + htmlHandling: "none", + notFoundHandling: "none", + runWorkerFirst: ["/*", "!/_next/static/*", "!/*/_next/static/*"], +}, +``` + +Middleware then runs before `public/` files, and Vinext serves the file from the `ASSETS` binding with any request headers middleware changed. **Existing projects aren't changed.** If your middleware should cover public files, add these settings to your config. Keep both exclusions: `runWorkerFirst: true` also sends the build output to the Worker, which returns 404 for it. [Middleware and static files](/docs/deploying/cloudflare#middleware-and-static-files) has the details. + +The image optimizer follows the same rules now. `/_next/image` requests go through middleware, and a local image source is fetched through middleware as an anonymous request, as Next.js does. A public image that middleware blocks can no longer be read through `/_next/image?url=…`. With a `basePath`, `next/image` also emits `${basePath}/_next/image`, which fixes 404s for optimized images on Workers apps without an image optimizer. + +## Cache keys + +Several fixes are for edge cases where two different requests could get the same cache key, so one caller's cached result could be served to another: + +- `unstable_cache` keys include the callback source even when you pass `keyParts`, as in Next.js, and are now a SHA-256 digest of an unambiguous encoding. Tuples such as `["a:b", "c"]` and `["a", "b:c"]` no longer share an entry. +- An `unstable_cache` result is stored under the arguments its callback ran with, so changing a shared options object between calls no longer stores one user's data under another's key. +- A cached `fetch` copies its URL, headers, `cf` and body when it is called, then keys and sends that copy. Bodies of every type are keyed, and on Workers so is `cf.resolveOverride`. The [caching guide](/docs/guides/caching#how-cached-fetch-request-bodies-are-keyed) lists where this goes further than Next.js. +- Long ISR paths and other oversized keys are shortened with SHA-256 rather than a non-cryptographic hash. +- `x-api-key` counts as a credential, so responses to those requests are never stored in the shared Cloudflare caches. +- `revalidateTag()` in the default in-memory cache no longer forgets an invalidation after 10,000 other tags. + +## Pages Router ISR + +Stale Pages Router entries used to be regenerated by rendering only the page and splicing it into the cached HTML. `_document`, its `getInitialProps` and any CSS-in-JS styles stayed as they were on the first render. Regeneration now runs the full render, as Next.js does. Vinext also finds `
` with a placeholder that request data can't forge, instead of searching for marker text. + +In production, Next.js never calls `getStaticPaths()` for a request. Vinext has no required prerender step, so it still checks new paths at request time, but it no longer calls `getStaticPaths()` for cache hits, stale responses, draft mode, or when Response Store or Workers Cache regenerates an entry it already holds. Before this, a dynamic `getStaticProps` page ran its full path list on every request unless you used the static assets cache. + +A `getStaticProps` page whose `_app` or `_document` sends the response itself, for example an auth redirect with `res.writeHead(302)`, is now still rendered and stored, as in Next.js. + +## next/dynamic + +[@Mauladen](https://github.com/Mauladen) and [@AhmedElBanna80](https://github.com/AhmedElBanna80) reported several ways `next/dynamic` differed from Next.js: + +- In the App Router, `dynamic()` only adds a `` boundary when you pass `loading` or `ssr: false`. Otherwise the component suspends to the nearest boundary above it, so content no longer pops in after the shell. +- Components loaded from outside the Vite root, such as workspace packages in a monorepo, get their CSS and preload links during SSR instead of rendering unstyled until hydration. Packages in `transpilePackages` get them too. +- `useId()` values inside a dynamic component match between the server and the client. +- Text right before a dynamic component no longer gets an extra `` from its script preloads. + +## Server Actions + +`@vitejs/plugin-rsc` 0.5.37 ties the encrypted values an inline Server Action closes over to that action, as Next.js does. On older versions, a client could send one action's encrypted closure to another action on the same route. Vinext now requires 0.5.37. The same kind of check now applies to inline `"use cache"` functions. + +Errors from actions match Next.js too. An action called with `fetch` that throws rejects in the browser with a `digest`, and is reported to `instrumentation.onRequestError`. A form submitted without JavaScript whose action throws gets a plain 500, rather than the nearest `error.tsx` with a 200 status. + +Action modules are now evaluated outside the request that first loads them, so a `cookies()` or `headers()` call at module scope can't keep the first user's values for later requests in the same isolate. + +## next-intl + +next-intl's [App Router playground](https://github.com/amannn/next-intl/tree/main/examples/example-app-router-playground) and its [canonical App Router example](https://github.com/amannn/next-intl/tree/main/examples/example-app-router) now run in our CI against a Workers production build, using next-intl's own Playwright tests. The playground runs in seven configurations, covering localized pathnames, `basePath`, trailing slashes, domains and locale cookies. It found three bugs, all fixed in this release: a middleware rewrite that also carried `x-middleware-next` was ignored, `clientOutDir` didn't apply to the client build, and `next/image` didn't use the `basePath`. `vinext check` now reports next-intl as supported. + +## Module Federation + +The Module Federation team now publishes [`@module-federation/vinext`](https://github.com/module-federation/vinext), a wrapper around `@module-federation/vite` with defaults for Vinext. It shares React and React DOM as singletons and injects the host's federation startup into the client entry, since a Vinext app has no HTML entry to put it in. [@Nsttt](https://github.com/Nsttt) wrote the new [Module Federation guide](/docs/guides/module-federation) around it. + +Getting a federated host and remote working turned up two fixes: + +- Files a plugin writes outside `_next/static`, such as a host's `/remoteEntry.js`, returned 404 from `vinext start` for App Router apps and from Workers using the `runWorkerFirst` setting above, so the page never hydrated. Vinext now serves files from the client build before middleware runs, which matches Cloudflare's default of serving assets first. +- `@vitejs/plugin-rsc` 0.5.36 and 0.5.37 turn template-literal dynamic imports into glob imports during their scan build, and those fail to resolve. That broke federated builds. Vinext works around it on those two versions. + +A federated host and remote now also run in CI against a Workers build. + +## Other changes + +- `vinext init --name` sets the Cloudflare Worker name. [@MattieTK](https://github.com/MattieTK) added it so tools like `@cloudflare/autoconfig` can pass the name the user picked. +- [@jeffreywescott](https://github.com/jeffreywescott) fixed RSC streams carrying large Flight rows. On a page with ~1.4 MB of client component props, the Worker spent about a second of CPU per request rescanning the row. +- `vinext start` and `vite dev` log uncaught errors and unhandled rejections instead of exiting, as `next start` does. [@jgeurts](https://github.com/jgeurts) stopped client disconnects from being reported to `onRequestError` as render failures. +- `` turns off smooth scrolling during route changes, as in Next.js. [@williamrogersdev](https://github.com/williamrogersdev) reported and fixed this for the App Router. +- `skipTrailingSlashRedirect` was parsed but ignored, and now works. +- [@wxpartners-web](https://github.com/wxpartners-web) reported that one cancelled request could stop the Static Assets response cache from serving prerendered pages for the rest of the isolate. That's fixed. +- Typed `cloudflare.config.ts` projects using Response Store in self-contained mode turned on Workers Cache for the whole Worker, so Cloudflare could serve application responses from its cache before the Worker ran. Every config Vinext generates now keeps it off except for the cache's own entrypoint. Rebuild and deploy to pick up the fix. +- Response Store answers a matching `If-None-Match` with a 304, as Next.js does, and a crawler's conditional request can no longer store an empty 304 for everyone else. +- `server-only` and `"use server"` modules are handled in browser Web Workers as they are in the rest of the client build. A Pages Router page that imports from middleware or an API route can no longer pull a `server-only` module into the client bundle. +- Pages Router builds work with `vite preview` on Node. +- `--warm-cache-include-fallbacks` never warmed fallback shells, and is now a deprecated no-op. + +Thanks to everyone who contributed to 1.2: [@jeffreywescott](https://github.com/jeffreywescott), [@jgeurts](https://github.com/jgeurts), [@MattieTK](https://github.com/MattieTK), [@Nsttt](https://github.com/Nsttt) and [@williamrogersdev](https://github.com/williamrogersdev), and to [@AhmedElBanna80](https://github.com/AhmedElBanna80), [@Mauladen](https://github.com/Mauladen) and [@wxpartners-web](https://github.com/wxpartners-web) for their bug reports. + +The full lists of changes are in the release notes for [vinext 1.2.0](https://github.com/cloudflare/vinext/releases/tag/vinext%401.2.0), [@vinext/cloudflare 1.1.1](https://github.com/cloudflare/vinext/releases/tag/%40vinext%2Fcloudflare%401.1.1) and [create-vinext-app 1.1.0](https://github.com/cloudflare/vinext/releases/tag/create-vinext-app%401.1.0). diff --git a/packages/cloudflare/CHANGELOG.md b/packages/cloudflare/CHANGELOG.md index e07372a29a..071022555a 100644 --- a/packages/cloudflare/CHANGELOG.md +++ b/packages/cloudflare/CHANGELOG.md @@ -1,5 +1,36 @@ # @vinext/cloudflare +## 1.1.1 + +Read the [Vinext 1.2 release post](https://vinext.dev/blog/vinext-1-2) for the highlights. + +### Bug Fixes + +#### Cloudflare + +- don't share an in-flight static assets cache index fetch across requests (#3965) +- never warm through an http-only Worker route (#3931) +- make --warm-cache-include-fallbacks a no-op (#3906) +- answer a stored page's conditional request as Next.js does (#3832) +- never store a conditional request's 304 in the Response Store (#3827) +- keep Workers Cache off Worker-wide in every generated config (#3820) + +#### Misc + +- **Cache:** treat x-api-key as a credential for shared response caches (#3879) +- **PPR:** key fallback shells apart from the pages of their literal paths (#3891) +- **Cache:** key oversized cache identities by a SHA-256 digest (#3876) + +### Performance + +- **Cloudflare:** share the pending static assets cache index fetch across requests (#3972) +- **Cloudflare:** skip getStaticPaths when Workers Cache revalidates a stored Pages entry (#3960) +- **Pages Router:** skip getStaticPaths when Response Store regenerates a stored entry (#3959) + +### Contributors + +- @james-elicx + ## 1.1.0 ### Features diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json index d3d0ab1fe3..c4f7fb3fb7 100644 --- a/packages/cloudflare/package.json +++ b/packages/cloudflare/package.json @@ -1,6 +1,6 @@ { "name": "@vinext/cloudflare", - "version": "1.1.0", + "version": "1.1.1", "description": "Cloudflare cache and image adapters for vinext.", "homepage": "https://vinext.dev/", "license": "MIT", diff --git a/packages/create-vinext-app/CHANGELOG.md b/packages/create-vinext-app/CHANGELOG.md index e1729417cf..1c43b9d580 100644 --- a/packages/create-vinext-app/CHANGELOG.md +++ b/packages/create-vinext-app/CHANGELOG.md @@ -1,5 +1,17 @@ # create-vinext-app +## 1.1.0 + +Read the [Vinext 1.2 release post](https://vinext.dev/blog/vinext-1-2) for the highlights. + +### Features + +- **Init:** add --name to set the Cloudflare Worker name (#3863) + +### Contributors + +- @MattieTK + ## 1.0.0 ### Features diff --git a/packages/create-vinext-app/package.json b/packages/create-vinext-app/package.json index 85c920d79f..5d46cae406 100644 --- a/packages/create-vinext-app/package.json +++ b/packages/create-vinext-app/package.json @@ -1,6 +1,6 @@ { "name": "create-vinext-app", - "version": "1.0.0", + "version": "1.1.0", "description": "Create a vinext app for Cloudflare Workers.", "homepage": "https://vinext.dev/", "license": "MIT", diff --git a/packages/vinext/CHANGELOG.md b/packages/vinext/CHANGELOG.md index 4ebe092ee9..98ea483521 100644 --- a/packages/vinext/CHANGELOG.md +++ b/packages/vinext/CHANGELOG.md @@ -1,5 +1,148 @@ # vinext +## 1.2.0 + +Read the [Vinext 1.2 release post](https://vinext.dev/blog/vinext-1-2) for the highlights. + +### Features + +- **Init:** add --name to set the Cloudflare Worker name (#3863) + +### Bug Fixes + +#### Actions + +- answer a plain 500 when a form action without JavaScript throws (#3903) +- send a failed fetch action's result as a rejected promise (#3902) +- evaluate server action modules outside the request scope (#3823) + +#### App Router + +- honour data-scroll-behavior="smooth" during route-change scroll (#3958) +- bound dynamicParams=false static-param checks to the request (#3930) +- proxy external config rewrites with middleware's request headers (#3885) +- gate SSR useSearchParams() in cacheComponents cache candidates (#3878) +- treat host-derived nextUrl reads as dynamic in route handlers (#3857) +- ignore non-Flight prefetch responses (#3826) +- drop an unauthorized interception source from non-RSC server actions (#3808) + +#### Build + +- honour clientOutDir for the client environment (#3952) +- limit the plugin-rsc scan glob workaround to 0.5.36 and 0.5.37 (#3970) +- keep server-only imports from middleware and API routes out of Pages client bundles (#3936) +- compile Server Function imports in browser Web Workers as references (#3819) +- reject server-only imports in browser Web Worker bundles (#3815) + +#### Cache + +- don't store an unstable_cache result under arguments changed before its callback ran (#3969) +- key and send cached fetch requests as they were when fetch was called (#3948) +- key ArrayBuffer and typed array body chunks by their bytes (#3942) +- keep in-memory tag invalidations from expiring (#3941) +- tag encoded Pages ISR paths with their decoded identity (#3940) +- key cached fetch bodies built into a Request (#3884) +- treat x-api-key as a credential for shared response caches (#3879) +- key fetch cache and dedupe by cf.resolveOverride (#3874) +- key oversized cache identities by a SHA-256 digest (#3876) +- rerun an unstable_cache nested in another's callback (#3867) +- keep fetch patching out of next/cache client graphs (#3855) +- key unstable_cache by an unambiguous, fixed-size digest (#3847) +- key unstable_cache by callback source even with keyParts (#3822) +- reject forged captures for inline "use cache" functions (#3812) + +#### Cloudflare + +- never follow a redirect during deploy-time path discovery (#3932) +- make --warm-cache-include-fallbacks a no-op (#3906) +- answer a stored page's conditional request as Next.js does (#3832) +- never store a conditional request's 304 in the Response Store (#3827) +- keep Workers Cache off Worker-wide in every generated config (#3820) +- scaffold runWorkerFirst for public files during init and apply middleware's request overrides (#3804) + +#### Config + +- match Next.js skipTrailingSlashRedirect behavior (#3946) +- collapse tab/newline-separated leading slashes in redirect destinations (#3935) +- match Next.js redirect query encoding and escape client rewrite params (#3810) + +#### Dynamic + +- add next/dynamic preload metadata to transpilePackages dependencies (#3793) +- only add a Suspense boundary for ssr: false or a loading component in the App Router (#3791) +- render the same element shape on the client so useId matches (#3792) +- render script preloads inside the next/dynamic Suspense boundary (#3790) +- key next/dynamic preloads for modules outside the Vite root (#3786) + +#### Image + +- emit the image endpoint under basePath like Next.js (#3961) +- admit Workers App Router image requests through middleware (#3890) +- answer 400 for a local image source that yields no image (#3943) +- fetch local image sources through middleware (#3937) +- admit App Router Node image requests through middleware (#3881) + +#### Pages + +- only expose res.revalidate() on API route responses (#3938) +- keep rendering and storing getStaticProps pages whose response was sent (#3873) +- render shared ISR status pages for their own pathname (#3856) +- splice custom Documents at per-render placeholders (#3850) +- splice the page at
instead of matching marker text in Document data (#3829) +- regenerate stale ISR entries with a full document render (#3861) +- keep raw-text markup out of the ISR shell splice (#3852) +- reject server-only behind a "use server" directive in Pages-only client builds (#3828) +- compare getStaticPaths catch-all params element-wise (#3824) +- keep header and cookie redirect conditions out of the client bundle (#3825) +- strip data exports assigned inside nested statements from client builds (#3818) + +#### Pages Router + +- honour data-scroll-behavior="smooth" during the shallow scroll reset (#3964) +- serve stored getStaticProps entries without rerunning getStaticPaths (#3928) +- route .html request paths as written in dev (#3934) +- keep the Host header out of the production request path (#3925) + +#### Server + +- cancel discarded response bodies with the request signal's reason (#3945) +- log uncaught errors on running servers instead of crashing (#3929) +- stop serving public files through double-encoded path aliases (#3846) + +#### Misc + +- **Middleware:** apply a rewrite that also carries x-middleware-next (#3951) +- **Deps:** require @vitejs/plugin-rsc 0.5.37 to bind Server Action closures to their action id (#3947) +- **Dev:** never honor a client-supplied forwarded middleware context (#3939) +- **Script:** escape