cdk-nextjs 0.6.2 → 0.7.1

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.
Files changed (263) hide show
  1. package/.jsii +2404 -2723
  2. package/.jsii.tabl.json +1 -1
  3. package/API.md +1681 -1930
  4. package/CLAUDE.md +2 -1
  5. package/README.md +429 -130
  6. package/assets/lambdas/post-deploy/post-deploy.lambda/index.js +148 -169
  7. package/assets/lambdas/redeploy-stage/redeploy-stage.lambda/index.js +875 -0
  8. package/docs/adapter-runtime.md +185 -0
  9. package/docs/breaking-changes.md +667 -4
  10. package/docs/caching-guide.md +156 -112
  11. package/docs/cdk-nextjs-cfn-exec-policy.json +2 -1
  12. package/docs/development-guide.md +1 -1
  13. package/docs/harness-coverage.md +598 -0
  14. package/docs/init-cache-deployment.md +32 -1
  15. package/docs/next-build-output-guide.md +30 -20
  16. package/examples/README.md +5 -1
  17. package/lib/adapter/__fixtures__/app-playground-base-path.json +4926 -0
  18. package/lib/adapter/__fixtures__/app-playground.json +4917 -0
  19. package/lib/adapter/__fixtures__/pages-i18n.json +1728 -0
  20. package/lib/adapter/adapter.mjs +1551 -185
  21. package/lib/adapter/adapter.test.js +106 -0
  22. package/lib/adapter/aws-cache-store.d.ts +489 -0
  23. package/lib/adapter/aws-cache-store.js +1004 -0
  24. package/lib/adapter/aws-cache-store.test.d.ts +1 -0
  25. package/lib/adapter/aws-cache-store.test.js +361 -0
  26. package/lib/adapter/build-outputs.d.ts +80 -0
  27. package/lib/adapter/build-outputs.js +857 -0
  28. package/lib/adapter/build-outputs.test.d.ts +1 -0
  29. package/lib/adapter/build-outputs.test.js +863 -0
  30. package/lib/adapter/cache-handler.d.ts +9 -2
  31. package/lib/adapter/cache-handler.js +72 -96
  32. package/lib/adapter/cache-handler.mjs +18956 -17774
  33. package/lib/adapter/cache-handler.test.js +263 -31
  34. package/lib/adapter/cache-kinds.d.ts +32 -0
  35. package/lib/adapter/cache-kinds.js +35 -0
  36. package/lib/adapter/cache-kinds.test.d.ts +1 -0
  37. package/lib/adapter/cache-kinds.test.js +69 -0
  38. package/lib/adapter/cache-utils.d.ts +90 -0
  39. package/lib/adapter/cache-utils.js +199 -4
  40. package/lib/adapter/cache-utils.test.d.ts +1 -0
  41. package/lib/adapter/cache-utils.test.js +179 -0
  42. package/lib/adapter/function-groups.d.ts +215 -0
  43. package/lib/adapter/function-groups.js +1099 -0
  44. package/lib/adapter/function-groups.test.d.ts +1 -0
  45. package/lib/adapter/function-groups.test.js +743 -0
  46. package/lib/adapter/init-cache.d.ts +6 -0
  47. package/lib/adapter/init-cache.js +245 -0
  48. package/lib/adapter/init-cache.test.d.ts +1 -0
  49. package/lib/adapter/init-cache.test.js +137 -0
  50. package/lib/adapter/local-file-cache-handler.d.ts +23 -0
  51. package/lib/adapter/local-file-cache-handler.js +52 -1
  52. package/lib/adapter/memory-cache-handler.d.ts +26 -17
  53. package/lib/adapter/memory-cache-handler.js +32 -50
  54. package/lib/adapter/memory-cache-handler.test.js +105 -85
  55. package/lib/adapter/s3-cache-handler.d.ts +179 -40
  56. package/lib/adapter/s3-cache-handler.js +807 -277
  57. package/lib/adapter/s3-cache-handler.test.js +1186 -31
  58. package/lib/adapter/use-cache-common.d.ts +90 -0
  59. package/lib/adapter/use-cache-common.js +250 -0
  60. package/lib/adapter/use-cache-common.test.d.ts +1 -0
  61. package/lib/adapter/use-cache-common.test.js +646 -0
  62. package/lib/adapter/use-cache-default-handler.d.ts +24 -0
  63. package/lib/adapter/use-cache-default-handler.js +90 -0
  64. package/lib/adapter/use-cache-default-handler.mjs +38645 -0
  65. package/lib/adapter/use-cache-handlers.test.d.ts +1 -0
  66. package/lib/adapter/use-cache-handlers.test.js +349 -0
  67. package/lib/adapter/use-cache-remote-handler.d.ts +26 -0
  68. package/lib/adapter/use-cache-remote-handler.js +186 -0
  69. package/lib/{image-optimization/handler.mjs → adapter/use-cache-remote-handler.mjs} +33572 -38798
  70. package/lib/generated-structs/OptionalNextjsApiProps.d.ts +59 -0
  71. package/lib/generated-structs/OptionalNextjsApiProps.js +3 -0
  72. package/lib/generated-structs/OptionalNextjsCacheProps.d.ts +15 -6
  73. package/lib/generated-structs/OptionalNextjsCacheProps.js +1 -1
  74. package/lib/generated-structs/OptionalNextjsContainersProps.d.ts +25 -11
  75. package/lib/generated-structs/OptionalNextjsContainersProps.js +1 -1
  76. package/lib/generated-structs/OptionalNextjsDistributionProps.d.ts +31 -9
  77. package/lib/generated-structs/OptionalNextjsDistributionProps.js +1 -1
  78. package/lib/generated-structs/OptionalNextjsPostDeployProps.d.ts +8 -0
  79. package/lib/generated-structs/OptionalNextjsPostDeployProps.js +1 -1
  80. package/lib/generated-structs/OptionalPostDeployCustomResourceProperties.d.ts +6 -4
  81. package/lib/generated-structs/OptionalPostDeployCustomResourceProperties.js +1 -1
  82. package/lib/index.d.ts +6 -7
  83. package/lib/index.js +2 -4
  84. package/lib/lambdas/post-deploy/prune-cache-bucket.d.ts +7 -3
  85. package/lib/lambdas/post-deploy/prune-cache-bucket.js +57 -71
  86. package/lib/lambdas/post-deploy/prune-cache-bucket.test.d.ts +1 -0
  87. package/lib/lambdas/post-deploy/prune-cache-bucket.test.js +114 -0
  88. package/lib/lambdas/post-deploy/prune-revalidation-table.d.ts +1 -0
  89. package/lib/lambdas/post-deploy/prune-revalidation-table.js +23 -31
  90. package/lib/lambdas/post-deploy/prune-revalidation-table.test.d.ts +1 -0
  91. package/lib/lambdas/post-deploy/prune-revalidation-table.test.js +71 -0
  92. package/lib/lambdas/post-deploy/prune-s3.d.ts +14 -5
  93. package/lib/lambdas/post-deploy/prune-s3.js +41 -12
  94. package/lib/lambdas/post-deploy/prune-s3.test.js +97 -18
  95. package/lib/lambdas/redeploy-stage/redeploy-stage-function.d.ts +13 -0
  96. package/lib/lambdas/redeploy-stage/redeploy-stage-function.js +55 -0
  97. package/lib/lambdas/redeploy-stage/redeploy-stage.d.ts +24 -0
  98. package/lib/lambdas/redeploy-stage/redeploy-stage.js +68 -0
  99. package/lib/lambdas/redeploy-stage/redeploy-stage.lambda.d.ts +20 -0
  100. package/lib/lambdas/redeploy-stage/redeploy-stage.lambda.js +24 -0
  101. package/lib/lambdas/redeploy-stage/redeploy-stage.test.d.ts +1 -0
  102. package/lib/lambdas/redeploy-stage/redeploy-stage.test.js +93 -0
  103. package/lib/nextjs-api.d.ts +102 -18
  104. package/lib/nextjs-api.js +338 -38
  105. package/lib/nextjs-api.test.js +537 -1
  106. package/lib/nextjs-build/global-containers.Dockerfile +15 -10
  107. package/lib/nextjs-build/nextjs-build.d.ts +339 -63
  108. package/lib/nextjs-build/nextjs-build.js +684 -327
  109. package/lib/nextjs-build/nextjs-build.test.d.ts +1 -0
  110. package/lib/nextjs-build/nextjs-build.test.js +412 -0
  111. package/lib/nextjs-build/patch-fetch.js +1 -1
  112. package/lib/nextjs-build/patch-fetch.test.js +166 -11
  113. package/lib/nextjs-build/regional-containers.Dockerfile +17 -10
  114. package/lib/nextjs-cache.d.ts +82 -4
  115. package/lib/nextjs-cache.js +186 -10
  116. package/lib/nextjs-cache.test.js +176 -1
  117. package/lib/nextjs-compute/nextjs-compute-base-props.d.ts +21 -8
  118. package/lib/nextjs-compute/nextjs-compute-base-props.js +41 -1
  119. package/lib/nextjs-compute/nextjs-compute-base-props.test.d.ts +1 -0
  120. package/lib/nextjs-compute/nextjs-compute-base-props.test.js +44 -0
  121. package/lib/nextjs-compute/nextjs-containers.d.ts +19 -0
  122. package/lib/nextjs-compute/nextjs-containers.js +57 -20
  123. package/lib/nextjs-compute/nextjs-functions.d.ts +122 -7
  124. package/lib/nextjs-compute/nextjs-functions.js +113 -67
  125. package/lib/nextjs-compute/nextjs-functions.test.d.ts +1 -0
  126. package/lib/nextjs-compute/nextjs-functions.test.js +99 -0
  127. package/lib/nextjs-distribution.d.ts +194 -23
  128. package/lib/nextjs-distribution.js +535 -62
  129. package/lib/nextjs-distribution.test.d.ts +1 -0
  130. package/lib/nextjs-distribution.test.js +697 -0
  131. package/lib/nextjs-post-deploy.d.ts +13 -5
  132. package/lib/nextjs-post-deploy.js +13 -10
  133. package/lib/nextjs-post-deploy.test.d.ts +1 -0
  134. package/lib/nextjs-post-deploy.test.js +34 -0
  135. package/lib/nextjs-static-assets.d.ts +17 -4
  136. package/lib/nextjs-static-assets.js +22 -5
  137. package/lib/root-constructs/nextjs-base-construct.d.ts +102 -58
  138. package/lib/root-constructs/nextjs-base-construct.js +139 -47
  139. package/lib/root-constructs/nextjs-global-containers.d.ts +18 -20
  140. package/lib/root-constructs/nextjs-global-containers.js +20 -71
  141. package/lib/root-constructs/nextjs-global-functions.d.ts +26 -28
  142. package/lib/root-constructs/nextjs-global-functions.js +52 -70
  143. package/lib/root-constructs/nextjs-regional-containers.d.ts +18 -5
  144. package/lib/root-constructs/nextjs-regional-containers.js +8 -15
  145. package/lib/root-constructs/nextjs-regional-functions.d.ts +22 -18
  146. package/lib/root-constructs/nextjs-regional-functions.js +8 -26
  147. package/lib/runtime/api-gateway-path.d.ts +34 -0
  148. package/lib/runtime/api-gateway-path.js +71 -0
  149. package/lib/runtime/api-gateway-path.test.d.ts +1 -0
  150. package/lib/runtime/api-gateway-path.test.js +65 -0
  151. package/lib/runtime/case-redirect.d.ts +22 -0
  152. package/lib/runtime/case-redirect.js +63 -0
  153. package/lib/runtime/case-redirect.test.d.ts +1 -0
  154. package/lib/runtime/case-redirect.test.js +32 -0
  155. package/lib/runtime/core.d.ts +234 -0
  156. package/lib/runtime/core.js +1216 -0
  157. package/lib/runtime/core.test.d.ts +1 -0
  158. package/lib/runtime/core.test.js +1686 -0
  159. package/lib/runtime/deployment-root.d.ts +13 -0
  160. package/lib/runtime/deployment-root.js +29 -0
  161. package/lib/runtime/dispatch.d.ts +331 -0
  162. package/lib/runtime/dispatch.js +751 -0
  163. package/lib/runtime/dispatch.test.d.ts +1 -0
  164. package/lib/runtime/dispatch.test.js +853 -0
  165. package/lib/runtime/entrypoints.d.ts +57 -0
  166. package/lib/runtime/entrypoints.js +115 -0
  167. package/lib/runtime/http/abort-signal.d.ts +6 -0
  168. package/lib/runtime/http/abort-signal.js +19 -0
  169. package/lib/runtime/http/abort-signal.test.d.ts +1 -0
  170. package/lib/runtime/http/abort-signal.test.js +49 -0
  171. package/lib/runtime/http/node-server.d.ts +38 -0
  172. package/lib/runtime/http/node-server.js +99 -0
  173. package/lib/runtime/http/node-server.test.d.ts +1 -0
  174. package/lib/runtime/http/node-server.test.js +96 -0
  175. package/lib/runtime/http/node-sink.d.ts +16 -0
  176. package/lib/runtime/http/node-sink.js +48 -0
  177. package/lib/runtime/http/node-sink.test.d.ts +1 -0
  178. package/lib/runtime/http/node-sink.test.js +66 -0
  179. package/lib/runtime/http/request.d.ts +62 -0
  180. package/lib/runtime/http/request.js +134 -0
  181. package/lib/runtime/http/request.test.d.ts +1 -0
  182. package/lib/runtime/http/request.test.js +103 -0
  183. package/lib/runtime/http/response.d.ts +77 -0
  184. package/lib/runtime/http/response.js +229 -0
  185. package/lib/runtime/http/response.test.d.ts +1 -0
  186. package/lib/runtime/http/response.test.js +178 -0
  187. package/lib/runtime/http/sink.d.ts +73 -0
  188. package/lib/runtime/http/sink.js +188 -0
  189. package/lib/runtime/http/sink.test.d.ts +1 -0
  190. package/lib/runtime/http/sink.test.js +257 -0
  191. package/lib/runtime/image-utils.d.ts +78 -0
  192. package/lib/runtime/image-utils.js +129 -0
  193. package/lib/runtime/image-utils.test.d.ts +1 -0
  194. package/lib/runtime/image-utils.test.js +237 -0
  195. package/lib/runtime/image.d.ts +76 -0
  196. package/lib/runtime/image.js +296 -0
  197. package/lib/runtime/image.test.d.ts +1 -0
  198. package/lib/runtime/image.test.js +405 -0
  199. package/lib/runtime/lambda-event.d.ts +4 -0
  200. package/lib/runtime/lambda-event.js +113 -0
  201. package/lib/runtime/lambda-event.test.d.ts +1 -0
  202. package/lib/runtime/lambda-event.test.js +119 -0
  203. package/lib/runtime/lambda.mjs +41600 -0
  204. package/lib/runtime/load-module.d.ts +8 -0
  205. package/lib/runtime/load-module.js +35 -0
  206. package/lib/runtime/manifest.d.ts +215 -0
  207. package/lib/runtime/manifest.js +118 -0
  208. package/lib/runtime/middleware.d.ts +43 -0
  209. package/lib/runtime/middleware.js +113 -0
  210. package/lib/runtime/middleware.test.d.ts +1 -0
  211. package/lib/runtime/middleware.test.js +259 -0
  212. package/lib/runtime/next-modules.d.ts +71 -0
  213. package/lib/runtime/next-modules.js +144 -0
  214. package/lib/runtime/next-modules.test.d.ts +1 -0
  215. package/lib/runtime/next-modules.test.js +34 -0
  216. package/lib/runtime/public-files.d.ts +32 -0
  217. package/lib/runtime/public-files.js +117 -0
  218. package/lib/runtime/public-files.test.d.ts +1 -0
  219. package/lib/runtime/public-files.test.js +97 -0
  220. package/lib/runtime/server.mjs +41593 -0
  221. package/lib/runtime/static-files.d.ts +50 -0
  222. package/lib/runtime/static-files.js +266 -0
  223. package/lib/runtime/static-files.test.d.ts +1 -0
  224. package/lib/runtime/static-files.test.js +118 -0
  225. package/lib/runtime/util.d.ts +28 -0
  226. package/lib/runtime/util.js +79 -0
  227. package/lib/utils/base-path.d.ts +106 -13
  228. package/lib/utils/base-path.js +254 -41
  229. package/lib/utils/base-path.test.js +219 -51
  230. package/lib/utils/get-architecture.d.ts +7 -8
  231. package/lib/utils/get-architecture.js +12 -13
  232. package/lib/utils/zip-directory.d.ts +14 -0
  233. package/lib/utils/zip-directory.js +121 -0
  234. package/lib/utils/zip-directory.test.d.ts +1 -0
  235. package/lib/utils/zip-directory.test.js +99 -0
  236. package/package.json +22 -3
  237. package/scripts/capture-adapter-fixture.mjs +325 -0
  238. package/scripts/e2e-deploy.sh +398 -0
  239. package/scripts/e2e-harness/README.md +707 -0
  240. package/scripts/e2e-harness/app.js +370 -0
  241. package/scripts/e2e-harness/common.sh +249 -0
  242. package/scripts/e2e-harness/screen.mjs +302 -0
  243. package/scripts/e2e-harness/stage-proxy.mjs +145 -0
  244. package/scripts/e2e-harness/type-filters.mjs +47 -0
  245. package/scripts/e2e-logs.sh +75 -0
  246. package/scripts/e2e-offline.sh +91 -0
  247. package/scripts/e2e-sweep.sh +370 -0
  248. package/scripts/e2e-warm.sh +114 -0
  249. package/scripts/zero-config-build.mjs +418 -0
  250. package/docs/1k-concurrent-users-executions.png +0 -0
  251. package/docs/plans/adapter-runtime-release.md +0 -362
  252. package/docs/plans/phase-0-image-function-opt-in.md +0 -295
  253. package/lib/generated-structs/OptionalDockerImageFunctionProps.d.ts +0 -425
  254. package/lib/generated-structs/OptionalDockerImageFunctionProps.js +0 -3
  255. package/lib/image-optimization/handler-utils.d.ts +0 -34
  256. package/lib/image-optimization/handler-utils.js +0 -83
  257. package/lib/image-optimization/handler-utils.test.js +0 -178
  258. package/lib/nextjs-build/functions.Dockerfile +0 -31
  259. package/lib/nextjs-compute/nextjs-image-function.d.ts +0 -53
  260. package/lib/nextjs-compute/nextjs-image-function.js +0 -59
  261. package/lib/utils/experimental-flags.d.ts +0 -24
  262. package/lib/utils/experimental-flags.js +0 -30
  263. /package/lib/{image-optimization/handler-utils.test.d.ts → adapter/adapter.test.d.ts} +0 -0
package/README.md CHANGED
@@ -29,7 +29,7 @@ Deploy [Next.js](https://nextjs.org/) apps on [AWS](https://aws.amazon.com/) wit
29
29
  - Global Content Delivery Network (CDN) built with [Amazon CloudFront](https://aws.amazon.com/cloudfront/) to deliver content with low latency and high transfer speeds.
30
30
  - Serverless functions powered by [AWS Lambda](https://aws.amazon.com/lambda/) or serverless containers powered by [AWS Fargate](https://aws.amazon.com/fargate/).
31
31
  - Static assets (JS, CSS, public folder) are stored and served from [Amazon Simple Storage Service (S3)](https://aws.amazon.com/s3/) for all constructs (except `NextjsRegionalContainers`) to decrease latency and reduce compute costs by serving directly from S3.
32
- - [Optimized images](https://nextjs.org/docs/pages/building-your-application/optimizing/images), [data cache](https://nextjs.org/docs/app/building-your-application/caching#data-cache), and [full route cache](https://nextjs.org/docs/app/building-your-application/caching#full-route-cache) are shared across compute with [Amazon Simple Storage Service (S3)](https://aws.amazon.com/s3/) with supporting metadata in [Amazon DynamoDB](https://aws.amazon.com/dynamodb).
32
+ - [Optimized images](https://nextjs.org/docs/pages/building-your-application/optimizing/images), [data cache](https://nextjs.org/docs/app/building-your-application/caching#data-cache), [full route cache](https://nextjs.org/docs/app/building-your-application/caching#full-route-cache) and [`'use cache: remote'`](https://nextjs.org/docs/app/api-reference/directives/use-cache-remote) are shared across compute with [Amazon Simple Storage Service (S3)](https://aws.amazon.com/s3/) with supporting metadata in [Amazon DynamoDB](https://aws.amazon.com/dynamodb), and `revalidateTag`/`revalidatePath` reach plain [`'use cache'`](https://nextjs.org/docs/app/api-reference/directives/use-cache) on every instance. See [`'use cache'` and `'use cache: remote'`](#use-cache-and-use-cache-remote).
33
33
  - Customize every construct via `overrides`.
34
34
  - AWS security and operational best practices are utilized, guided by [cdk-nag](https://github.com/cdklabs/cdk-nag).
35
35
  - First class support for [monorepos](https://monorepo.tools/).
@@ -38,34 +38,45 @@ Deploy [Next.js](https://nextjs.org/) apps on [AWS](https://aws.amazon.com/) wit
38
38
 
39
39
  ## Prerequisites
40
40
 
41
- - Next.js app running v16.2 or higher. If you don't have one yet - follow [these steps](https://nextjs.org/docs/getting-started) to create one.
41
+ - Next.js app running v16.3 or higher (`next build` fails on older versions). If you don't have one yet - follow [these steps](https://nextjs.org/docs/getting-started) to create one.
42
42
  - [AWS Cloud Development Kit](https://docs.aws.amazon.com/cdk/v2/guide/home.html) app either in the same package or separate package. cdk-nextjs supports monorepos.
43
- - Docker compatible container engine - we recommend [Rancher Desktop](https://rancherdesktop.io/) with dockerd (moby).
43
+ - A Docker compatible container engine, but **only** for `NextjsGlobalContainers` and `NextjsRegionalContainers` - we recommend [Rancher Desktop](https://rancherdesktop.io/) with dockerd (moby). The two Functions types deploy zip Lambdas and need no container engine.
44
44
  - [Node.js](https://nodejs.org/en) v24 (or LTS)
45
45
 
46
46
  ## Getting Started
47
47
 
48
48
  1. Install `cdk-nextjs` in the package(s) containing your CDK and Next.js app with `npm i cdk-nextjs`
49
- 2. Update next.config.ts to include `adapterPath`:
49
+ 2. Deploy your Next.js app to AWS: `cdk deploy`. Make sure you have [AWS credentials](https://docs.aws.amazon.com/cli/v1/userguide/cli-chap-configure.html) configured.
50
+ 3. Visit URL printed in terminal (CloudFormation Output) to view your Next.js app!
51
+
52
+ No `next.config` change is needed: cdk-nextjs runs `next build` itself, and sets [`NEXT_ADAPTER_PATH`](https://nextjs.org/docs/app/api-reference/config/next-config-js/adapterPath) on that build so Next.js loads cdk-nextjs's [Deployment Adapter](https://nextjs.org/docs/app/guides/deployment-adapters).
53
+
54
+ ### Registering the adapter yourself
55
+
56
+ Set `adapterPath` in `next.config` in three cases. An explicit `adapterPath` always wins over `NEXT_ADAPTER_PATH`, so this is also how you pin a specific adapter.
57
+
58
+ - **cdk-nextjs isn't running your build** — `skipBuild: true`, or you build in CI and hand the output to CDK.
59
+ - **cdk-nextjs isn't a dependency of the Next.js app**, only of the CDK app. cdk-nextjs resolves the adapter from your app's directory, so it can't find it in that layout.
60
+ - **cdk-nextjs is linked from outside your project root** — a `link:`/`file:` dependency on a checkout elsewhere on disk, as this repo's own examples use. The symlink resolves out of the project, and the adapter derives its cache handler path from its own location, so Next.js gets a `cacheHandler` outside `turbopack.root` and Turbopack rejects it. Setting `adapterPath` fixes this because the build resolves it, after whatever put the adapter in place.
50
61
 
51
62
  ```ts
52
63
  import { NextConfig } from "next";
53
64
 
54
65
  const nextConfig: NextConfig = {
55
66
  // ...
56
- adapterPath: import.meta.resolve("cdk-nextjs/adapter"), // for ESM
57
- // adapterPath: require.resolve("cdk-nextjs/adapter"), // for CJS
67
+ adapterPath: require.resolve("cdk-nextjs/adapter"),
58
68
  };
59
69
 
60
70
  export default nextConfig;
61
71
  ```
62
72
 
63
- 3. Deploy your Next.js app to AWS: `cdk deploy`. Make sure you have [AWS credentials](https://docs.aws.amazon.com/cli/v1/userguide/cli-chap-configure.html) configured.
64
- 4. Visit URL printed in terminal (CloudFormation Output) to view your Next.js app!
73
+ `require.resolve` works in an ESM `next.config.ts` too, because Next.js transpiles the config to CJS before evaluating it.
65
74
 
66
75
  ### cdk-nextjs Created Dockerfile
67
76
 
68
- cdk-nextjs will generate a Dockerfile in your Next.js app's directory. cdk-nextjs does not have the ability to clean up the Dockerfile after the container is built so you can either add it to source control or gitignore it. When detectd, cdk-nextjs will not overwrite this Dockerfile so you're free to customize it.
77
+ For the Containers types, cdk-nextjs generates a Dockerfile in your Next.js app's directory. It cannot clean the file up after the image is built, so either commit it or gitignore it.
78
+
79
+ Every generated Dockerfile starts with the line `# ~~ Generated by cdk-nextjs ~~`, and cdk-nextjs **overwrites** any file carrying it — that way an upgrade that changes how the image is built does not leave a stale Dockerfile behind. To customize it, delete that first line; cdk-nextjs then leaves the file alone. If you edited a generated file without deleting the header, cdk-nextjs saves your version beside it as `<name>.Dockerfile.bak` before replacing it; gitignore that too.
69
80
 
70
81
  ## Basic Example CDK App
71
82
 
@@ -80,7 +91,6 @@ class WebStack extends Stack {
80
91
  super(scope, id, props);
81
92
  new NextjsGlobalFunctions(this, "Nextjs", {
82
93
  buildDirectory: join(import.meta.dirname),
83
- healthCheckPath: "/api/health",
84
94
  });
85
95
  }
86
96
  }
@@ -94,7 +104,27 @@ See [examples/](./examples/) for more usage examples.
94
104
 
95
105
  ## Configuration
96
106
 
97
- cdk-nextjs supports configuration via environment variables. These are automatically set by the construct during deployment, but you can override them for custom behavior.
107
+ The variables below are read at runtime by the cache handler inside your deployed
108
+ Lambda functions or containers. Set them on the compute through `overrides`:
109
+
110
+ ```ts
111
+ // NextjsGlobalFunctions / NextjsRegionalFunctions
112
+ overrides: {
113
+ nextjsFunctions: {
114
+ functionProps: { environment: { CDK_NEXTJS_MEMORY_CACHE_TTL_MS: "0" } },
115
+ },
116
+ },
117
+
118
+ // NextjsGlobalContainers / NextjsRegionalContainers
119
+ overrides: {
120
+ nextjsContainers: {
121
+ taskImageOptions: { environment: { CDK_NEXTJS_MEMORY_CACHE_TTL_MS: "0" } },
122
+ },
123
+ },
124
+ ```
125
+
126
+ Values you pass are merged over the ones the construct sets, so the same mechanism
127
+ overrides the [infrastructure variables](#infrastructure-configuration) too.
98
128
 
99
129
  ### Cache Configuration
100
130
 
@@ -102,7 +132,7 @@ cdk-nextjs supports configuration via environment variables. These are automatic
102
132
 
103
133
  Time to live in milliseconds for in-memory cache entries. After this duration, entries expire and are removed from the cache.
104
134
 
105
- **Important**: Due to the distributed nature of compute instances (Lambda functions, ECS Fargate containers, etc.), the memory cache by default provides eventual consistency across instances. Tag revalidations will clear the cache on the instance that processes the revalidation, but other instances may serve stale data until their cache entries expire. If you require strong consistency, set this to `0` to disable the memory cache (all requests will fall through to S3 + DynamoDB).
135
+ **Note**: Every memory hit is checked against the tag revalidation markers, so an entry revalidated on another instance is not served from memory. A tag's marker is read from DynamoDB only on its first use and a rolling re-read every few minutes; in between, revalidations arrive through the revalidation log (one `Query` per [`CDK_NEXTJS_TAG_REFRESH_MS`](#cdk_nextjs_tag_refresh_ms)). Memory still saves the S3 read and body parse. Set this to `0` to disable the memory cache entirely (all requests will fall through to S3 + DynamoDB).
106
136
 
107
137
  **Default**: `3600000` (1 hour)
108
138
 
@@ -136,9 +166,80 @@ Maximum number of cache entries to store in memory. When this limit is reached,
136
166
 
137
167
  **Memory considerations**: Each entry stores the full cache value (HTML, JSON, etc.). A typical page cache might be 10-100KB, so 1000 entries ≈ 10-100MB of memory. Consider your compute environment's memory limits (Lambda: 128MB-10GB, Fargate: 512MB-30GB) and size accordingly.
138
168
 
169
+ ### `'use cache'` and `'use cache: remote'`
170
+
171
+ The adapter registers two [`cacheHandlers`](https://nextjs.org/docs/app/api-reference/config/next-config-js/cacheHandlers)
172
+ next to the `cacheHandler` it already sets, unless your `next.config` names a
173
+ handler of its own for either:
174
+
175
+ | Directive | `cacheHandlers` name | Stored | Tags |
176
+ | --------------------- | -------------------- | -------------------------------- | ----------------------- |
177
+ | `'use cache'` | `default` | each instance's memory | DynamoDB, all instances |
178
+ | `'use cache: remote'` | `remote` | S3 cache bucket, memory in front | DynamoDB, all instances |
179
+
180
+ - **`'use cache: remote'`** is shared: an entry any instance generates is the one
181
+ every instance serves, until it expires or a tag is revalidated. Use it for
182
+ results you don't want each Lambda instance or task to compute on its own. It
183
+ costs an S3 `GetObject` the first time an instance reads an entry, and a
184
+ `PutObject` per generated entry. Objects live under
185
+ `<buildId>/_use-cache/` in the cache bucket and are pruned with the build.
186
+ - **`'use cache'`** stays per instance, as Next.js intends — but `revalidateTag`,
187
+ `updateTag` and `revalidatePath` now expire it on every instance, not only the
188
+ one that ran them. Next.js's built-in handler keeps tags in process memory, so
189
+ before this every other instance kept serving the revalidated value, even into
190
+ pages re-rendered because of that revalidation.
191
+
192
+ Both read the same per-tag marker rows in the revalidation table that ISR and
193
+ the data cache use. A tag an instance has not seen yet — an entry read from S3,
194
+ a page's implicit `revalidatePath` tags — is read from its marker before it is
195
+ trusted, once per instance.
196
+
197
+ After that, an instance doesn't re-read the tags it tracks to learn what
198
+ changed. Each `revalidateTag`/`updateTag` also writes a row to a revalidation
199
+ log in the same table (`pk = <buildId>#log`, expiring after 15 minutes through
200
+ the table's `ttl` attribute). At most once per
201
+ [`CDK_NEXTJS_TAG_REFRESH_MS`](#cdk_nextjs_tag_refresh_ms),
202
+ each instance sends one DynamoDB `Query` for the rows written since its last
203
+ one and applies those for tags it tracks. So a revalidation on another instance
204
+ is honored within that window (1 second by default), and on the instance that ran
205
+ it immediately. An idle instance's query costs 0.5 RCU a second, where
206
+ re-reading up to 1000 tracked tags cost about 500. Each tag's marker is still
207
+ re-read every 7.5 to 10 minutes, a few each second rather than all at once (about 8
208
+ RCU a second at the most an instance tracks, 10,000 tags). After a gap the log
209
+ may no longer cover (a Lambda frozen between invocations, a run of failed
210
+ queries), an instance forgets its tracked markers and reads each again as it's
211
+ needed. ISR and the data cache share the same copy of the markers, so one
212
+ `revalidateTag` writes each tag's rows once and one `Query` serves them all.
213
+
214
+ If you pass your own `revalidationTable`, enabling TTL on its `ttl` attribute is
215
+ recommended. Without it the log rows (about 100 bytes per revalidated tag) are
216
+ kept until a deploy replaces their build, whose post-deploy step deletes them.
217
+
218
+ Two instances that generate the same `'use cache: remote'` entry at the same
219
+ moment both store it; each serves its own copy until that copy's `revalidate`
220
+ time, after which S3's is used.
221
+
222
+ #### `CDK_NEXTJS_TAG_REFRESH_MS`
223
+
224
+ How often, at most, in milliseconds, an instance asks the revalidation log what
225
+ other instances revalidated: the longest a `revalidateTag` elsewhere goes unseen,
226
+ for `'use cache'`, ISR and the data cache alike. It's one DynamoDB `Query`,
227
+ however many tags the instance tracks, shared by every request on the instance
228
+ during the window. `0` asks before every cache check.
229
+
230
+ **Default**: `1000`
231
+
232
+ #### `CDK_NEXTJS_USE_CACHE_MEMORY_BYTES`
233
+
234
+ Size bound, in bytes, of each handler's in-memory store: all of `'use cache'`,
235
+ and the memory tier in front of S3 for `'use cache: remote'`. Least recently used
236
+ entries are evicted first.
237
+
238
+ **Default**: `52428800` (50 MB, Next.js's own default)
239
+
139
240
  ### Infrastructure Configuration
140
241
 
141
- The following environment variables are automatically set by the construct and typically don't need to be modified:
242
+ The construct sets these itself and they typically don't need to be modified:
142
243
 
143
244
  #### `CDK_NEXTJS_BUILD_ID`
144
245
 
@@ -146,7 +247,7 @@ Unique identifier for the Next.js build. Used for cache isolation between deploy
146
247
 
147
248
  #### `CDK_NEXTJS_CACHE_BUCKET_NAME`
148
249
 
149
- S3 bucket name for storing cached data (optimized images, data cache, full route cache).
250
+ S3 bucket name for storing cached data (optimized images, data cache, full route cache, `'use cache: remote'`).
150
251
 
151
252
  #### `CDK_NEXTJS_REVALIDATION_TABLE_NAME`
152
253
 
@@ -154,10 +255,161 @@ DynamoDB table name for tracking cache revalidations and tag-to-cache-key mappin
154
255
 
155
256
  #### `DEBUG`
156
257
 
157
- Set to `cdk-nextjs:*` to set debug logs. This is especially useful to see cache handler activity.
258
+ Not set by default. Set it to `cdk-nextjs:*` to enable debug logs. This is especially useful to see cache handler activity.
259
+
260
+ ### `next.config.js` options cdk-nextjs reads
261
+
262
+ Both are read out of your build, so you set them in one place — your app — and the
263
+ infrastructure follows.
264
+
265
+ #### `basePath`
266
+
267
+ Becomes the S3 key prefix for static assets and the prefix on every CloudFront
268
+ cache behavior. See [Resource Isolation](#resource-isolation); the `basePath`
269
+ prop only exists to be explicit about it.
270
+
271
+ #### `assetPrefix`
272
+
273
+ - **A path** (`assetPrefix: "/cdn"`) — Next.js emits bundle URLs as
274
+ `/cdn/_next/static/...`, on top of `basePath` rather than under it, while the
275
+ objects stay in S3 under `<basePath>/_next/static/...`. `NextjsGlobalFunctions`
276
+ and `NextjsGlobalContainers` add a cache behavior for
277
+ `<assetPrefix>/_next/static*` pointing at the assets bucket, with a CloudFront
278
+ Function that rewrites the prefix back to the S3 keys. It costs one of the 25
279
+ cache behaviors cdk-nextjs budgets for you. **Not supported on
280
+ `NextjsRegionalFunctions` or `NextjsRegionalContainers`** — nothing there maps
281
+ the prefix back, so bundles would 404; synth warns if your app sets one.
282
+ - **An absolute URL** (`assetPrefix: "https://cdn.example.com"`) — names an origin
283
+ cdk-nextjs doesn't serve, so nothing is added, on any construct. Point that host
284
+ at the assets bucket yourself, including the `basePath` prefix the objects are
285
+ stored under.
286
+ - **An absolute URL with a path** (`assetPrefix: "https://cdn.example.com/cdn"`) —
287
+ its path is treated exactly like the path case above, because `next build`
288
+ compiles a `/cdn/_next/:path+` rewrite of its own: `next start` serves bundles
289
+ under that path too, so the Global constructs answer there as well. Useful when
290
+ that host is a second domain on the same distribution.
291
+
292
+ ## Splitting a Large App Across Functions
293
+
294
+ Only for `NextjsGlobalFunctions` and `NextjsRegionalFunctions`, and only if you
295
+ need it. AWS Lambda caps a function's unzipped code at 250 MB. If your app
296
+ exceeds it, cdk-nextjs fails at synth with the measured size and points you here:
297
+
298
+ ```
299
+ Function group "default" is 274 MB unzipped, over Lambda's 250 MB limit. Use the
300
+ `functionGroups` prop to package routes into separate functions. Note that
301
+ splitting only removes route-local code — anything reachable from a shared layout
302
+ or the `next` runtime is in every group.
303
+ ```
304
+
305
+ `functionGroups` declares which routes get their own Lambda. Everything you do
306
+ not name stays on the `default` function:
307
+
308
+ ```ts
309
+ new NextjsGlobalFunctions(this, "Nextjs", {
310
+ buildDirectory: join(import.meta.dirname, "..", "web"),
311
+ functionGroups: [
312
+ { name: "reports", routes: ["/dashboard/reports/**"] },
313
+ { name: "admin", routes: ["/admin", "/admin/**", "/settings"] },
314
+ ],
315
+ });
316
+ ```
317
+
318
+ Each group becomes one Lambda function containing only the routes it owns (plus
319
+ the framework closure every function needs), and one CloudFront behavior — or API
320
+ Gateway resource, for `NextjsRegionalFunctions` — per pattern.
321
+
322
+ **Route patterns** are either an exact path (`/settings`) or a subtree
323
+ (`/admin/**`). A subtree owns what is _under_ it, not the path itself:
324
+ `/admin/**` does not claim `/admin`. To own a page and everything under it, list
325
+ both, as the example above does: `["/admin", "/admin/**"]`. Where two groups
326
+ could both match, the longest pattern wins, so `/api/**` and `/api/reports/**`
327
+ can coexist in different groups. Dynamic segments (`/blog/[slug]`), route group
328
+ segments (`/(marketing)/about`), `/`, and `/**` are rejected at synth —
329
+ CloudFront matches literal path prefixes and cannot express them. `/index` is
330
+ accepted for an App Router `app/index/page.tsx`, but not for a Pages Router home
331
+ page, which is the same file as `/`.
332
+
333
+ **What is routed for you.** Patterns decide which _files_ a group packages; the
334
+ edge then has to send every URL those files serve to that group. cdk-nextjs
335
+ derives these without extra patterns:
336
+
337
+ - A Pages Router page's data URL, `/_next/data/<buildId>/<page>.json`. The
338
+ behavior carries the literal build ID, so it changes on every deploy; a
339
+ client still on the previous build falls through to the `default` function,
340
+ which answers 404, and the Next.js client reloads the page.
341
+ - The `trailingSlash` form of an exact pattern (`/pricing/`). CloudFront gets a
342
+ behavior for it; on `NextjsRegionalFunctions` none is needed, because API
343
+ Gateway already routes `/pricing/` to the `/pricing` resource.
344
+ - The parent of an optional catch-all. `/shop/**` moving
345
+ `app/shop/[[...slug]]/page.tsx` also routes `/shop`, which the same file
346
+ serves. Do not add `/shop` yourself: it matches no route and is rejected.
347
+ - An [interception route](https://nextjs.org/docs/app/api-reference/file-conventions/intercepting-routes)
348
+ is packaged with the group that owns the URL it intercepts, not by its own
349
+ path: `app/feed/(..)photo/[id]` follows `/photo/[id]`, because a soft
350
+ navigation requests `/photo/1` and Next.js rewrites it in whichever function
351
+ receives it.
352
+
353
+ **What synth rejects.** After assigning routes, cdk-nextjs replays the
354
+ CloudFront behaviors over every URL each file serves and fails when one would
355
+ reach a function without its file, naming the file, the URL and the pattern to
356
+ add. That covers:
357
+
358
+ - A file behind several templates that a pattern only partly covers — root
359
+ params (`app/[locale]/page.tsx` prerendered as `/en` and `/de`) grouped with
360
+ `/en` alone. A dynamic first segment can never be routed, so such a file stays
361
+ in `default`.
362
+ - An intercepted URL space split between groups (`/photo/[id]` in one,
363
+ `/photo/1` in another), since the intercepting file cannot be in both.
364
+ - A `next.config` rewrite whose source reaches one group and whose destination
365
+ file is in another: rewrites run inside the function that received the
366
+ request. Only rewrites with a literal destination are checked.
367
+ - A group pattern that duplicates a top-level `public/` entry's behavior
368
+ (`public/docs/` and `/docs/**`), or sits under one (`/docs/guide/**`), which
369
+ would send the group's routes to S3.
370
+
371
+ Not checked, because it is only known at request time: **a middleware
372
+ `NextResponse.rewrite()`** to a route in another group reaches the function that
373
+ received the original URL, which lacks the route's file. It answers 404 (or a
374
+ 308 to the canonical URL when only the case differs) and logs a warning naming
375
+ the group that owns the route. Keep a middleware rewrite's source and
376
+ destination in the same group. The same goes
377
+ for a `next.config` rewrite whose destination is built from its parameters
378
+ (`/b/:slug` → `/blog/:slug`), and for a dynamic segment of a `default` route
379
+ that overlaps a group's literal path (`/[section]/intro` against `/docs/**`).
380
+ Likewise **a Pages Router `res.revalidate()`** renders the page in the function
381
+ that called it, so it can only revalidate pages in its own group; the error
382
+ names the group that owns the page. Use `revalidatePath()` or `revalidateTag()`
383
+ across groups. And **an `<Image>` whose `src` is a route in another group**
384
+ (`/api/avatar/42` under an `/api/**` group) can't be optimized: `/_next/image`
385
+ runs in the `default` function, which fetches a local source that isn't a
386
+ `public/` or `_next/static` file by running its route in-process, and it doesn't
387
+ carry that route, so the image request fails. Keep image-serving routes in
388
+ `default`, or point `src` at a `public/` file or a remote URL.
389
+
390
+ **What splitting does and does not save.** Every function ships the same `next`
391
+ runtime closure, so splitting only moves route-_local_ code and its dependencies.
392
+ A group whose routes import a large library is worth extracting; splitting an app
393
+ in half does not halve either function.
394
+
395
+ Notes:
396
+
397
+ - Group names must be valid CDK construct ids (letters, digits, `-`). `default`
398
+ is reserved.
399
+ - A pattern that matches no route in the build is a synth error, not a silent
400
+ no-op — it is almost always a typo.
401
+ - Cannot be combined with `i18n`: locale-prefixed routes would need one
402
+ CloudFront behavior per locale per pattern.
403
+ - Group patterns (and the routes derived from them above) count against the
404
+ same CloudFront behavior budget as your `public/` entries (see
405
+ [Limitations](#limitations)).
406
+ - Per-group `overrides` let you size each function independently:
407
+ `{ name: "reports", routes: [...], overrides: { functionProps: { memorySize: 2048 } } }`.
158
408
 
159
409
  ## Architecture
160
410
 
411
+ How requests are served inside the compute — the adapter runtime, and where it deliberately differs from `next start` — is in [docs/adapter-runtime.md](./docs/adapter-runtime.md).
412
+
161
413
  ### `NextjsGlobalFunctions`
162
414
 
163
415
  Architecture includes [AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/welcome.html) Functions to respond to dynamic requests and [CloudFront](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Introduction.html) Distribution to globally serve requests and distribute static assets. Use this construct when you have unpredictable traffic, can afford occasional latency (i.e. cold starts - [typically 1% of production traffic](https://aws.amazon.com/blogs/compute/operating-lambda-performance-optimization-part-1/)), and/or want the most granular pricing model. ([code](./src/root-constructs/nextjs-global-functions.ts))
@@ -258,14 +510,14 @@ The simplest path to deploy Next.js is on [Vercel](https://vercel.com/) - the Pl
258
510
 
259
511
  ## Design Principles
260
512
 
261
- - Treat Next.js as black box. Minimize reliance on Next.js internal APIs to reduce chance of incompatibility between this construct and future versions of Next.js.
513
+ - Integrate through Next.js's official interfaces. Build on the [Deployment Adapter](https://nextjs.org/docs/app/guides/deployment-adapters) API and [`@next/routing`](https://www.npmjs.com/package/@next/routing) rather than Next.js internals, so new Next.js versions rarely break this construct.
262
514
  - Security first.
263
515
  - One architecture does not fit all.
264
516
  - Enable customization everywhere.
265
517
 
266
518
  ## Bring Your Own Resources
267
519
 
268
- cdk-nextjs supports importing existing AWS resources instead of creating new ones. This is especially useful for per-branch (MR/PR) environments where you deploy shared infrastructure once and spin up lightweight branch stacks that reuse it.
520
+ cdk-nextjs supports importing existing AWS resources instead of creating new ones. This is especially useful for per-branch (MR/PR) environments where you deploy shared infrastructure (VPC, ECS cluster, ALB) once and spin up lightweight branch stacks that reuse it.
269
521
 
270
522
  ### Supported Resources
271
523
 
@@ -280,8 +532,8 @@ cdk-nextjs supports importing existing AWS resources instead of creating new one
280
532
 
281
533
  ### Resource Isolation
282
534
 
283
- - **Cache bucket and DynamoDB table** are isolated by `buildId` prefix. Multiple branches safely share one bucket/table with no conflicts.
284
- - **Static assets bucket** — Next.js includes content hashes in static asset filenames, so different branches deploying the same file will produce identical content. It's safe for branches to overwrite each other. If you're already using `basePath` for routing, assets will naturally be prefixed by it — `NextjsGlobalFunctions` and `NextjsGlobalContainers` read the `basePath` out of your Next.js build and use it as the S3 key prefix, since CloudFront serves assets from S3 by request path and the two can't differ. Set the `basePath` prop only if you want to be explicit about it; a value that disagrees with your app's fails at synth.
535
+ - **Cache bucket and DynamoDB table** must be dedicated to one deployment. Sharing either between deployments (branches, stages, apps) is not supported: each deploy's post-deploy step prunes every cache object and revalidation entry that isn't from its own current build, so deploying one branch wipes the others' caches. Buckets and tables are cheap, so give each deployment its own, or leave `cacheBucket`/`revalidationTable` unset and let cdk-nextjs create them (they are removed with the stack).
536
+ - **Static assets bucket** can be shared, but only between deployments with different `basePath`s, which become their S3 key prefixes. Pruning only looks at `<basePath>/_next/`, so it never touches another prefix. Two deployments under the same prefix (for example two subdomain previews both at the root) overwrite each other's `public/` files and prune each other's `_next/` assets once they are past the prune TTL (30 days by default), so give those their own buckets. `NextjsGlobalFunctions` and `NextjsGlobalContainers` read the `basePath` out of your Next.js build and use it as the S3 key prefix, since CloudFront serves assets from S3 by request path and the two can't differ. Set the `basePath` prop only if you want to be explicit about it; a value that disagrees with your app's fails at synth.
285
537
 
286
538
  ### Shared ALB and `removeAutoCreatedListener()`
287
539
 
@@ -298,13 +550,15 @@ nextjs.nextjsContainers.removeAutoCreatedListener();
298
550
 
299
551
  ### Examples
300
552
 
301
- See [examples/bring-your-own/](./examples/bring-your-own/) for a complete deployable example with shared infrastructure and per-branch host-header routing.
553
+ See [examples/bring-your-own/](./examples/bring-your-own/) for a complete deployable example with a shared VPC, ECS cluster, and ALB, per-branch host-header routing, and a cache bucket, revalidation table, and static assets bucket per branch.
302
554
 
303
555
  ## Preview Environments (Per-Branch Deployments)
304
556
 
305
557
  cdk-nextjs can deploy ephemeral preview environments per merge request (MR) or pull request (PR). The recommended approach uses subdomain-based routing (`pr-123.app.example.com`) so each preview environment runs the same Next.js build as production — no `basePath` configuration or separate builds required.
306
558
 
307
- See [examples/bring-your-own/](./examples/bring-your-own/) for a fully deployable example using `NextjsRegionalContainers` with a shared ALB, ECS Cluster, S3 buckets, and DynamoDB table — connected via SSM Parameter Store.
559
+ See [examples/bring-your-own/](./examples/bring-your-own/) for a fully deployable example using `NextjsRegionalContainers` with a shared VPC, ALB, and ECS Cluster (connected via SSM Parameter Store), and S3 buckets and a DynamoDB table owned by each branch stack.
560
+
561
+ Each preview environment needs its own cache bucket and revalidation table (see [Resource Isolation](#resource-isolation)). Since every preview serves the same build at the root, give each its own static assets bucket too. Leaving `cacheBucket`, `revalidationTable`, and `staticAssetsBucket` unset does this: cdk-nextjs creates them in the branch stack and deletes them when it's torn down.
308
562
 
309
563
  ### Prerequisites
310
564
 
@@ -323,29 +577,31 @@ See [examples/bring-your-own/](./examples/bring-your-own/) for the full implemen
323
577
 
324
578
  Subdomain routing via API Gateway custom domain mappings.
325
579
 
326
- 1. Deploy shared infrastructure once: S3 buckets, DynamoDB table
327
- 2. Per branch, deploy a cdk-nextjs stack that imports shared resources and creates its own API Gateway
328
- 3. Create an API Gateway custom domain (`pr-123.app.example.com`) mapped to the branch's API stage
329
- 4. Tear down the branch stack on MR close
580
+ 1. Per branch, deploy a cdk-nextjs stack that creates its own API Gateway, Lambda function, S3 buckets, and DynamoDB table
581
+ 2. Create an API Gateway custom domain (`pr-123.app.example.com`) mapped to the branch's API stage
582
+ 3. Tear down the branch stack on MR close
330
583
 
331
584
  #### `NextjsGlobalFunctions` (Lambda + CloudFront)
332
585
 
333
586
  Requires a separate cdk-nextjs stack per branch, each with its own CloudFront distribution. CloudFront cannot route to different Lambda Function URL origins based on the `Host` header — origin selection is determined by cache behavior path patterns, not request headers.
334
587
 
335
- 1. Deploy shared infrastructure once: S3 buckets, DynamoDB table
336
- 2. Per branch, deploy a full cdk-nextjs stack that imports shared resources (`cacheBucket`, `revalidationTable`, `staticAssetsBucket`) but creates its own CloudFront distribution and Lambda function
337
- 3. Point `pr-123.app.example.com` DNS to the branch's CloudFront distribution
338
- 4. Tear down the branch stack on MR close
588
+ 1. Per branch, deploy a full cdk-nextjs stack that creates its own CloudFront distribution, Lambda function, S3 buckets, and DynamoDB table
589
+ 2. Point `pr-123.app.example.com` DNS to the branch's CloudFront distribution
590
+ 3. Tear down the branch stack on MR close
339
591
 
340
592
  Note: CloudFront distributions take several minutes to create/update, so this architecture has the slowest preview environment spin-up time.
341
593
 
342
594
  ## Limitations
343
595
 
344
- - If using `NextjsGlobalFunctions` or `NextjsGlobalContainers` (which use CloudFront), the number of top level files/directories cannot exceed 25, the max number of behaviors a CloudFront Distribution supports. We recommend you put all of your public assets into one top level directory (i.e. public/static) so you don't reach this limit. See [CloudFront Quotas](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html) for more information.
345
- - If using `NextjsGlobalFunctions`, when [revalidating data in Next.js](https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#on-demand-revalidation) (i.e. [revalidatePath](https://nextjs.org/docs/app/api-reference/functions/revalidatePath)), the CloudFront Cache will still hold stale data. You'll need to use AWS SDK JS V3 [CreateInvalidationCommand](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-client-cloudfront/Class/CreateInvalidationCommand/) to manually invalidate the path in CloudFront. See more [here](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html).
346
- - If using `NextjsGlobalFunctions`, setting an Authorization header won't work by default because of Lambda Function URL with IAM Auth is already using the Authorization header. You can use the `AWS_LWA_AUTHORIZATION_SOURCE` environment variable of [AWS Lambda Web Adapter](https://github.com/awslabs/aws-lambda-web-adapter) to set an alternative Authorization header in the client which will then be set to the Authorization header when it reaches your app.
347
- - `NextjsRegionalFunctions` streaming depends on whether your server Lambda supports response streaming (e.g. [AWS Lambda Web Adapter](https://github.com/awslabs/aws-lambda-web-adapter)'s `AWS_LWA_INVOKE_MODE`) — override `overrides.nextjsApi.dynamicIntegrationProps.responseTransferMode` to `BUFFERED` if it doesn't, otherwise API Gateway returns a 500 for every request.
348
- - If using `NextjsRegionalFunctions` without a custom domain, API Gateway REST APIs require a [stage name](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-stages.html) (default: `/prod`) to be specified. This causes links to pages and static assets to break because they're not prefixed with the stage name. You can work around this issue by specifying [basePath](https://nextjs.org/docs/app/api-reference/config/next-config-js/basePath) in next.config.js as your stage name — leave the construct's `basePath` prop unset when you do, since API Gateway strips the stage before matching resources. Additionally, you'll need to add middleware logic to rewrite requests to include the stage name because API Gateway does not include the stage name in the path passed to Lambda. See [examples/app-playground/middleware.ts](./examples/app-playground/middleware.ts). Mapping a custom domain at the root avoids all of this — see [examples/regional-functions/README.md](./examples/regional-functions/README.md#with-a-custom-domain-no-stage-workarounds).
596
+ - If using `NextjsGlobalFunctions` or `NextjsGlobalContainers` (which use CloudFront), each top level file/directory in `public/` takes a CloudFront cache behavior, and a distribution's default quota is 75 behaviors (cdk-nextjs uses 3 to 6 itself). We recommend you put all of your public assets into one top level directory (i.e. public/static) so you don't reach this limit. If you raise the quota for your account, set `maxCacheBehaviors` on the distribution props (`overrides.nextjsGlobalFunctions.nextjsDistributionProps`, or the Containers equivalent) to match. The synth check only counts the behaviors cdk-nextjs adds, so if you pass your own `distribution` (or add behaviors through `overrides.nextjsDistribution.distributionProps`), lower `maxCacheBehaviors` by the behaviors it already has. See [CloudFront Quotas](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html) for more information.
597
+ - If using `NextjsGlobalFunctions` or `NextjsGlobalContainers`, on-demand revalidation ([revalidatePath](https://nextjs.org/docs/app/api-reference/functions/revalidatePath), [revalidateTag](https://nextjs.org/docs/app/api-reference/functions/revalidateTag)) creates the CloudFront invalidations for you (so does the Pages Router's `res.revalidate()`, for the page's HTML and `_next/data` copies), but [invalidations](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html) are eventually consistent: until one completes (usually seconds, occasionally minutes), some edge locations can still serve the previous copy.
598
+ - **Burst limit.** CloudFront allows 15 wildcard invalidation paths in progress per distribution, and each page cdk-nextjs invalidates is one wildcard path (`/blog*`). Next.js sends every `revalidatePath`/`revalidateTag` from one request as a single call, one invalidation per cache-life profile, however many pages it covers. A Pages Router `res.revalidate()` sends one invalidation per call, and a page regenerated after a stale-while-revalidate `revalidateTag(tag, profile)` sends one more when it lands. When the quota is full, cdk-nextjs retries the request as a single `/*` (a colder edge, never a stale page). If that is also rejected, it logs `Failed to create CloudFront invalidation` and the page stays stale at the edge until its `s-maxage` expires.
599
+ - **For bulk updates** (a CMS webhook touching many pages), call `revalidateTag` with a tag the pages share, or `revalidatePath` for each page within one request, rather than looping `res.revalidate()`. If you need to know when invalidations are dropped, add a CloudWatch Logs metric filter on that log line.
600
+ - If using `NextjsGlobalFunctions`, a client-supplied `Authorization` header does not reach your app: CloudFront signs each request to the Lambda Function URL with SigV4, which uses that header. Send credentials under a different name (e.g. `x-authorization`) and read that header in your app. cdk-nextjs does not rename it for you — it no longer uses [AWS Lambda Web Adapter](https://github.com/awslabs/aws-lambda-web-adapter), so `AWS_LWA_AUTHORIZATION_SOURCE` no longer applies.
601
+ - If using `NextjsGlobalFunctions`, a `POST` or `PUT` with a request body must carry an `x-amz-content-sha256` header holding the hex SHA-256 of that body. This is [AWS behavior](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-lambda.html), not a cdk-nextjs choice: CloudFront signs the origin request to the Lambda Function URL with SigV4 but does not hash the body, and Lambda rejects an unsigned payload with `403 InvalidSignatureException`. Requests from your own pages are handled for you — cdk-nextjs injects a `fetch`/`XMLHttpRequest` wrapper into the client bundle that adds the header, so server actions, route handlers, `fetch` and `XMLHttpRequest` from the browser work untouched, whatever the body type. The exception is a native `<form method="post">` submitted before the page hydrates (a server action form without JavaScript, or clicked before the bundle loads): the browser sends it, not the wrapper, so it has no header and gets the 403. **Anything that is not that browser bundle has to add the header itself**: `curl`, a mobile app, a webhook provider, another service calling your API routes. Server-to-server callers can compute it with the AWS SDK's `Sha256`, or with `crypto.createHash("sha256").update(body).digest("hex")`. The other three deployment types are unaffected.
602
+ - If using `NextjsGlobalFunctions`, a `HEAD` request is answered with `content-length: 0`, whatever length your handler declares. This is Lambda Function URL behavior: the same runtime's declared length reaches the client through API Gateway (`NextjsRegionalFunctions`) and from an ALB, with or without CloudFront in front (the two Containers types).
603
+ - Group patterns declared via [`functionGroups`](#splitting-a-large-app-across-functions) also count against the CloudFront behavior limit above.
604
+ - If using `NextjsRegionalFunctions` without a custom domain, API Gateway REST APIs require a [stage name](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-stages.html) (default: `/prod`) to be specified. This causes links to pages and static assets to break because they're not prefixed with the stage name. You can work around this issue by specifying [basePath](https://nextjs.org/docs/app/api-reference/config/next-config-js/basePath) in next.config.js as your stage name — leave the construct's `basePath` prop unset when you do, since API Gateway strips the stage before matching resources. API Gateway strips the stage from the path it passes to Lambda; cdk-nextjs puts it back for an app whose `basePath` starts with it, reading the stage off each request, so no middleware is needed and a renamed stage needs no configuration. Mapping a custom domain at the root avoids all of this — see [examples/regional-functions/README.md](./examples/regional-functions/README.md#with-a-custom-domain-no-stage-workarounds).
349
605
 
350
606
  ## Additional Security Recommendations
351
607
 
@@ -437,111 +693,157 @@ More Details:
437
693
 
438
694
  ## Performance
439
695
 
440
- [Artillery Playwright](https://www.artillery.io/docs/reference/engines/playwright#overview) app playground example load tests results with 1K concurrent users. Reproduce with `pnpm test-fargate:lg` within `examples/load-tests`.
696
+ Measured with [k6](https://grafana.com/docs/k6/latest/) from EC2 in us-east-1, the region of the stacks under test (one load generator per stack), against a small benchmark app ([`examples/bench-app`](./examples/bench-app)) that makes no calls outside its own stack. Functions run the default 2048 MB Lambda functions. Containers run the default 1 vCPU / 2 GB Fargate tasks, with CPU autoscaling from 2 to 10 tasks added (the default is one task and no autoscaling). Methodology, caveats, and how to reproduce the numbers: [`examples/load-tests`](./examples/load-tests).
441
697
 
442
- ### NextjsGlobalFunctions
698
+ What each route measures:
443
699
 
444
- <details>
445
- <summary>`NextjsGlobalFunctions` Performance Details</summary>
446
-
447
- ```bash
448
- browser.page.TTFB.https://abc123.cloudfront.net/isr:
449
- min: ......................................................................... 6.3
450
- max: ......................................................................... 5017.4
451
- mean: ........................................................................ 11.5
452
- median: ...................................................................... 10.3
453
- p95: ......................................................................... 15.6
454
- p99: ......................................................................... 22.9
455
- browser.page.TTFB.https://abc123.cloudfront.net/isr/1:
456
- min: ......................................................................... 3.2
457
- max: ......................................................................... 560.6
458
- mean: ........................................................................ 9.4
459
- median: ...................................................................... 5.4
460
- p95: ......................................................................... 11.1
461
- p99: ......................................................................... 162.4
462
- browser.page.TTFB.https://abc123.cloudfront.net/isr/2:
463
- min: ......................................................................... 3.1
464
- max: ......................................................................... 1511.9
465
- mean: ........................................................................ 9.2
466
- median: ...................................................................... 5.2
467
- p95: ......................................................................... 10.7
468
- p99: ......................................................................... 149.9
469
- browser.page.TTFB.https://abc123.cloudfront.net/isr/3:
470
- min: ......................................................................... 3.4
471
- max: ......................................................................... 131.1
472
- mean: ........................................................................ 7.1
473
- median: ...................................................................... 5.3
474
- p95: ......................................................................... 10.1
475
- p99: ......................................................................... 64.7
476
- browser.page.TTFB.https://abc123.cloudfront.net/ssg:
477
- min: ......................................................................... 6.4
478
- max: ......................................................................... 5015.1
479
- mean: ........................................................................ 11.5
480
- median: ...................................................................... 10.3
481
- p95: ......................................................................... 15.6
482
- p99: ......................................................................... 23.3
483
- browser.page.TTFB.https://abc123.cloudfront.net/ssg/3:
484
- min: ......................................................................... 2.9
485
- max: ......................................................................... 98
486
- mean: ........................................................................ 5.1
487
- median: ...................................................................... 4.6
488
- p95: ......................................................................... 8.2
489
- p99: ......................................................................... 12.8
490
- browser.page.TTFB.https://abc123.cloudfront.net/ssr:
491
- min: ......................................................................... 6.4
492
- max: ......................................................................... 5018.6
493
- mean: ........................................................................ 11.3
494
- median: ...................................................................... 10.3
495
- p95: ......................................................................... 15.6
496
- p99: ......................................................................... 23.3
497
- browser.page.TTFB.https://abc123.cloudfront.net/ssr/2:
498
- min: ......................................................................... 83.4
499
- max: ......................................................................... 150.7
500
- mean: ........................................................................ 119
501
- median: ...................................................................... 111.1
502
- p95: ......................................................................... 147
503
- p99: ......................................................................... 147
504
- browser.page.TTFB.https://abc123.cloudfront.net/streaming:
505
- min: ......................................................................... 6.4
506
- max: ......................................................................... 5015.2
507
- mean: ........................................................................ 11.8
508
- median: ...................................................................... 10.3
509
- p95: ......................................................................... 15.6
510
- p99: ......................................................................... 23.3
511
- ```
700
+ - `static-asset`, `static`, `image`: CloudFront's cache on the Global constructs, and the construct itself on the Regional ones.
701
+ - `isr`: the cache until the page is stale (10 s), then a background revalidation through the S3 + DynamoDB cache.
702
+ - `ssr`, `stream`, `rsc`, `api`: the construct's compute on every request, on every construct.
512
703
 
513
- </details>
704
+ <!-- tables generated by `pnpm report` in examples/load-tests -->
514
705
 
515
- ![1K concurrent users](./docs/1k-concurrent-users-executions.png)
706
+ | Construct | `ssr` at 10 req/s | `ssr` at 50 req/s | `ssr` capacity | `isr` capacity | Cold start p50 |
707
+ | -------------------------- | ----------------- | ----------------------------------------- | -------------- | -------------- | -------------- |
708
+ | `NextjsGlobalFunctions` | 36 / 103 | 33 / 84 | ≥ 6560 req/s | ≥ 6560 req/s | 1390 ms |
709
+ | `NextjsGlobalContainers` | 19 / 44 | 18 / 219 | 194 req/s | ≥ 6400 req/s | – |
710
+ | `NextjsRegionalContainers` | 22 / 172 | 1972 / 39536 ⚠️ 0.54% errors, 76% dropped | 675 req/s | ≥ 6075 req/s | – |
711
+ | `NextjsRegionalFunctions` | 32 / 114 | 30 / 103 | ≥ 6560 req/s | ≥ 6560 req/s | 1380 ms |
516
712
 
517
- ### NextjsGlobalContainers
713
+ _Latency is p50 / p99 in ms, with every route loaded at that rate at once. Capacity is the highest rate sustained with p99 under 1000 ms and under 1% errors._
714
+
715
+ What this means for choosing one:
716
+
717
+ - **Functions constructs scale with no sizing.** Both held at least 6,560 req/s of SSR, the load generator's limit, at the same latency as at 10 req/s. The cost is a cold start of about 1.4 s on a new execution environment.
718
+ - **Containers constructs are fastest per request but need sizing.** Two 1 vCPU tasks served SSR at 19–22 ms p50 against Lambda's ~30 ms, but tasks take minutes to add. Past capacity they don't degrade gradually: tasks too busy to answer the ALB health check are replaced, which leaves fewer tasks for the same load. Leave headroom.
719
+ - **CloudFront carries the Global constructs' cached traffic.** Prerendered pages, ISR hits, images and assets were answered at the edge in 2–3 ms at every rate. On the Regional constructs every request reaches the construct: `NextjsRegionalContainers` serves assets and image optimization from the same tasks, so 50 req/s on every route at once (400 req/s) is already more than two tasks handle.
720
+ - **Cached pages scale on every construct.** ISR hits held at least 6,000 req/s on all four: at the edge on the Global constructs, and through the S3 + DynamoDB cache on the Regional ones, where each instance holds a tag's revalidation marker for a second (`CDK_NEXTJS_TAG_REFRESH_MS`, see the [Caching Guide](./docs/caching-guide.md)).
518
721
 
519
722
  <details>
520
- <summary>`NextjsGlobalContainers` Performance Details</summary>
723
+ <summary><code>NextjsGlobalFunctions</code></summary>
521
724
 
522
- ```bash
523
- TODO
524
- ```
725
+ Latency per route, p50 / p99 in ms, with every route at the same rate at once:
525
726
 
526
- </details>
727
+ | Route | 10 req/s | 50 req/s | 250 req/s | 1000 req/s | CDN hits |
728
+ | ------------- | --------- | --------- | --------- | ---------- | -------- |
729
+ | static-asset | 2.1 / 4.8 | 2.1 / 4.7 | 2.0 / 4.9 | 2.1 / 11 | 100% |
730
+ | static | 3.3 / 7.5 | 3.2 / 6.8 | 3.1 / 7.3 | 3.2 / 13 | 100% |
731
+ | isr | 3.3 / 7.4 | 3.3 / 7.7 | 3.2 / 7.2 | 3.2 / 12 | 100% |
732
+ | ssr | 36 / 103 | 33 / 84 | 31 / 86 | 29 / 73 | 0% |
733
+ | stream | 233 / 268 | 230 / 261 | 228 / 259 | 226 / 252 | 0% |
734
+ | stream (TTFB) | 32 / 89 | 29 / 66 | 27 / 65 | 25 / 57 | |
735
+ | rsc | 32 / 67 | 28 / 61 | 27 / 60 | 25 / 52 | 0% |
736
+ | api | 29 / 79 | 26 / 55 | 24 / 61 | 23 / 47 | 0% |
737
+ | image | 3.2 / 6.3 | 3.1 / 6.8 | 3.1 / 7.0 | 3.1 / 12 | 100% |
527
738
 
528
- ### NextjsRegionalContainers
739
+ Capacity: `isr` ≥ 6560 req/s, `ssr` ≥ 6560 req/s.
740
+
741
+ Cold start, time to first byte of `/ssr` on a new execution environment: p50 1390 ms, p90 1451 ms (warm: 84 ms).
742
+
743
+ Browser, p75 in ms, under 50 req/s per route:
744
+
745
+ | Route | LCP | TTFB | Client-side navigation |
746
+ | ------ | --- | ---- | ---------------------- |
747
+ | static | 76 | 13 | 15 |
748
+ | ssr | 80 | 39 | 53 |
749
+ | isr | 52 | 8.7 | 12 |
750
+ | stream | 64 | 34 | 38 |
751
+ | image | 52 | 6.7 | 10 |
752
+
753
+ </details>
529
754
 
530
755
  <details>
531
- <summary>`NextjsRegionalContainers` Performance Details</summary>
756
+ <summary><code>NextjsGlobalContainers</code></summary>
757
+
758
+ Latency per route, p50 / p99 in ms, with every route at the same rate at once:
759
+
760
+ | Route | 10 req/s | 50 req/s | CDN hits |
761
+ | ------------- | --------- | --------- | -------- |
762
+ | static-asset | 1.4 / 3.9 | 1.5 / 3.7 | 100% |
763
+ | static | 1.9 / 4.8 | 1.8 / 3.7 | 100% |
764
+ | isr | 2.0 / 4.4 | 1.7 / 4.0 | 100% |
765
+ | ssr | 19 / 44 | 18 / 219 | 0% |
766
+ | stream | 217 / 242 | 217 / 359 | 0% |
767
+ | stream (TTFB) | 17 / 45 | 16 / 207 | |
768
+ | rsc | 12 / 26 | 12 / 86 | 0% |
769
+ | api | 11 / 23 | 10 / 81 | 0% |
770
+ | image | 2.0 / 4.7 | 1.7 / 4.1 | 100% |
771
+
772
+ Capacity: `isr` ≥ 6400 req/s, `ssr` 194 req/s.
773
+
774
+ Browser, p75 in ms, under 50 req/s per route:
775
+
776
+ | Route | LCP | TTFB | Client-side navigation |
777
+ | ------ | --- | ---- | ---------------------- |
778
+ | static | 88 | 12 | 17 |
779
+ | ssr | 80 | 26 | 39 |
780
+ | isr | 56 | 6.1 | 13 |
781
+ | stream | 64 | 20 | 25 |
782
+ | image | 52 | 5.4 | 11 |
532
783
 
533
- ```bash
534
- TODO
535
- ```
784
+ </details>
536
785
 
537
- ### NextjsRegionalFunctions
786
+ <details>
787
+ <summary><code>NextjsRegionalContainers</code></summary>
788
+
789
+ Latency per route, p50 / p99 in ms, with every route at the same rate at once:
790
+
791
+ | Route | 10 req/s | 50 req/s | CDN hits |
792
+ | ------------- | --------- | ----------------------------------------- | -------- |
793
+ | static-asset | 18 / 159 | 1698 / 28824 ⚠️ 0.40% errors, 72% dropped | no CDN |
794
+ | static | 41 / 163 | 916 / 28298 ⚠️ 0.46% errors, 66% dropped | no CDN |
795
+ | isr | 40 / 169 | 697 / 27146 ⚠️ 0.36% errors, 57% dropped | no CDN |
796
+ | ssr | 22 / 172 | 1972 / 39536 ⚠️ 0.54% errors, 76% dropped | no CDN |
797
+ | stream | 221 / 354 | 1909 / 27423 ⚠️ 0.44% errors, 72% dropped | no CDN |
798
+ | stream (TTFB) | 19 / 166 | 1881 / 27162 | |
799
+ | rsc | 9.4 / 64 | 433 / 26066 ⚠️ 0.25% errors, 39% dropped | no CDN |
800
+ | api | 6.9 / 55 | 429 / 26041 ⚠️ 0.25% errors, 38% dropped | no CDN |
801
+ | image | 140 / 440 | 1599 / 27489 ⚠️ 0.22% errors, 69% dropped | no CDN |
802
+
803
+ Capacity: `isr` ≥ 6075 req/s, `ssr` 675 req/s.
804
+
805
+ Browser, p75 in ms, under 10 req/s per route. `static` is each visit's first page: on a plain-HTTP origin, Chrome tries HTTPS first and falls back after about 3 s (HTTPS-Upgrades), which real visitors pay too. Put a certificate on the ALB to avoid it.
806
+
807
+ | Route | LCP | TTFB | Client-side navigation |
808
+ | ------ | ---- | ---- | ---------------------- |
809
+ | static | 3100 | 3047 | 16 |
810
+ | ssr | 68 | 14 | 27 |
811
+ | isr | 88 | 43 | 13 |
812
+ | stream | 48 | 11 | 13 |
813
+ | image | 212 | 38 | 9.6 |
814
+
815
+ </details>
538
816
 
539
817
  <details>
540
- <summary>`NextjsRegionalFunctions` Performance Details</summary>
818
+ <summary><code>NextjsRegionalFunctions</code></summary>
541
819
 
542
- ```bash
543
- TODO
544
- ```
820
+ Latency per route, p50 / p99 in ms, with every route at the same rate at once:
821
+
822
+ | Route | 10 req/s | 50 req/s | 250 req/s | 1000 req/s | CDN hits |
823
+ | ------------- | --------- | --------- | --------- | ---------- | -------- |
824
+ | static-asset | 34 / 76 | 32 / 72 | 30 / 72 | 30 / 68 | no CDN |
825
+ | static | 58 / 138 | 57 / 140 | 57 / 147 | 57 / 127 | no CDN |
826
+ | isr | 58 / 149 | 56 / 124 | 56 / 145 | 55 / 125 | no CDN |
827
+ | ssr | 32 / 114 | 30 / 103 | 29 / 115 | 29 / 98 | no CDN |
828
+ | stream | 229 / 293 | 227 / 288 | 227 / 299 | 226 / 282 | no CDN |
829
+ | stream (TTFB) | 27 / 85 | 25 / 84 | 25 / 97 | 24 / 76 | |
830
+ | rsc | 27 / 76 | 26 / 73 | 25 / 83 | 25 / 70 | no CDN |
831
+ | api | 24 / 84 | 22 / 70 | 22 / 97 | 22 / 67 | no CDN |
832
+ | image | 127 / 210 | 125 / 221 | 125 / 236 | 123 / 211 | no CDN |
833
+
834
+ Capacity: `isr` ≥ 6560 req/s, `ssr` ≥ 6560 req/s.
835
+
836
+ Cold start, time to first byte of `/ssr` on a new execution environment: p50 1380 ms, p90 1442 ms (warm: 71 ms).
837
+
838
+ Browser, p75 in ms, under 50 req/s per route:
839
+
840
+ | Route | LCP | TTFB | Client-side navigation |
841
+ | ------ | --- | ---- | ---------------------- |
842
+ | static | 128 | 70 | 17 |
843
+ | ssr | 88 | 35 | 50 |
844
+ | isr | 108 | 62 | 13 |
845
+ | stream | 64 | 31 | 36 |
846
+ | image | 220 | 60 | 9.6 |
545
847
 
546
848
  </details>
547
849
 
@@ -564,11 +866,11 @@ This project uses Projen, so make sure to not edit [Projen](https://projen.io/)
564
866
 
565
867
  ## FAQ
566
868
 
567
- Q: How does this compare to [cdk-nextjs-standalone](https://github.com/jetbridge/cdk-nextjs)?<br/>
568
- A: cdk-nextjs-standalone relies on [OpenNext](https://github.com/sst/open-next). OpenNext injects custom code to interact with private Next.js APIs. While OpenNext is able to make some optimizations that are great for serverless environments, this comes at an increase maintenance cost and increased chances for breaking changes. A goal of cdk-nextjs is to customize Next.js as little as possible to reduce the maintenance burden and decrease chances of breaking changes.
869
+ Q: How does this compare to [cdk-nextjs-standalone](https://github.com/jetbridge/cdk-nextjs) or [OpenNext](https://opennext.js.org)?<br/>
870
+ A: cdk-nextjs builds on Next.js's official [Deployment Adapter](https://nextjs.org/docs/app/guides/deployment-adapters) API and runs requests through [`@next/routing`](https://www.npmjs.com/package/@next/routing), the routing library Next.js publishes for platform adapters. Next.js tells cdk-nextjs what it built, and routing follows the same rules as `next start`. Nothing re-implements Next.js's router or patches its server. The few Next.js internals cdk-nextjs does load (the image optimizer and static file server) are loaded as they are, not modified. This keeps upgrades cheap and breaking changes rare. cdk-nextjs is tested against Next.js's own end-to-end test suite; [harness-coverage.md](docs/harness-coverage.md) lists the cases that differ and why.
569
871
 
570
- Q: Why does cdk-nextjs depend upon Next.js v16.2 or higher?
571
- A: This version is required for [Image Optimization Caching](https://nextjs.org/docs/app/api-reference/config/next-config-js/incrementalCacheHandlerPath#image-optimization-caching) so that cdk-nextjs can depend upon public Next.js API.
872
+ Q: Why does cdk-nextjs depend upon Next.js v16.3 or higher?
873
+ A: cdk-nextjs builds through the public [Adapter API](https://nextjs.org/docs/app/api-reference/config/next-config-js/adapterPath), and 16.3 is the first version whose build output carries everything it reads (middleware matchers and per-file asset hashes). 16.2 also added the [Image Optimization Caching](https://nextjs.org/docs/app/api-reference/config/next-config-js/incrementalCacheHandlerPath#image-optimization-caching) it relies on. The runtime bundles `@next/routing` at the version it was tested against (see `package.json`), and newer Next.js 16 releases are expected to work.
572
874
 
573
875
  Q: How does cdk-nextjs support caching in Next.js?<br/>
574
876
  A: See [Caching Guide](./docs/caching-guide.md)
@@ -582,15 +884,12 @@ A: See [low-cost example](./examples/low-cost/app.ts).
582
884
  Q: What is difference between `NextjsGlobalFunctionsProps.overrides.nextjsDistribution` and `NextjsGlobalFunctionsProps.overrides.nextjsGlobalFunctions.nextjsDistributionProps`<br/>
583
885
  A: `NextjsGlobalFunctionsProps.overrides.nextjsDistribution` allows you to customize any construct's props _within_ `NextjsDistribution` and is likely what you want whereas `NextjsGlobalFunctionsProps.overrides.nextjsGlobalFunctions.nextjsDistributionProps` allows you to customize the props passed into the construct: `NextjsDistribution`. This principle also applies to other similarly named overrides.
584
886
 
585
- Q: Why use container image for `NextjsGlobalFunctions`?<br />
586
- A: Read [The case for containers on Lambda (with benchmarks)](https://aaronstuyvenberg.com/posts/containers-on-lambda). Also, we depend upon [AWS Lambda Web Adapter](https://github.com/awslabs/aws-lambda-web-adapter) to transform lambda event payloads into HTTP requests that Next.js expects.
587
-
588
887
  Q: How can I `cdk bootstrap --cloudformation-execution-policies ...` my AWS Account with limited permissions for cdk-nextjs to deploy?<br />
589
888
  A: See [docs/cdk-nextjs-cfn-exec-policy.json](./docs/cdk-nextjs-cfn-exec-policy.json). Note, this IAM Policy is scoped to all cdk-nextjs constructs so you can remove services if you know the construct you're using doesn't use that service.
590
889
 
591
890
  ## Acknowledgements
592
891
 
593
- This construct was built on the shoulders of giants. Thank you to the contributors of [cdk-nextjs-standalone](https://github.com/jetbridge/cdk-nextjs) and [open-next](https://github.com/sst/open-next).
892
+ This construct was built on the shoulders of giants. Thank you to the contributors of [cdk-nextjs-standalone](https://github.com/jetbridge/cdk-nextjs) and [OpenNext](https://opennext.js.org).
594
893
 
595
894
  ## 🥂 Thanks Contributors
596
895