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
@@ -8,7 +8,7 @@ Next.js uses multiple caching layers to improve performance and reduce costs. cd
8
8
 
9
9
  ## Next.js Caching Mechanisms
10
10
 
11
- Next.js uses multiple caching layers, each with a specific `CachedRouteKind` that determines how cdk-nextjs organizes them in S3:
11
+ Next.js uses multiple caching layers. Those the incremental cache stores each have a `CachedRouteKind`, recorded in the entry itself:
12
12
 
13
13
  ### 1. Request Memoization
14
14
 
@@ -25,7 +25,7 @@ Next.js uses multiple caching layers, each with a specific `CachedRouteKind` tha
25
25
  **Kind**: `FETCH`
26
26
  **cdk-nextjs Implementation**:
27
27
 
28
- - **Storage**: S3 bucket at `/{buildId}/{cache-key}`
28
+ - **Storage**: S3 bucket at `{buildId}/{cache-key}.json`
29
29
  - **Custom Cache Handler**: S3CacheHandler manages read/write operations
30
30
  - **Revalidation**: DynamoDB table tracks tag-based revalidation metadata
31
31
 
@@ -39,7 +39,8 @@ Next.js uses multiple caching layers, each with a specific `CachedRouteKind` tha
39
39
  - `APP_PAGE` - App Router pages
40
40
  - `APP_ROUTE` - App Router API routes
41
41
  - `PAGES` - Pages Router pages
42
- **cdk-nextjs Implementation**:
42
+
43
+ **cdk-nextjs Implementation**:
43
44
 
44
45
  - **ISR Support**: Files updated during Incremental Static Regeneration
45
46
  - **Revalidation**: Tag-based invalidation via DynamoDB tracking
@@ -52,15 +53,15 @@ Next.js uses multiple caching layers, each with a specific `CachedRouteKind` tha
52
53
  **Kind**: `IMAGE`
53
54
  **cdk-nextjs Implementation**:
54
55
 
55
- - **Storage**: S3 bucket at `/{buildId}/{cache-key}`
56
+ - **Storage**: S3 bucket at `{buildId}/{cache-key}.json`
56
57
  - **Optimization**: Cached resized, format-converted images
57
58
  - **Revalidation**: Time-based or on-demand revalidation
58
59
 
59
- This applies on every `NextjsType`, since `_next/image` is served by the Next.js
60
- server. If you replace that route with your own image optimization Lambda (via
61
- `NextjsApiProps.imageFunction` or `NextjsDistributionProps.imageFunctionUrl`),
62
- this cache kind is no longer written and you rely on HTTP caching instead
63
- (`Cache-Control`/`ETag` response headers, browser/CDN-cached).
60
+ This applies on every `NextjsType`: `_next/image` is handled inside cdk-nextjs's
61
+ runtime, which runs Next.js's own image optimizer and image cache over the cache
62
+ handler, so a variant is optimized once and then served with
63
+ `X-Nextjs-Cache: HIT`. Behind CloudFront (the Global types) that makes an edge
64
+ miss an S3 read rather than a second optimization.
64
65
 
65
66
  ### 5. Redirect Cache (REDIRECT)
66
67
 
@@ -70,7 +71,7 @@ this cache kind is no longer written and you rely on HTTP caching instead
70
71
  **Kind**: `REDIRECT`
71
72
  **cdk-nextjs Implementation**:
72
73
 
73
- - **Storage**: S3 bucket at `/{buildId}/{cache-key}`
74
+ - **Storage**: S3 bucket at `{buildId}/{cache-key}.json`
74
75
  - **Configuration**: Cached redirect rules and destinations
75
76
 
76
77
  ### 6. Router Cache (Client-side)
@@ -89,14 +90,24 @@ this cache kind is no longer written and you rely on HTTP caching instead
89
90
 
90
91
  ### S3 Cache Storage
91
92
 
92
- cdk-nextjs uses a dedicated S3 bucket for cache storage with BUILD_ID and kind prefixing for deployment isolation and organization:
93
+ cdk-nextjs uses a dedicated S3 bucket for cache storage, with every key under the build's ID so deployments never read each other's entries:
93
94
 
94
95
  ```
95
96
  Cache Bucket Structure:
96
- /{buildId}/
97
- └── {cache-key}.json # All cache entries (FETCH, IMAGE, APP_PAGE, APP_ROUTE, PAGES, REDIRECT)
97
+ {buildId}/
98
+ ├── {cache-key}.json # Incremental cache entries (FETCH, IMAGE, APP_PAGE, APP_ROUTE, PAGES, REDIRECT)
99
+ └── _use-cache/{sha256}.entry # 'use cache: remote' entries, keyed by a hash of the cache key
98
100
  ```
99
101
 
102
+ Each instance also keeps an in-memory copy of the entries it read or wrote, in
103
+ front of S3, for `CDK_NEXTJS_MEMORY_CACHE_TTL_MS` (1 hour by default; see the
104
+ [README](../README.md#cdk_nextjs_memory_cache_ttl_ms)). A memory hit is still
105
+ checked against its tags' revalidation markers, so a `revalidateTag` on any
106
+ instance expires it. `'use cache'` and `'use cache: remote'` have their own
107
+ handlers, described in the
108
+ [README](../README.md#use-cache-and-use-cache-remote); they read the same
109
+ revalidation markers.
110
+
100
111
  #### Examples
101
112
 
102
113
  <details>
@@ -110,10 +121,8 @@ Cache Bucket Structure:
110
121
  "kind": "APP_PAGE",
111
122
  "html": "<!DOCTYPE html><html lang=\"en\" class=\"[color-scheme:dark]\">...</html>",
112
123
  "rscData": {
113
- "type": "Buffer",
114
- "data": [
115
- 49, 58, 34, 36, 83, 114, 101, 97, 99, 116, 46, 102, 114, 97, 103, 109, ...
116
- ]
124
+ "__type": "Buffer",
125
+ "base64": "MToiJFNyZWFjdC5mcmFnbQ..."
117
126
  },
118
127
  "headers": {
119
128
  "x-nextjs-stale-time": "300",
@@ -123,46 +132,32 @@ Cache Bucket Structure:
123
132
  "__type": "Map",
124
133
  "data": {
125
134
  "/_tree": {
126
- "type": "Buffer",
127
- "data": [
128
- 58, 72, 76, 91, 34, 47, 95, 110, 101, 120, 116, 47, 115, 116, 97, ...
129
- ]
135
+ "__type": "Buffer",
136
+ "base64": "OkhMWyIvX25leHQvc3Rh..."
130
137
  },
131
138
  "/_full": {
132
- "type": "Buffer",
133
- "data": [
134
- 49, 58, 34, 36, 83, 114, 101, 97, 99, 116, 46, 102, 114, 97, 103, ...
135
- ]
139
+ "__type": "Buffer",
140
+ "base64": "MToiJFNyZWFjdC5mcmFn..."
136
141
  },
137
142
  "/isr/$d$id/__PAGE__": {
138
- "type": "Buffer",
139
- "data": [
140
- 49, 58, 34, 36, 83, 114, 101, 97, 99, 116, 46, 102, 114, 97, 103, ...
141
- ]
143
+ "__type": "Buffer",
144
+ "base64": "MToiJFNyZWFjdC5mcmFn..."
142
145
  },
143
146
  "/isr/$d$id": {
144
- "type": "Buffer",
145
- "data": [
146
- 49, 58, 34, 36, 83, 114, 101, 97, 99, 116, 46, 102, 114, 97, 103, ...
147
- ]
147
+ "__type": "Buffer",
148
+ "base64": "MToiJFNyZWFjdC5mcmFn..."
148
149
  },
149
150
  "/isr": {
150
- "type": "Buffer",
151
- "data": [
152
- 49, 58, 34, 36, 83, 114, 101, 97, 99, 116, 46, 102, 114, 97, 103, ...
153
- ]
151
+ "__type": "Buffer",
152
+ "base64": "MToiJFNyZWFjdC5mcmFn..."
154
153
  },
155
154
  "/_index": {
156
- "type": "Buffer",
157
- "data": [
158
- 49, 58, 34, 36, 83, 114, 101, 97, 99, 116, 46, 102, 114, 97, 103, ...
159
- ]
155
+ "__type": "Buffer",
156
+ "base64": "MToiJFNyZWFjdC5mcmFn..."
160
157
  },
161
158
  "/_head": {
162
- "type": "Buffer",
163
- "data": [
164
- 49, 58, 34, 36, 83, 114, 101, 97, 99, 116, 46, 102, 114, 97, 103, ...
165
- ]
159
+ "__type": "Buffer",
160
+ "base64": "MToiJFNyZWFjdC5mcmFn..."
166
161
  }
167
162
  }
168
163
  }
@@ -184,10 +179,8 @@ Cache Bucket Structure:
184
179
  "kind": "APP_ROUTE",
185
180
  "status": 200,
186
181
  "body": {
187
- "type": "Buffer",
188
- "data": [
189
- 0, 0, 1, 0, 3, 0, 48, 48, 0, 0, 1, 0, 32, 0, 168, 37, 0, 0, 54, 0, 0, 0, ...
190
- ]
182
+ "__type": "Buffer",
183
+ "base64": "AAABAAMAMDAAAAEAIACoJQAANgAAAA..."
191
184
  },
192
185
  "headers": {
193
186
  "cache-control": "public, max-age=0, must-revalidate",
@@ -260,10 +253,8 @@ Cache Bucket Structure:
260
253
  "value": {
261
254
  "kind": "IMAGE",
262
255
  "buffer": {
263
- "type": "Buffer",
264
- "data": [
265
- 255, 216, 255, 219, 0, 67, 0, 8, 8, 8, 8, 9, 8, 9, 10, 10, 9, 13, 14, ...
266
- ]
256
+ "__type": "Buffer",
257
+ "base64": "/9j/2wBDAAgICAgJCAkKCgkNDg..."
267
258
  },
268
259
  "etag": "J29FvqevmUxXsoXBJzPbJC-g_PiMBegRLXAFLl_ZfhE",
269
260
  "extension": "jpeg",
@@ -272,72 +263,114 @@ Cache Bucket Structure:
272
263
  },
273
264
  "tags": []
274
265
  }
275
-
276
266
  ```
277
267
 
278
268
  </details>
279
269
 
280
270
  **Key Features**:
281
271
 
282
- - **BUILD_ID Isolation**: All cache keys prefixed with `/{buildId}/`
272
+ - **BUILD_ID Isolation**: All cache keys prefixed with `{buildId}/`
283
273
  - **Next.js Cache Key Passthrough**: Preserves Next.js internal cache key structure
284
274
  - **Cache Kind Metadata**: Cache type stored within each cache entry's metadata
275
+ - **Binary Payloads as base64**: RSC payloads and image bodies are `Buffer`s, written as
276
+ `{ "__type": "Buffer", "base64": "..." }`. `JSON.stringify` on a `Buffer` would otherwise
277
+ produce `{ "type": "Buffer", "data": [97, 97, ...] }` — one JSON number per byte, roughly
278
+ 3x the bytes and, on read, one `JSON.parse` reviver call per byte. A 1 MiB RSC payload
279
+ costs ~4.8s of Lambda time to parse that way against ~0.2s for base64. Entries written by
280
+ an older build in the integer-array format are still read correctly.
285
281
 
286
282
  ### DynamoDB Revalidation Tracking
287
283
 
288
- A DynamoDB table tracks tag-to-S3-key mappings for efficient revalidation:
284
+ The revalidation table holds four kinds of item. Everything a build writes is
285
+ under its `buildId`, so pruning a previous build is a query of its partitions,
286
+ not a scan.
289
287
 
290
288
  ```typescript
291
- interface RevalidationItem {
292
- pk: string; // Partition Key: buildId (e.g., "build-abc123")
293
- sk: string; // Sort Key: "{tag}#{s3Key}" (e.g., "user-profile#build-abc123/fetch/api-users-123")
294
- createdAt: number; // Creation timestamp
295
- revalidatedAt: number; // Last revalidation timestamp
289
+ // When a tag was last revalidated, and how. What every revalidation check
290
+ // reads, and the source of truth.
291
+ interface TagMarkerItem {
292
+ pk: string; // buildId, e.g. "build-abc123"
293
+ sk: string; // the tag, e.g. "user-profile"
294
+ revalidatedAt?: number; // expired outright: updateTag, or revalidateTag with no profile
295
+ staleAt?: number; // stale from here: revalidateTag(tag, profile)
296
+ expiredAt?: number; // when that profile's expire runs out
297
+ }
298
+
299
+ // One row per revalidation of a tag, so an instance can ask "what changed
300
+ // since I last looked?" in one Query instead of re-reading every tag it keeps.
301
+ interface RevalidationLogItem {
302
+ pk: string; // "{buildId}#log"
303
+ sk: string; // "{epoch ms, zero-padded to 15 digits}#{tag}": sorts by time
304
+ revalidatedAt?: number; // the same fields the marker got
305
+ staleAt?: number;
306
+ expiredAt?: number;
307
+ ttl: number; // epoch seconds, 15 minutes out; DynamoDB deletes it after
308
+ }
309
+
310
+ // Which cached pages a tag appears on, so revalidateTag can name their
311
+ // CloudFront paths. Only written on the Global constructs.
312
+ interface TagMappingItem {
313
+ pk: string; // buildId
314
+ sk: string; // "{tag}#{s3Key}"
315
+ createdAt: number;
296
316
  }
297
317
 
298
318
  interface MetadataItem {
299
- pk: "METADATA"; // Special partition key for metadata
300
- sk: "CURRENT_BUILD"; // Special sort key for tracking current build
301
- buildId: string; // Current BUILD_ID for efficient pruning
302
- updatedAt: number; // Last update timestamp
319
+ pk: "METADATA";
320
+ sk: "CURRENT_BUILD";
321
+ buildId: string; // the current build, so the next deploy knows what to prune
322
+ updatedAt: number;
303
323
  }
304
324
  ```
305
325
 
306
- **Example Data**:
326
+ **Example Data**, after `revalidateTag("user-profile")`:
307
327
 
308
328
  ```
309
- PK: "build-abc123" SK: "user-profile#build-abc123/api-users-123"
310
- PK: "build-abc123" SK: "user-profile#build-abc123/users-profile"
311
- PK: "build-abc123" SK: "product-data#build-abc123/products-electronics"
312
- PK: "build-abc123" SK: "product-images#build-abc123/product-123-thumb"
313
- PK: "METADATA" SK: "CURRENT_BUILD" buildId: "build-abc123"
329
+ PK: "build-abc123" SK: "user-profile" revalidatedAt: 1790467483203
330
+ PK: "build-abc123#log" SK: "001790467483193#user-profile" revalidatedAt: 1790467483193, ttl: 1790468384
331
+ PK: "build-abc123" SK: "user-profile#build-abc123/users-profile.json" createdAt: 1790467248586
332
+ PK: "build-abc123" SK: "product-data#build-abc123/products.json" createdAt: 1790467248586
333
+ PK: "METADATA" SK: "CURRENT_BUILD" buildId: "build-abc123"
314
334
  ```
315
335
 
316
336
  **Key Features**:
317
337
 
318
- - **Efficient Pruning**: Query previous build's partition to delete old entries (no table scan)
319
- - **Metadata Tracking**: Stores current BUILD_ID for identifying previous build during pruning
320
- - **Full S3 Key Storage**: Sort key contains complete S3 path for direct deletion
321
- - **Efficient Revalidation**: Query by buildId + tag prefix returns all related cache entries
338
+ - **Checks read markers, not mappings**: an instance reads a tag's marker the
339
+ first time it needs it, then learns of other instances' revalidations from
340
+ one log `Query` per second (see
341
+ [On-Demand Revalidation](#on-demand-revalidation)).
342
+ - **Log rows sort by time**: the sort key's timestamp is what makes "every
343
+ revalidation since my last query" one key range. Rows older than the query's
344
+ range are never read, so DynamoDB deleting expired rows late costs only
345
+ storage.
346
+ - **Mapping rows name CloudFront paths**: `revalidateTag` queries
347
+ `begins_with(sk, "{tag}#")` for the S3 keys, and so the paths, to invalidate.
348
+ Runtime `set`s write them; build-time prerenders are named by the build's
349
+ tag manifest in the cache bucket instead.
350
+ - **Efficient pruning**: the post-deploy step queries the previous build's
351
+ partition and deletes it. Log rows expire through TTL instead.
322
352
 
323
353
  ### Custom Cache Handler
324
354
 
325
- The S3CacheHandler implements Next.js's cache interface with comprehensive tag-based revalidation:
355
+ The `cacheHandler` the adapter registers (`src/adapter/cache-handler.ts`) writes local files at build time and, at runtime, puts an in-memory handler in front of `S3CacheHandler`, which implements Next.js's incremental cache interface:
326
356
 
327
357
  ```typescript
328
358
  export class S3CacheHandler {
329
359
  async get(
330
360
  cacheKey: string,
331
- ctx: { kind: CachedRouteKind },
361
+ ctx: GetIncrementalFetchCacheContext | GetIncrementalResponseCacheContext,
332
362
  ): Promise<CacheHandlerValue | null>;
333
363
 
334
364
  async set(
335
365
  cacheKey: string,
336
- data: IncrementalCacheValue,
337
- ctx: { tags: string[] },
366
+ data: IncrementalCacheValue | null,
367
+ ctx: SetIncrementalFetchCacheContext | SetIncrementalResponseCacheContext,
338
368
  ): Promise<void>;
339
369
 
340
- async revalidateTag(tag: string): Promise<void>;
370
+ async revalidateTag(
371
+ tag: string | string[],
372
+ durations?: { expire?: number },
373
+ ): Promise<void>;
341
374
  }
342
375
  ```
343
376
 
@@ -365,9 +398,9 @@ Each cache entry stored in S3 includes both the cached data and associated tags
365
398
  **Cache Retrieval Process**:
366
399
 
367
400
  1. **Fetch from S3**: Retrieve cache entry with embedded tags
368
- 2. **Revalidation Check**: Query DynamoDB to check if any tag has been revalidated since cache creation
369
- 3. **Timestamp Comparison**: Compare `revalidatedAt` with cache entry's `lastModified`
370
- 4. **Invalidation**: If any tag was revalidated after cache creation, delete S3 entry and return cache miss
401
+ 2. **Revalidation Check**: Look up each tag's marker (see [On-Demand Revalidation](#on-demand-revalidation) for where it comes from)
402
+ 3. **Timestamp Comparison**: Compare the marker's `revalidatedAt`/`staleAt`/`expiredAt` with the entry's `lastModified`
403
+ 4. **Invalidation**: If a tag was revalidated after the entry was created, a `fetch` entry reads as a miss and a page entry comes back expired, so Next.js refetches or re-renders it. The S3 object is left for that write to replace.
371
404
  5. **Return**: If valid, return cached data without tags
372
405
 
373
406
  **Key Features**:
@@ -376,7 +409,6 @@ Each cache entry stored in S3 includes both the cached data and associated tags
376
409
  - **Tag Storage**: Tags stored with cache entries for revalidation checking
377
410
  - **Timestamp Validation**: Prevents serving stale data after tag revalidation
378
411
  - **Graceful Error Handling**: Logs errors and returns cache miss on failures
379
- - **Bulletproof Consistency**: Even if S3 deletions fail, stale data won't be served
380
412
 
381
413
  ## Static Assets vs Cache Assets
382
414
 
@@ -388,13 +420,17 @@ Each cache entry stored in S3 includes both the cached data and associated tags
388
420
 
389
421
  - `public/` folder contents
390
422
  - `.next/static/` build artifacts (JS, CSS, images)
391
- - BUILD_ID metadata for pruning
423
+ - A `BUILD_ID` object metadata entry on each file, for pruning
392
424
 
393
- **CloudFront Integration**:
425
+ **Serving** (Global constructs):
394
426
 
395
427
  - Requests to `/_next/static/*` → S3 bucket
396
- - Requests to `/public/*` → S3 bucket
397
- - Long-term caching headers for performance
428
+ - Requests for `public/` files, at their own paths (one CloudFront behavior per top-level entry, e.g. `/favicon.ico`, `/images/*`) → S3 bucket
429
+ - Cached at the edge with CloudFront's `CachingOptimized` policy
430
+
431
+ On the Regional constructs there is no CDN: `NextjsRegionalFunctions` serves
432
+ them through API Gateway's S3 integration, and `NextjsRegionalContainers` from
433
+ the tasks.
398
434
 
399
435
  ### Cache Assets (S3CacheHandler)
400
436
 
@@ -412,47 +448,55 @@ Each cache entry stored in S3 includes both the cached data and associated tags
412
448
 
413
449
  When `revalidateTag("user-profile")` is called:
414
450
 
415
- 1. **Query DynamoDB**: Find all cache keys tagged with `pk = {buildId} and sk starts_with user-profile`
416
- 2. **Update Timestamps**: Mark revalidation time in DynamoDB for each cache entry
417
- 3. **Delete S3 Objects**: Remove corresponding cache files from S3
451
+ 1. **Write the marker**: Set `revalidatedAt` (or `staleAt`/`expiredAt` for a profile) on the tag's marker row, `pk = {buildId}, sk = user-profile`
452
+ 2. **Log it**: Put a revalidation log row, `pk = {buildId}#log, sk = {epoch ms}#user-profile`, for other instances to find
453
+ 3. **Find the pages** (Global constructs only): Query the mapping rows, `pk = {buildId} and begins_with(sk, "user-profile#")`, for the S3 keys, and so the paths, the tag appears on, plus the build-time prerenders the build's tag manifest (`{buildId}/_cdk-nextjs-tag-manifest.json` in the cache bucket, read once per instance) lists for it
418
454
  4. **Invalidate CloudFront** (`NextjsGlobalFunctions`/`NextjsGlobalContainers` only): Evict the corresponding paths from the CDN edge cache too, so the origin's now-fresh state isn't masked by a still-cached edge response. This is best-effort and asynchronous (`cloudfront:CreateInvalidation` has no bounded completion SLA), so a client may briefly still observe stale content immediately after revalidation.
419
455
 
420
- **Revalidation Safety**: Even if S3 deletions fail due to network issues or race conditions, the cache handler will detect stale data during the next `get()` operation by comparing timestamps and automatically remove invalid entries.
456
+ No S3 object is deleted: the next `get()` of an entry the tag is on compares the marker with the entry's `lastModified` and treats it as revalidated, and the refetch or re-render that follows overwrites it.
421
457
 
422
458
  **Cache Retrieval After Revalidation**:
423
459
 
424
460
  1. **Fetch Cache Entry**: Retrieve from S3 with embedded tags
425
- 2. **Check Revalidation**: Query DynamoDB for each tag's `revalidatedAt` timestamp
426
- 3. **Compare Timestamps**: If any `revalidatedAt` > cache `lastModified`, cache is invalid
427
- 4. **Auto-cleanup**: Delete stale S3 entry and return cache miss
428
- 5. **Fresh Data**: Next request will fetch fresh data and create new cache entry
461
+ 2. **Check Revalidation**: Look up each tag's marker
462
+ 3. **Compare Timestamps**: If any `revalidatedAt` (or a past `expiredAt`) > the entry's `lastModified`, it is expired; a later `staleAt` alone makes it stale, served while it regenerates
463
+ 4. **Fresh Data**: The refetch or re-render writes a new entry over the old one
464
+
465
+ **Marker reads are kept per instance, and caught up from a log.** Every cache hit checks its tags, in-memory hits included, and every marker of a deployment shares one partition key. So each instance reads a tag's marker the first time it needs it and keeps it. Once a second it sends one `Query` to the revalidation log (`pk = <buildId>#log`, one row per revalidated tag, expiring after 15 minutes) for what other instances revalidated since, instead of re-reading its tags. Concurrent checks share that query. It still re-reads each marker it keeps every 7.5 to 10 minutes (a random point per tag, so tags first read together don't come due together), since the marker rows are the source of truth, but at most 100 per second rather than all at once. An instance keeps up to 10,000 tags. The instance that calls `revalidateTag` sees it immediately; other instances see it up to 1 s later, which is within what CloudFront's invalidation already takes. ISR, the data cache and `'use cache'` share one copy of the markers per instance, and so one query. Set `CDK_NEXTJS_TAG_REFRESH_MS` on the functions or tasks (through `overrides`) to change the interval, or to `0` to query the log on every check.
429
466
 
430
467
  ### Time-based Revalidation
431
468
 
432
469
  For routes with time-based revalidation (e.g., `revalidate: 3600`):
433
470
 
434
- - Next.js checks cache age before serving
435
- - Triggers background regeneration when expired
436
- - Updates cache files in S3 automatically
471
+ - Next.js checks the entry's age before serving
472
+ - Past `revalidate`, serves the stale entry and regenerates it in the background
473
+ - The regenerated entry is written to S3 (and memory) as usual
437
474
 
438
- **Known limitation on Lambda**: this background regeneration is Next.js's own fire-and-forget work, not something cdk-nextjs's cache handler is invoked to await — the same "no signal for when background work is complete" gap that led to `NextjsRevalidation` (an SQS-based workaround) being removed in favor of waiting for Next.js's [Deployment Adapters `waitFor` API](https://github.com/vercel/next.js/discussions/77740) (see `docs/breaking-changes.md` 0.4.0). Until cdk-nextjs adopts `waitFor`, time-based revalidation on Lambda may occasionally not complete before the execution environment spins down, unrelated to the CloudFront-edge-cache invalidation described below (which only fires for explicit tag/path revalidation, not time-based expiry).
475
+ **On Lambda, background regeneration completes before the environment freezes.** Next.js hands this work to the runtime through the adapter API's `waitUntil`. The runtime awaits it after the response stream has closed, so it adds billed duration but no latency, and the function's timeout bounds it. The Containers constructs' server does the same. This is unrelated to the CloudFront edge-cache invalidation described below, which only fires for explicit tag or path revalidation, not time-based expiry.
439
476
 
440
477
  ### Tag-based Revalidation
441
478
 
442
479
  ```typescript
443
- // In your API route or Server Action
444
- import { revalidateTag } from "next/cache";
480
+ "use server";
481
+ // In a Server Action
482
+ import { revalidateTag, updateTag } from "next/cache";
445
483
 
446
484
  export async function updateUser(userId: string) {
447
- // Update user data
448
485
  await updateUserInDatabase(userId);
449
486
 
450
- // Invalidate all cache entries tagged with this user
451
- revalidateTag(`user-${userId}`);
452
- revalidateTag("user-list");
487
+ // Expire at once, so this user sees their own change on the next render
488
+ updateTag(`user-${userId}`);
489
+ // Serve stale while every page listing users regenerates in the background
490
+ revalidateTag("user-list", "max");
453
491
  }
454
492
  ```
455
493
 
494
+ `updateTag` (Server Actions only) sets the marker's `revalidatedAt`: entries
495
+ expire immediately. `revalidateTag(tag, profile)` sets `staleAt`, and `expiredAt`
496
+ from the profile's `expire`: entries are served stale while they regenerate,
497
+ until that runs out (with `{ expire: 0 }`, at once). Route handlers can use
498
+ `revalidateTag` but not `updateTag`.
499
+
456
500
  ## Performance Characteristics
457
501
 
458
502
  ### S3 Cache Operations
@@ -465,7 +509,7 @@ export async function updateUser(userId: string) {
465
509
  ### DynamoDB Revalidation
466
510
 
467
511
  - **Query Latency**: ~1-5ms (single partition key lookup)
468
- - **Revalidation Check**: Additional 1-5ms per tag during cache retrieval
512
+ - **Revalidation Check**: One `BatchGetItem` for tags an instance hasn't seen yet, then one revalidation log `Query` per second per instance, however many tags it keeps (`CDK_NEXTJS_TAG_REFRESH_MS`)
469
513
  - **Scalability**: Handles millions of cache entries
470
514
  - **Cost**: Minimal - only pays for actual reads/writes
471
515
  - **Consistency**: Eventually consistent (sufficient for cache invalidation)
@@ -481,7 +525,7 @@ export async function updateUser(userId: string) {
481
525
 
482
526
  ### Cache Not Working
483
527
 
484
- 1. Check environment variables are set correctly
528
+ 1. Check the compute's environment has `CDK_NEXTJS_CACHE_BUCKET_NAME`, `CDK_NEXTJS_REVALIDATION_TABLE_NAME` and `CDK_NEXTJS_BUILD_ID` (the constructs set them)
485
529
  2. Verify S3 bucket and DynamoDB table exist
486
530
  3. Check Lambda/container permissions
487
531
  4. Review CloudWatch logs for errors
@@ -489,11 +533,11 @@ export async function updateUser(userId: string) {
489
533
  ### Revalidation Issues
490
534
 
491
535
  1. Verify tags are set correctly in fetch requests
492
- 2. Check DynamoDB for tag-to-cache-key mappings
536
+ 2. Check the tag's marker row in DynamoDB (`pk = {buildId}, sk = {tag}`) and its log rows (`pk = {buildId}#log`)
493
537
  3. Ensure revalidateTag() calls are working
494
- 4. Monitor S3 object deletions
495
- 5. Check CloudWatch logs for "CACHE INVALIDATED BY TAG" messages
496
- 6. Verify timestamp comparisons in DynamoDB revalidation entries
538
+ 4. On the Global constructs, check the tag's mapping rows (`sk` beginning `{tag}#`) and the build's tag manifest (`{buildId}/_cdk-nextjs-tag-manifest.json`), which name the CloudFront paths to invalidate
539
+ 5. With `DEBUG=cdk-nextjs:*`, check CloudWatch logs for "CACHE INVALIDATED BY TAG" messages
540
+ 6. Compare the marker's timestamps with the entry's `lastModified`
497
541
 
498
542
  ### Performance Problems
499
543
 
@@ -11,10 +11,11 @@
11
11
  "cloudfront:*",
12
12
  "cloudwatch:*",
13
13
  "dynamodb:*",
14
- "ecs:*",
15
14
  "ec2:*",
15
+ "ecs:*",
16
16
  "elasticfilesystem:*",
17
17
  "elasticloadbalancing:*",
18
+ "events:*",
18
19
  "iam:*",
19
20
  "lambda:*",
20
21
  "logs:*",
@@ -53,7 +53,7 @@ Expanding Turbopack's root to include the entire monorepo causes it to repeatedl
53
53
  The `examples/app-playground/package.json` includes a `sync-cdk-nextjs` script that copies the necessary files directly into `node_modules` without symlinks:
54
54
 
55
55
  ```json
56
- "sync-cdk-nextjs": "rm -rf node_modules/cdk-nextjs && mkdir -p node_modules/cdk-nextjs/lib/adapter && cp ../../package.json node_modules/cdk-nextjs/package.json && cp ../../lib/adapter/adapter.mjs node_modules/cdk-nextjs/lib/adapter/adapter.mjs && cp ../../lib/adapter/cache-handler.mjs node_modules/cdk-nextjs/lib/adapter/cache-handler.mjs"
56
+ "sync-cdk-nextjs": "rm -rf node_modules/cdk-nextjs && mkdir -p node_modules/cdk-nextjs/lib/adapter && cp ../../package.json node_modules/cdk-nextjs/package.json && cp ../../lib/adapter/adapter.mjs node_modules/cdk-nextjs/lib/adapter/adapter.mjs && cp ../../lib/adapter/cache-handler.mjs ../../lib/adapter/use-cache-default-handler.mjs ../../lib/adapter/use-cache-remote-handler.mjs node_modules/cdk-nextjs/lib/adapter/"
57
57
  ```
58
58
 
59
59
  Both `build` and `dev` chain it explicitly (`npm run sync-cdk-nextjs && next …`) so the copy happens no matter which package manager invokes them. While hacky, it: