cdk-nextjs 0.7.6 → 0.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.jsii +3 -3
- package/.jsii.tabl.json +1 -1
- package/README.md +12 -3
- package/assets/lambdas/post-deploy/post-deploy.lambda/index.js +31 -13
- package/docs/adapter-runtime.md +1 -1
- package/docs/caching-guide.md +11 -1
- package/docs/harness-coverage.md +154 -73
- package/lib/adapter/adapter.mjs +12 -3
- package/lib/adapter/aws-cache-store.d.ts +221 -3
- package/lib/adapter/aws-cache-store.js +481 -35
- package/lib/adapter/aws-cache-store.test.js +706 -3
- package/lib/adapter/cache-handler.d.ts +5 -3
- package/lib/adapter/cache-handler.js +16 -10
- package/lib/adapter/cache-handler.mjs +517 -49
- package/lib/adapter/cache-handler.test.js +44 -1
- package/lib/adapter/cache-utils.d.ts +22 -0
- package/lib/adapter/cache-utils.js +44 -1
- package/lib/adapter/function-groups.js +17 -4
- package/lib/adapter/function-groups.test.js +33 -1
- package/lib/adapter/memory-cache-handler.d.ts +5 -1
- package/lib/adapter/memory-cache-handler.js +6 -2
- package/lib/adapter/s3-cache-handler.d.ts +12 -1
- package/lib/adapter/s3-cache-handler.js +30 -8
- package/lib/adapter/s3-cache-handler.test.js +84 -2
- package/lib/adapter/use-cache-common.d.ts +66 -1
- package/lib/adapter/use-cache-common.js +142 -19
- package/lib/adapter/use-cache-common.test.js +174 -1
- package/lib/adapter/use-cache-default-handler.js +36 -6
- package/lib/adapter/use-cache-default-handler.mjs +553 -47
- package/lib/adapter/use-cache-handlers.test.js +247 -7
- package/lib/adapter/use-cache-remote-handler.js +9 -7
- package/lib/adapter/use-cache-remote-handler.mjs +542 -45
- package/lib/lambdas/post-deploy/prune-revalidation-table.d.ts +2 -1
- package/lib/lambdas/post-deploy/prune-revalidation-table.js +35 -13
- package/lib/lambdas/post-deploy/prune-revalidation-table.test.js +19 -1
- package/lib/nextjs-api.js +1 -1
- package/lib/nextjs-build/nextjs-build.js +1 -1
- package/lib/nextjs-cache.js +1 -1
- package/lib/nextjs-compute/nextjs-containers.js +1 -1
- package/lib/nextjs-compute/nextjs-functions.js +1 -1
- package/lib/nextjs-distribution.js +1 -1
- package/lib/nextjs-post-deploy.js +1 -1
- package/lib/nextjs-static-assets.js +1 -1
- package/lib/root-constructs/nextjs-base-construct.js +1 -1
- package/lib/root-constructs/nextjs-global-containers.js +1 -1
- package/lib/root-constructs/nextjs-global-functions.js +1 -1
- package/lib/root-constructs/nextjs-regional-containers.js +1 -1
- package/lib/root-constructs/nextjs-regional-functions.js +1 -1
- package/lib/runtime/core.js +20 -4
- package/lib/runtime/core.test.js +17 -1
- package/lib/runtime/image-utils.d.ts +8 -9
- package/lib/runtime/image-utils.js +16 -1
- package/lib/runtime/image-utils.test.js +13 -1
- package/lib/runtime/image.js +9 -3
- package/lib/runtime/lambda.mjs +66 -18
- package/lib/runtime/server.mjs +66 -18
- package/lib/runtime/tag-manifest.d.ts +40 -0
- package/lib/runtime/tag-manifest.js +59 -0
- package/lib/runtime/tag-manifest.test.d.ts +1 -0
- package/lib/runtime/tag-manifest.test.js +47 -0
- package/package.json +3 -3
- package/scripts/e2e-deploy.sh +7 -2
- package/scripts/e2e-harness/README.md +72 -35
- package/scripts/e2e-harness/screen.mjs +148 -7
- package/scripts/playwright-install-deps.sh +40 -0
package/docs/harness-coverage.md
CHANGED
|
@@ -29,40 +29,47 @@ as a regression test once fixed.
|
|
|
29
29
|
|
|
30
30
|
## Status
|
|
31
31
|
|
|
32
|
-
As of 2026-10-
|
|
32
|
+
As of 2026-10-07, against next.js 16.4.0. The screening funnel lives in the
|
|
33
33
|
manifest's `screening` block, regenerated by
|
|
34
34
|
`node scripts/e2e-harness/screen.mjs --next ../next.js --write` (`--check` to
|
|
35
35
|
verify, which every e2e-harness run does in shard 1).
|
|
36
36
|
|
|
37
|
-
| Of next.js's
|
|
38
|
-
| ----------------------------- |
|
|
39
|
-
| edge-free (the hard ceiling) |
|
|
40
|
-
| candidates after every screen | **
|
|
41
|
-
| deployed and screened | 472
|
|
42
|
-
| in `rules.include` |
|
|
43
|
-
| plus, per-case, in `suites` |
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
|
59
|
-
|
|
|
60
|
-
|
|
|
37
|
+
| Of next.js's 1213 e2e files | Count |
|
|
38
|
+
| ----------------------------- | ------- |
|
|
39
|
+
| edge-free (the hard ceiling) | 1009 |
|
|
40
|
+
| candidates after every screen | **165** |
|
|
41
|
+
| deployed and screened | 472 |
|
|
42
|
+
| in `rules.include` | 421 |
|
|
43
|
+
| plus, per-case, in `suites` | 14 |
|
|
44
|
+
|
|
45
|
+
16.4 reopened the candidate pool. next.js replaced most `skipDeployment: true`
|
|
46
|
+
calls with `// @force-gate !deploy` pragmas, and in doing so dropped the deploy
|
|
47
|
+
exclusion from about 110 files that had carried it, so those now run in deploy
|
|
48
|
+
mode upstream. With about 30 files new in 16.4 and some 25 whose deploy
|
|
49
|
+
exclusion now covers only some of their suites, that makes 165 candidates, none
|
|
50
|
+
deployed yet. One more is `app-dir/treeshake-mw`: its `middleware.ts` opts into
|
|
51
|
+
the Node runtime, and the middleware screen used to count every `middleware.*`
|
|
52
|
+
as edge. Two included files, `prefetch-app-shell-cached-gsp` and
|
|
53
|
+
`prefetch-app-shell-global-eager`, no longer exist upstream and left
|
|
54
|
+
`rules.include`. Every disqualification that is a cdk-nextjs decision has a
|
|
55
|
+
verdict below. The job now is working through the candidates, and keeping the
|
|
56
|
+
included files passing on each scheduled run.
|
|
57
|
+
|
|
58
|
+
| Verdict | Files |
|
|
59
|
+
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
60
|
+
| pass | 421 whole files, plus the passing cases of the 14 `suites` files |
|
|
61
|
+
| fixed | the defects in the table below |
|
|
62
|
+
| bug | none open |
|
|
63
|
+
| upstream | 2 — `rewrites-destination-query-array`, `incremental-cache-path-traversal` |
|
|
64
|
+
| unsupported | 4 whole files (`prerender-encoding`, `middleware-fetches-with-any-http-method`, `revalidate-dynamic`, `proxy-readable-toweb`), plus the skipped cases of `trailingslash`, `revalidate-path-with-rewrites`, `partial-fallback-shell-upgrade`, `cached-navigations` and the 6 `invalid-static-asset-404-*` files, and 203 files the edge screen disqualifies without deploying |
|
|
65
|
+
| CDN-inherent | the 4 skipped cases of `dynamic-route-interpolation` |
|
|
66
|
+
| architectural | the 2 skipped `resume-data-cache` cases |
|
|
67
|
+
| no signal | 49 — 2 gated by next.js (plus 3 cases next.js runs as `.failing` in deploy mode), 29 `skipDeployment` or stubbed in deploy mode, 18 `next-config-ts-native-ts` files that cannot be built here |
|
|
61
68
|
|
|
62
69
|
### Which types the harness proves
|
|
63
70
|
|
|
64
|
-
The harness runs on all four types
|
|
65
|
-
|
|
71
|
+
The harness runs on all four types, on the schedule and by `workflow_dispatch`
|
|
72
|
+
(`nextjs_type`, `all` for every type) or by hand, with
|
|
66
73
|
`NextjsRegionalFunctions` behind `scripts/e2e-harness/stage-proxy.mjs`, which puts
|
|
67
74
|
back the stage prefix the harness would otherwise drop. The findings whose
|
|
68
75
|
plumbing differs per deployment type are also back-filled into
|
|
@@ -116,6 +123,13 @@ manifest's `rules.excludeByType["regional-functions"]`:
|
|
|
116
123
|
| `app-dir/action-forward-loop` | fails | Action forwarding fetches the request's own origin (`createForwardedActionResponse`, from `initURL`) — `127.0.0.1`. Same cause. |
|
|
117
124
|
| `hydration` | 1 / 3 | **Defect, fixed:** API Gateway collapses `//` to `/` in `event.path`, so Next.js never redirected it and the page failed to hydrate at `//`. `apiGatewayRequestPath` now restores the slashes. |
|
|
118
125
|
|
|
126
|
+
`app-dir/asset-prefix` joined them on 2026-10-08, for the same unserved
|
|
127
|
+
`assetPrefix`. It had passed until then by timing: its bundles case fetches
|
|
128
|
+
inside an un-awaited `forEach`, so the 404s reject after the case ends and jest
|
|
129
|
+
charges them to whichever case is running, usually none. That run they landed
|
|
130
|
+
in `rewrites that do not start with assetPrefix should still work`. Excluding
|
|
131
|
+
the bundles case removes the stray rejections too.
|
|
132
|
+
|
|
119
133
|
## Passing — in `rules.include`
|
|
120
134
|
|
|
121
135
|
The list is the manifest's `rules.include`; it is not duplicated here. Notes worth
|
|
@@ -151,15 +165,19 @@ A `suites` file is included with the cases in its `failed` array skipped
|
|
|
151
165
|
Use this instead of dropping a whole file when the residual failures have a
|
|
152
166
|
verdict below.
|
|
153
167
|
|
|
154
|
-
| File | Cases | Skipped, and why
|
|
155
|
-
| ------------------------------------------------------------------ | ---------- |
|
|
156
|
-
| `app-dir/trailingslash` | 6 / 8 | 2 `should revalidate a page with generated static params` — unsupported (async invalidation)
|
|
157
|
-
| `app-dir/resume-data-cache` | 3 / 5 | 2 `should have consistent data between static and dynamic renders` — architectural
|
|
158
|
-
| `dynamic-route-interpolation` | 3 / 7 | 4 requesting unencoded `[`/`]` — CDN-inherent
|
|
159
|
-
| `app-dir/revalidate-path-with-rewrites` | 1 / 2 | `static page` — unsupported (async invalidation)
|
|
160
|
-
| `
|
|
161
|
-
| `invalid-static-asset-404-
|
|
162
|
-
| `
|
|
168
|
+
| File | Cases | Skipped, and why |
|
|
169
|
+
| ------------------------------------------------------------------ | ---------- | -------------------------------------------------------------------------------------------------------------- |
|
|
170
|
+
| `app-dir/trailingslash` | 6 / 8 | 2 `should revalidate a page with generated static params` — unsupported (async invalidation) |
|
|
171
|
+
| `app-dir/resume-data-cache` | 3 / 5 | 2 `should have consistent data between static and dynamic renders` — architectural |
|
|
172
|
+
| `dynamic-route-interpolation` | 3 / 7 | 4 requesting unencoded `[`/`]` — CDN-inherent |
|
|
173
|
+
| `app-dir/revalidate-path-with-rewrites` | 1 / 2 | `static page` — unsupported (async invalidation) |
|
|
174
|
+
| `app-dir/partial-fallback-shell-upgrade` | 10 / 13 | 3 waiting for `x-vercel-cache: HIT` — unsupported (Vercel's response header) |
|
|
175
|
+
| `invalid-static-asset-404-app` (+ `-asset-prefix`, `-base-path`) | 2 / 3 each | `should return 404 with plain text when fetching invalid asset path` — S3's XML 404, unsupported |
|
|
176
|
+
| `invalid-static-asset-404-pages` (+ `-asset-prefix`, `-base-path`) | 2 / 3 each | Same case, same reason |
|
|
177
|
+
| `next-form/default/app-dir` | 13 / 14 | `flakey`: `should soft-navigate on submit and show the prefetched loading state` — timing |
|
|
178
|
+
| `app-dir/metadata-streaming-parallel-routes` | 4 / 6 | 2 `should only bundle the selected metadata resolution implementation` — no signal (`@gate !deploy` inversion) |
|
|
179
|
+
| `app-dir/segment-cache/metadata` | 2 / 3 | `requests only the head when the page segments are cached` — no signal (`@gate !deploy` inversion) |
|
|
180
|
+
| `app-dir/segment-cache/cached-navigations` | 19 / 20 | `caches a fully static on-demand param for repeated navigations` — unsupported (`next start` serving) |
|
|
163
181
|
|
|
164
182
|
The `next-form` case submits right after the page loads and waits for the loading
|
|
165
183
|
state the router prefetched; where the prefetch has not landed, `#loading` never
|
|
@@ -172,48 +190,50 @@ another, with no cdk-nextjs error.
|
|
|
172
190
|
`#` labels a row for the rest of this doc. **23**, **37** and **38** were found by
|
|
173
191
|
the four-type suite (`examples/e2e-tests`) rather than the harness: 23 is under
|
|
174
192
|
"Stale-while-revalidate after a tag revalidation", 37 and 38 follow the table.
|
|
175
|
-
There is no **28**: the number was skipped, not withdrawn. So
|
|
193
|
+
There is no **28**: the number was skipped, not withdrawn. So 35 of the 38 fixed
|
|
176
194
|
defects came from the harness and 3 from the four-type suite.
|
|
177
195
|
|
|
178
196
|
"4-type spec" names the `examples/e2e-tests/src/*.test.ts` file guarding it on all
|
|
179
197
|
four deployment types; `—` means harness or unit tests only.
|
|
180
198
|
|
|
181
|
-
| # | Symptom (as a test sees it) | Root cause
|
|
182
|
-
| --- | ------------------------------------------------------------------------------------------------------------ |
|
|
183
|
-
| 1 | Every POST with a body (server actions) → `403 InvalidSignatureException` | `patchFetchInClientJs` ran inside `runNextBuild()`, so `skipBuild: true` shipped an unpatched client with no `x-amz-content-sha256`
|
|
184
|
-
| 2 | RSC responses labeled `text/html`; navigation duration `NaN`; action `redirect()` with no head | Seeded the HTML prerender's `initialHeaders` (`content-type`, `vary`, `x-nextjs-prerender`/`-postponed`) into `APP_PAGE` entries
|
|
185
|
-
| 3 | Empty streamed response: Function URL drops the prelude (bare `200 octet-stream`), API Gateway `502` | `RESPONSE_STREAM` with zero bytes after the prelude delimiter
|
|
186
|
-
| 4 | Client navigation to `/` → 404, `x-nextjs-cache: HIT`, empty body | `groupPrerenders` rebuilt `${route}.rsc`; the root's payload is `/index.rsc`
|
|
187
|
-
| 5 | `[id]/[id2]`: second param is the first's value plus `2` | `@next/routing` substitutes `$nxtP*` placeholders in insertion order, so a prefixing name is mangled
|
|
188
|
-
| 6 | `trailingSlash: true` — every canonical `/a/` URL 404s | Output pathnames never carry the slash; `@next/routing` matches exactly
|
|
189
|
-
| 7 | `next build` fails: "encountered uncached or runtime data during prerendering" (`cacheComponents` + handler) | Build-time cache handler was write-only; the final prerender pass re-issued fetches
|
|
190
|
-
| 8 | `revalidateTag` never reaches a build-time prerender; tagged entries evicted right after storing | Handler read a `tags` array only runtime `set` writes, not `x-next-cache-tags`; mapping rows stamped `revalidatedAt = now`
|
|
191
|
-
| 9 | Origin re-renders after `revalidatePath`, browser still gets the old page (`x-cache: Hit from cloudfront`) | Invalidation paths came only from tag mapping rows, which build-time prerenders lack
|
|
192
|
-
| 10 | Fixture _n+1_ serves fixture _n_'s pages (another app's `/_not-found`) | With `NEXT_DEPLOYMENT_ID` set, next's `BUILD_ID` is the constant `build-TfctsWXpff2fKS`; our cache is partitioned by build id
|
|
193
|
-
| 11 | Unmatched optional catchall renders a segment `"undefined"` | `@next/routing` reports an unfilled param as a present key with value `undefined`
|
|
194
|
-
| 12 | `robots.txt`, `sitemap.xml`, `manifest.webmanifest` served as `application/octet-stream` | Static metadata staged as `<route>.body`; `send` types by extension
|
|
195
|
-
| 13 | Root-params app (no `app/layout.tsx`) 404s at every URL | next gates the `dynamicRoutes` rule on draft cookies; concrete prerenders were not registered
|
|
196
|
-
| 14 | PPR fallback shell rendered per request (`(runtime)` where `(buildtime)` expected) | Seeding loop skipped every prerender group whose pathname contains `[`
|
|
197
|
-
| 15 | Custom `pages/404` replaced by the built-in 404 | `statusTarget` tried `/_error` before an invocable `/404`
|
|
198
|
-
| 16 | Path-style `assetPrefix` — every bundle 404s | No CloudFront behavior for `<prefix>/_next/static*`; Next applies `assetPrefix` on top of `basePath`
|
|
199
|
-
| 17 | Pages Router home page `/` 404s (many unrelated-looking symptoms) | `normalizePagePath("/")` is `/index`; nothing registered `/`
|
|
200
|
-
| 18 | Space in a `public/` filename fails synth: "Invalid CloudFront Distribution Cache Behavior Path Pattern" | Characters outside CloudFront's path-pattern alphabet threw
|
|
201
|
-
| 19 | A throw answers plain-text 500, never the app's error page | No error-page ladder; one `failWith` wrote a bare 500
|
|
202
|
-
| 20 | Every i18n app answers `/` with a 308 to `/en-US` | `@next/routing` builds `/${locale}/`, which the trailing-slash 308 then matches
|
|
203
|
-
| 21 | Static `getStaticProps` data route 404s, so client navigation into it full-reloads | Without middleware, next emits no rule for it; the concrete prerender was skipped
|
|
204
|
-
| 22 | Absolute `assetPrefix` with a path — every bundle 404s | The prefix's path was reduced to `""`, so no behavior was added
|
|
205
|
-
| 24 | `/_next/image` 400s for a `public/` file whose name needs percent-encoding | `fetchFromS3` used the still-encoded href as the S3 key
|
|
206
|
-
| 25 | A web worker never starts (assertion times out on placeholder text) | `patch-fetch.js` was prepended to `turbopack-worker-*.js` and read `window`
|
|
207
|
-
| 26 | Large init cache only partly seeded (`MISS`, postponed shell); deploy reported success | `BucketDeployment` Lambda hit `ENOSPC` in 512 MiB `/tmp`; `--hotswap` ignores a custom resource's `FAILED`
|
|
208
|
-
| 27 | A 1 MB segment prefetch takes ~5s | Buffers serialized as JSON integer arrays and parsed with a reviver
|
|
209
|
-
| 29 | A `beforeFiles` rewrite that matches its own output is applied twice | `RouteModule.prepare` re-runs `handleRewrites` on the already-rewritten `req.url`
|
|
210
|
-
| 30 | `//` → 500, `/api//json` → 404, instead of a 308 | `resolveRoutes` does not collapse repeated slashes; `new URL("//")` reads a host
|
|
211
|
-
| 31 | On Global Functions, `//` paths → `403 InvalidSignatureException` | OAC signs the raw path, the Function URL canonicalizes before verifying
|
|
212
|
-
| 32 | Build-time `notFound: true` served as `200` with the 404 body | Seeded a `PAGES` entry holding `404.html`; the Pages HIT path ignores `value.status`
|
|
213
|
-
| 33 | `res.revalidate()` fails (`{revalidated:false}`), reason reports `stale` | No `routerServerContext.revalidate` supplied: "missing internal router-server-methods"
|
|
214
|
-
| 34 | Untagged `force-cache` fetch is never evicted by `revalidatePath`/`revalidateTag` | Fetch entries were checked against their stored tags, not the request's `ctx.tags` + `ctx.softTags`
|
|
215
|
-
| 35 | Catch-all capture containing `%2F` → 500 ("Requested and resolved page mismatch") | The `nxtP` query contract decodes twice and splits on `/`
|
|
216
|
-
| 36 | Regional Functions: every redirect drops the stage prefix, following it → 403 | API Gateway strips the stage before invoking the Lambda
|
|
199
|
+
| # | Symptom (as a test sees it) | Root cause | Fix / guard | 4-type spec |
|
|
200
|
+
| --- | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
|
|
201
|
+
| 1 | Every POST with a body (server actions) → `403 InvalidSignatureException` | `patchFetchInClientJs` ran inside `runNextBuild()`, so `skipBuild: true` shipped an unpatched client with no `x-amz-content-sha256` | Call moved to the `NextjsBuild` constructor, marker-guarded against double wrap. The harness is the `skipBuild` guard | `request-methods` (in-page POST ok, raw POST 403) |
|
|
202
|
+
| 2 | RSC responses labeled `text/html`; navigation duration `NaN`; action `redirect()` with no head | Seeded the HTML prerender's `initialHeaders` (`content-type`, `vary`, `x-nextjs-prerender`/`-postponed`) into `APP_PAGE` entries | `appPageCacheHeaders`, `src/adapter/cache-utils.ts` | `rsc-navigation` |
|
|
203
|
+
| 3 | Empty streamed response: Function URL drops the prelude (bare `200 octet-stream`), API Gateway `502` | `RESPONSE_STREAM` with zero bytes after the prelude delimiter | `ResponseSink.padEmptyBody` (one space byte), `src/runtime/http/sink.test.ts` | `response-bodies`, `request-methods`, `server-action-redirect` |
|
|
204
|
+
| 4 | Client navigation to `/` → 404, `x-nextjs-cache: HIT`, empty body | `groupPrerenders` rebuilt `${route}.rsc`; the root's payload is `/index.rsc` | `groupPrerenders`, `src/adapter/cache-utils.ts` | `rsc-navigation` |
|
|
205
|
+
| 5 | `[id]/[id2]`: second param is the first's value plus `2` | `@next/routing` substitutes `$nxtP*` placeholders in insertion order, so a prefixing name is mangled | `repairRouteParamQuery`, `src/runtime/dispatch.ts` | `routing-params` |
|
|
206
|
+
| 6 | `trailingSlash: true` — every canonical `/a/` URL 404s | Output pathnames never carry the slash; `@next/routing` matches exactly | `withTrailingSlashVariants`, `Dispatcher.normalizePathname` | — |
|
|
207
|
+
| 7 | `next build` fails: "encountered uncached or runtime data during prerendering" (`cacheComponents` + handler) | Build-time cache handler was write-only; the final prerender pass re-issued fetches | `LocalFileCacheHandler.get`; `cache-handler.test.ts` | — |
|
|
208
|
+
| 8 | `revalidateTag` never reaches a build-time prerender; tagged entries evicted right after storing | Handler read a `tags` array only runtime `set` writes, not `x-next-cache-tags`; mapping rows stamped `revalidatedAt = now` | `entryTags`, `checkIfRevalidated`, per-tag marker row — `src/adapter/s3-cache-handler.ts` | — |
|
|
209
|
+
| 9 | Origin re-renders after `revalidatePath`, browser still gets the old page (`x-cache: Hit from cloudfront`) | Invalidation paths came only from tag mapping rows, which build-time prerenders lack | `implicitTagPath`/`invalidationVariants`; the build's tag manifest (s3-cache-handler) | — |
|
|
210
|
+
| 10 | Fixture _n+1_ serves fixture _n_'s pages (another app's `/_not-found`) | With `NEXT_DEPLOYMENT_ID` set, next's `BUILD_ID` is the constant `build-TfctsWXpff2fKS`; our cache is partitioned by build id | `NextjsBuild.buildId` suffixes the manifest's `deploymentId` (`src/nextjs-build/nextjs-build.ts`) | — |
|
|
211
|
+
| 11 | Unmatched optional catchall renders a segment `"undefined"` | `@next/routing` reports an unfilled param as a present key with value `undefined` | `repairRouteParamQuery` second pass; `dispatch.test.ts` | `routing-params` |
|
|
212
|
+
| 12 | `robots.txt`, `sitemap.xml`, `manifest.webmanifest` served as `application/octet-stream` | Static metadata staged as `<route>.body`; `send` types by extension | `setBodyFileContentType`, `src/runtime/static-files.ts` | `metadata-routes` |
|
|
213
|
+
| 13 | Root-params app (no `app/layout.tsx`) 404s at every URL | next gates the `dynamicRoutes` rule on draft cookies; concrete prerenders were not registered | `addPrerenderPathnames`, `src/adapter/build-outputs.ts` | `draft-mode`, `cookies` (cookie half only) |
|
|
214
|
+
| 14 | PPR fallback shell rendered per request (`(runtime)` where `(buildtime)` expected) | Seeding loop skipped every prerender group whose pathname contains `[` | Prerender-group loop in `writeInitCache`, `src/adapter/init-cache.ts`; `init-cache.test.ts` | — |
|
|
215
|
+
| 15 | Custom `pages/404` replaced by the built-in 404 | `statusTarget` tried `/_error` before an invocable `/404` | `statusTarget`; `dispatch.test.ts` "prefers an invocable custom /404 over /\_error" | — |
|
|
216
|
+
| 16 | Path-style `assetPrefix` — every bundle 404s | No CloudFront behavior for `<prefix>/_next/static*`; Next applies `assetPrefix` on top of `basePath` | `relativeAssetPrefix`, `NextjsDistribution.addAssetPrefixBehavior` (CF function); regional types warn at synth | — |
|
|
217
|
+
| 17 | Pages Router home page `/` 404s (many unrelated-looking symptoms) | `normalizePagePath("/")` is `/index`; nothing registered `/` | `routablePathnames`, `src/adapter/build-outputs.ts` | — |
|
|
218
|
+
| 18 | Space in a `public/` filename fails synth: "Invalid CloudFront Distribution Cache Behavior Path Pattern" | Characters outside CloudFront's path-pattern alphabet threw | `toPathPattern` (`src/nextjs-distribution.ts`); `NextjsApi` warns and skips on Regional Functions. See 37 | `static-assets` (asserts 404 on Regional Functions) |
|
|
219
|
+
| 19 | A throw answers plain-text 500, never the app's error page | No error-page ladder; one `failWith` wrote a bare 500 | `resolveErrorTarget` (dispatch.ts) + `NextjsRuntime.sendError`, `no-store` | `status-codes` (App Router side) |
|
|
220
|
+
| 20 | Every i18n app answers `/` with a 308 to `/en-US` | `@next/routing` builds `/${locale}/`, which the trailing-slash 308 then matches | `Dispatcher.withRootLocale`, `normalizeRedirectLocation` | — |
|
|
221
|
+
| 21 | Static `getStaticProps` data route 404s, so client navigation into it full-reloads | Without middleware, next emits no rule for it; the concrete prerender was skipped | `addPrerenderPathnames` registers concrete `/_next/data/…json` when no ungated rule matches | — |
|
|
222
|
+
| 22 | Absolute `assetPrefix` with a path — every bundle 404s | The prefix's path was reduced to `""`, so no behavior was added | `assetPrefixPath`, `src/utils/base-path.ts` | — |
|
|
223
|
+
| 24 | `/_next/image` 400s for a `public/` file whose name needs percent-encoding | `fetchFromS3` used the still-encoded href as the S3 key | Tolerant percent-decode in `fetchFromS3`, `src/runtime/image-utils.ts` | `static-assets` (asserts `image/webp`) |
|
|
224
|
+
| 25 | A web worker never starts (assertion times out on placeholder text) | `patch-fetch.js` was prepended to `turbopack-worker-*.js` and read `window` | `patch-fetch.js` uses `globalThis`; worker-scope block in `patch-fetch.test.ts` | — |
|
|
225
|
+
| 26 | Large init cache only partly seeded (`MISS`, postponed shell); deploy reported success | `BucketDeployment` Lambda hit `ENOSPC` in 512 MiB `/tmp`; `--hotswap` ignores a custom resource's `FAILED` | `sizeDeploymentLambda`, `src/nextjs-cache.ts` | — |
|
|
226
|
+
| 27 | A 1 MB segment prefetch takes ~5s | Buffers serialized as JSON integer arrays and parsed with a reviver | Base64 in `serializeCacheValue`/`parseCacheValue` (old format still readable) | — |
|
|
227
|
+
| 29 | A `beforeFiles` rewrite that matches its own output is applied twice | `RouteModule.prepare` re-runs `handleRewrites` on the already-rewritten `req.url` | `src/runtime/core.ts` hands `requestMeta.query` the resolved query | `config-routing` |
|
|
228
|
+
| 30 | `//` → 500, `/api//json` → 404, instead of a 308 | `resolveRoutes` does not collapse repeated slashes; `new URL("//")` reads a host | Collapse + 308 in `src/runtime/core.ts`, ahead of any parsing | `url-normalization` |
|
|
229
|
+
| 31 | On Global Functions, `//` paths → `403 InvalidSignatureException` | OAC signs the raw path, the Function URL canonicalizes before verifying | Viewer-request function, `createDynamicCloudFrontFunctionAssociations` | `url-normalization` (`FunctionGeneratedResponse`) |
|
|
230
|
+
| 32 | Build-time `notFound: true` served as `200` with the 404 body | Seeded a `PAGES` entry holding `404.html`; the Pages HIT path ignores `value.status` | `writeInitCache` skips a `PAGES` prerender whose `initialStatus` ≠ 200; `init-cache.test.ts` | `status-codes` (App Router half) |
|
|
231
|
+
| 33 | `res.revalidate()` fails (`{revalidated:false}`), reason reports `stale` | No `routerServerContext.revalidate` supplied: "missing internal router-server-methods" | `requestMeta.revalidate` in `src/runtime/core.ts`; `core.test.ts` | — |
|
|
232
|
+
| 34 | Untagged `force-cache` fetch is never evicted by `revalidatePath`/`revalidateTag` | Fetch entries were checked against their stored tags, not the request's `ctx.tags` + `ctx.softTags` | `isFetchCacheGet` split in `S3CacheHandler.get` | — |
|
|
233
|
+
| 35 | Catch-all capture containing `%2F` → 500 ("Requested and resolved page mismatch") | The `nxtP` query contract decodes twice and splits on `/` | `outOfBandRouteParams` (dispatch.ts) passes `requestMeta.params`. Verified offline; the file is upstream | — |
|
|
234
|
+
| 36 | Regional Functions: every redirect drops the stage prefix, following it → 403 | API Gateway strips the stage before invoking the Lambda | `src/runtime/api-gateway-path.ts` uses `requestContext.path` when `basePath` starts with the stage | `url-normalization` (where it was found) |
|
|
235
|
+
| 39 | `cached-navigations`: a cached navigation shows params or lacks `'use cache'` content in its static stage | `refreshTags` awaited the revalidation log `Query`, which Next awaits in the `'use cache'` lookup, past the stage timer | `tagMethods` starts the refresh unawaited (`use-cache-common.ts`); `use-cache-handlers.test.ts` | — |
|
|
236
|
+
| 40 | `cached-navigations`: after a draft mode page load, a navigation reuses nothing from it | 16.4 ends a staged render's static stage on `Date.now()`, `new Date()`, `Math.random()` or a `crypto` random call in the request's context. `refreshTags` read `Date.now()` there, and a draft mode page is rendered whole at request time, so its static stage (`l`) was 0 bytes | Cache handler methods run outside the request (`outsideRender`, `use-cache-common.ts`; `outsideRequest`, `runtime/tag-manifest.ts`); `tag-manifest.test.ts`, `use-cache-common.test.ts` | — |
|
|
217
237
|
|
|
218
238
|
Found by the four-type suite:
|
|
219
239
|
|
|
@@ -358,6 +378,47 @@ Making them pass would mean blocking every revalidation on an invalidation.
|
|
|
358
378
|
attempt, on a case with no prerender to invalidate. That was defect 34 — the
|
|
359
379
|
`dynamic page` case of `revalidate-path-with-rewrites` had passed once by luck.
|
|
360
380
|
|
|
381
|
+
### `x-vercel-cache` is Vercel's header
|
|
382
|
+
|
|
383
|
+
`app-dir/partial-fallback-shell-upgrade`, 3 of 13 (the other 10 via `suites`),
|
|
384
|
+
all new in 16.4 and all in the `partialPrefetching disabled` describe. Under
|
|
385
|
+
`isNextDeploy` each polls for `x-vercel-cache: HIT`, the header Vercel's CDN adds
|
|
386
|
+
to a response it served from cache. Nothing in cdk-nextjs emits it, so the poll
|
|
387
|
+
times out on `null` before the case's own checks (the shell's `data-rendered-at`
|
|
388
|
+
reused across params and across a revalidation) run. Those checks are the
|
|
389
|
+
coverage being given up; the passing `prefix` cases cover shell sharing and
|
|
390
|
+
revalidation for a fallback shell, but not for an on-demand shell (no
|
|
391
|
+
`generateStaticParams`). Emitting the header would mean claiming a CDN hit
|
|
392
|
+
cdk-nextjs can't vouch for. Failed both attempts on Global Functions in
|
|
393
|
+
the 2026-10-07 run, with no deploy error.
|
|
394
|
+
|
|
395
|
+
### An on-demand param's RSC navigation is a live render, not a prerender
|
|
396
|
+
|
|
397
|
+
`app-dir/segment-cache/cached-navigations` (1 of 20, via `suites`), new in 16.4:
|
|
398
|
+
`caches a fully static on-demand param for repeated navigations`. Its
|
|
399
|
+
`isNextDeploy` branch navigates to `/fully-static-params/t4` (not in
|
|
400
|
+
`generateStaticParams`) and expects a repeat visit a minute later to make no
|
|
401
|
+
request, because "the platform serves a completed static prerender". On Vercel,
|
|
402
|
+
Next.js runs in minimal mode: an RSC navigation without postponed state is not a
|
|
403
|
+
dynamic RSC request (`isDynamicRSCRequest` in `app-page-runtime.js`), so the
|
|
404
|
+
function prerenders `t4` and the edge caches the result, which starts with the
|
|
405
|
+
`#` "complete" marker byte (`prependIsPartialByteToChunks`). cdk-nextjs runs
|
|
406
|
+
Next.js the way `next start` does, where the same request is a dynamic RSC
|
|
407
|
+
request: a live render, without a marker, which the client reads as partial
|
|
408
|
+
(`stripIsPartialByte`) and so fetches again. The response we sent was exactly
|
|
409
|
+
that: `cache-control: private, no-store`, no marker, `"l"` set to its 2666-byte
|
|
410
|
+
static stage. The case's own non-`isNextDeploy` branch asserts this request for
|
|
411
|
+
`next start`. Matching Vercel would mean minimal mode, with the runtime doing the
|
|
412
|
+
platform's prerender routing and caching. Not done.
|
|
413
|
+
|
|
414
|
+
The file's other case new in 16.4 that failed, `reuses cached page segment
|
|
415
|
+
across fallback params after a draft mode HTML load`, was a cdk-nextjs defect,
|
|
416
|
+
fixed (40 in the table). It passed offline because `e2e-offline.sh` has no
|
|
417
|
+
revalidation log, so `refreshTags` returned before reading the clock. And it
|
|
418
|
+
passed on Regional Containers only because that harness URL is plain HTTP: the
|
|
419
|
+
browser drops the `Secure` draft cookie there, so the page never loaded in
|
|
420
|
+
draft mode.
|
|
421
|
+
|
|
361
422
|
### A test's `env` reaches `next build`, not the deployed app
|
|
362
423
|
|
|
363
424
|
Per-test `env` from `nextTestSetup({ env })` is set for `next build`, so values
|
|
@@ -420,6 +481,26 @@ payload — the problem next.js's `cdn-cache-busting` gates out itself.
|
|
|
420
481
|
| `next-dynamic-lazy-compilation` | Returns early under `shouldUseTurbopack()`, and the harness sets `IS_TURBOPACK_TEST=1`. Webpack-only. 5.1s. |
|
|
421
482
|
| `skipDeployment: true` files (manifest key `skipDeployment, 236 files`) | See "The `skipDeployment` traps" below. |
|
|
422
483
|
|
|
484
|
+
### A `@gate !deploy` case that passes is reported as a failure
|
|
485
|
+
|
|
486
|
+
16.4's case-level `// @gate !deploy` does not skip the case in deploy mode: it
|
|
487
|
+
runs it as jest's `.failing` (`test/lib/gate/runtime.ts`), on the assumption that
|
|
488
|
+
it cannot pass on Vercel. Where cdk-nextjs passes it, jest reports _"Failing test
|
|
489
|
+
passed even though it was supposed to fail"_ on every attempt and every type. So
|
|
490
|
+
the skip is in `suites`, and the case says nothing either way:
|
|
491
|
+
|
|
492
|
+
- `metadata-streaming-parallel-routes`, its bundle case (in both
|
|
493
|
+
`parallelRouteMetadata` describes): it reads `.next/server` from the test dir,
|
|
494
|
+
which Vercel's deploy harness doesn't expose and ours, building in place, does.
|
|
495
|
+
- `segment-cache/metadata`, its head-only case: on Vercel a cold ISR path is a
|
|
496
|
+
completed static prerender that sends the page body along with the head.
|
|
497
|
+
cdk-nextjs serves it the `next start` way (see "An on-demand param's RSC
|
|
498
|
+
navigation is a live render" above), so only the head is requested, which is
|
|
499
|
+
what the case asserts.
|
|
500
|
+
|
|
501
|
+
Found on the 2026-10-08 run. `screen.mjs` doesn't flag case-level `@gate`; a new
|
|
502
|
+
one surfaces as this error on the first run that includes it.
|
|
503
|
+
|
|
423
504
|
## No signal — the fixture cannot be built here
|
|
424
505
|
|
|
425
506
|
### `next-config-ts-native-ts/**` needs a `next build` flag the harness does not pass
|
package/lib/adapter/adapter.mjs
CHANGED
|
@@ -1633,9 +1633,18 @@ function resolveFile(pathname, { fileOfTemplate: files, routing }) {
|
|
|
1633
1633
|
return exact;
|
|
1634
1634
|
}
|
|
1635
1635
|
for (const route of routing.dynamicRoutes ?? []) {
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1636
|
+
const match = new RegExp(route.sourceRegex, "i").exec(pathname);
|
|
1637
|
+
if (match) {
|
|
1638
|
+
const template = route.destination?.split("?")[0].replace(
|
|
1639
|
+
/\$([1-9]\d*)/g,
|
|
1640
|
+
// A group that matched nothing (an absent `.rsc` suffix) is "", as
|
|
1641
|
+
// in `replaceDestination`; an index past the last group stays as is.
|
|
1642
|
+
(literal, index) => Number(index) < match.length ? match[Number(index)] ?? "" : literal
|
|
1643
|
+
);
|
|
1644
|
+
const file = template === void 0 ? void 0 : files.get(template);
|
|
1645
|
+
if (file !== void 0) {
|
|
1646
|
+
return file;
|
|
1647
|
+
}
|
|
1639
1648
|
}
|
|
1640
1649
|
}
|
|
1641
1650
|
return void 0;
|