From abeb9cdb91f8e3a1efbb9a8b66b910a5e1ce4699 Mon Sep 17 00:00:00 2001 From: conico974 Date: Tue, 28 Nov 2023 22:34:31 +0100 Subject: [PATCH] Fix tags cache (#317) * fix: fetchCache for next 14+ * fix: regression from #295, tags were not generated * test: add test for revalidateTag * pin next version * add changeset --- .changeset/poor-ravens-attack.md | 5 + examples/app-pages-router/package.json | 6 +- .../app/api/revalidate-tag/route.ts | 7 ++ .../app-router/app/revalidate-tag/layout.tsx | 19 ++++ .../app/revalidate-tag/nested/page.tsx | 3 + .../app-router/app/revalidate-tag/page.tsx | 8 ++ examples/app-router/middleware.ts | 8 ++ examples/app-router/package.json | 6 +- examples/pages-router/package.json | 4 +- packages/open-next/src/adapters/cache.ts | 14 ++- packages/open-next/src/build.ts | 5 +- .../tests/appRouter/revalidateTag.test.ts | 56 ++++++++++ pnpm-lock.yaml | 104 +++++++++++------- 13 files changed, 192 insertions(+), 53 deletions(-) create mode 100644 .changeset/poor-ravens-attack.md create mode 100644 examples/app-router/app/api/revalidate-tag/route.ts create mode 100644 examples/app-router/app/revalidate-tag/layout.tsx create mode 100644 examples/app-router/app/revalidate-tag/nested/page.tsx create mode 100644 examples/app-router/app/revalidate-tag/page.tsx create mode 100644 packages/tests-e2e/tests/appRouter/revalidateTag.test.ts diff --git a/.changeset/poor-ravens-attack.md b/.changeset/poor-ravens-attack.md new file mode 100644 index 00000000..60623490 --- /dev/null +++ b/.changeset/poor-ravens-attack.md @@ -0,0 +1,5 @@ +--- +"open-next": patch +--- + +Setting the right tag values for fetch cache (#304); Fix getHeader crash external rewrites (#321); Added --package-json option to specify package json path (#322); Change querystring format for multi value parameters (#320);Fix tags cache (#317);Fix skip trailing slash redirect (#323) diff --git a/examples/app-pages-router/package.json b/examples/app-pages-router/package.json index 013df2b5..01b3cc16 100644 --- a/examples/app-pages-router/package.json +++ b/examples/app-pages-router/package.json @@ -8,13 +8,13 @@ "build": "next build", "start": "next start --port 3003", "lint": "next lint", - "clean": "rm -rf .turbo node_modules .next .open-next" + "clean": "rm -rf .turbo node_modules .next .open-next" }, "dependencies": { - "open-next": "workspace:*", "@example/shared": "workspace:*", "@open-next/utils": "workspace:*", - "next": "latest", + "next": "^14.0.3", + "open-next": "workspace:*", "react": "latest", "react-dom": "latest" }, diff --git a/examples/app-router/app/api/revalidate-tag/route.ts b/examples/app-router/app/api/revalidate-tag/route.ts new file mode 100644 index 00000000..9f52bf19 --- /dev/null +++ b/examples/app-router/app/api/revalidate-tag/route.ts @@ -0,0 +1,7 @@ +import { revalidateTag } from "next/cache"; + +export async function GET() { + revalidateTag("revalidate"); + + return new Response("ok"); +} diff --git a/examples/app-router/app/revalidate-tag/layout.tsx b/examples/app-router/app/revalidate-tag/layout.tsx new file mode 100644 index 00000000..a0f8f9fd --- /dev/null +++ b/examples/app-router/app/revalidate-tag/layout.tsx @@ -0,0 +1,19 @@ +import { unstable_cache } from "next/cache"; +import type { ReactNode } from "react"; + +export default async function Layout({ children }: { children: ReactNode }) { + const fakeFetch = unstable_cache( + async () => new Date().getTime(), + ["fakeFetch"], + { + tags: ["revalidate"], + }, + ); + const fetchedDate = await fakeFetch(); + return ( +
+
Fetched time: {new Date(fetchedDate).toISOString()}
+ {children} +
+ ); +} diff --git a/examples/app-router/app/revalidate-tag/nested/page.tsx b/examples/app-router/app/revalidate-tag/nested/page.tsx new file mode 100644 index 00000000..8436502b --- /dev/null +++ b/examples/app-router/app/revalidate-tag/nested/page.tsx @@ -0,0 +1,3 @@ +export default async function Nested() { + return
Nested
; +} diff --git a/examples/app-router/app/revalidate-tag/page.tsx b/examples/app-router/app/revalidate-tag/page.tsx new file mode 100644 index 00000000..c07794e7 --- /dev/null +++ b/examples/app-router/app/revalidate-tag/page.tsx @@ -0,0 +1,8 @@ +async function getTime() { + return new Date().toISOString(); +} + +export default async function ISR() { + const time = getTime(); + return
Time: {time}
; +} diff --git a/examples/app-router/middleware.ts b/examples/app-router/middleware.ts index e0c77ac7..ce6233af 100644 --- a/examples/app-router/middleware.ts +++ b/examples/app-router/middleware.ts @@ -42,6 +42,14 @@ export function middleware(request: NextRequest) { ); } + // It is so that cloudfront doesn't cache the response + if (path.startsWith("/revalidate-tag")) { + responseHeaders.set( + "cache-control", + "private, no-cache, no-store, max-age=0, must-revalidate", + ); + } + const r = NextResponse.next({ headers: responseHeaders, request: { diff --git a/examples/app-router/package.json b/examples/app-router/package.json index 74efe54a..8775b4ea 100644 --- a/examples/app-router/package.json +++ b/examples/app-router/package.json @@ -8,13 +8,13 @@ "build": "next build", "start": "next start --port 3001", "lint": "next lint", - "clean": "rm -rf .turbo node_modules .next .open-next" + "clean": "rm -rf .turbo node_modules .next .open-next" }, "dependencies": { - "open-next": "workspace:*", "@example/shared": "workspace:*", "@open-next/utils": "workspace:*", - "next": "latest", + "next": "^14.0.3", + "open-next": "workspace:*", "react": "latest", "react-dom": "latest" }, diff --git a/examples/pages-router/package.json b/examples/pages-router/package.json index c83bc78a..590a56d5 100644 --- a/examples/pages-router/package.json +++ b/examples/pages-router/package.json @@ -8,7 +8,7 @@ "build": "next build", "start": "next start --port 3002", "lint": "next lint", - "clean": "rm -rf .turbo node_modules .next .open-next" + "clean": "rm -rf .turbo node_modules .next .open-next" }, "dependencies": { "@example/shared": "workspace:*", @@ -16,7 +16,7 @@ "@types/react": "18.2.20", "@types/react-dom": "18.2.7", "autoprefixer": "10.4.15", - "next": "latest", + "next": "^14.0.3", "postcss": "8.4.27", "react": "latest", "react-dom": "latest", diff --git a/packages/open-next/src/adapters/cache.ts b/packages/open-next/src/adapters/cache.ts index ec1bbe00..48e52d9a 100644 --- a/packages/open-next/src/adapters/cache.ts +++ b/packages/open-next/src/adapters/cache.ts @@ -159,12 +159,22 @@ export default class S3Cache { this.buildId = NEXT_BUILD_ID!; } - public async get(key: string, options?: boolean | { fetchCache?: boolean }) { + public async get( + key: string, + // fetchCache is for next 13.5 and above, kindHint is for next 14 and above and boolean is for earlier versions + options?: + | boolean + | { fetchCache?: boolean; kindHint?: "app" | "pages" | "fetch" }, + ) { if (globalThis.disableIncrementalCache) { return null; } const isFetchCache = - typeof options === "object" ? options.fetchCache : options; + typeof options === "object" + ? options.kindHint + ? options.kindHint === "fetch" + : options.fetchCache + : options; return isFetchCache ? this.getFetchCache(key) : this.getIncrementalCache(key); diff --git a/packages/open-next/src/build.ts b/packages/open-next/src/build.ts index e5631513..a184e56d 100755 --- a/packages/open-next/src/build.ts +++ b/packages/open-next/src/build.ts @@ -509,8 +509,6 @@ function createCacheAssets(monorepoRoot: string, disableDynamoDBCache = false) { fs.writeFileSync(cacheFilePath, JSON.stringify(cacheFileContent)); }); - removeFiles(outputPath, (file) => !file.endsWith(".cache")); - if (!disableDynamoDBCache) { // Generate dynamodb data // We need to traverse the cache to find every .meta file @@ -597,6 +595,9 @@ function createCacheAssets(monorepoRoot: string, disableDynamoDBCache = false) { ); } } + + // We need to remove files later because we need the metafiles for dynamodb tags cache + removeFiles(outputPath, (file) => !file.endsWith(".cache")); } /***************************/ diff --git a/packages/tests-e2e/tests/appRouter/revalidateTag.test.ts b/packages/tests-e2e/tests/appRouter/revalidateTag.test.ts new file mode 100644 index 00000000..ae3d1aa4 --- /dev/null +++ b/packages/tests-e2e/tests/appRouter/revalidateTag.test.ts @@ -0,0 +1,56 @@ +import { expect, test } from "@playwright/test"; + +test("Revalidate tag", async ({ page, request }) => { + test.setTimeout(45000); + let responsePromise = page.waitForResponse((response) => { + return response.status() === 200; + }); + await page.goto("/revalidate-tag"); + let elLayout = page.getByText("Fetched time:"); + let time = await elLayout.textContent(); + let newTime; + + let response = await responsePromise; + const nextCacheHeader = response.headers()["x-nextjs-cache"]; + expect(nextCacheHeader).toMatch(/^(HIT|STALE)$/); + + // Send revalidate tag request + + const result = await request.get("/api/revalidate-tag"); + expect(result.status()).toEqual(200); + const text = await result.text(); + expect(text).toEqual("ok"); + + responsePromise = page.waitForResponse((response) => { + return response.status() === 200; + }); + await page.reload(); + elLayout = page.getByText("Fetched time:"); + newTime = await elLayout.textContent(); + + expect(newTime).not.toEqual(time); + + response = await responsePromise; + expect(response.headers()["x-nextjs-cache"]).toEqual("MISS"); + + //Check if nested page is also a miss + responsePromise = page.waitForResponse((response) => { + return response.status() === 200; + }); + await page.goto("/revalidate-tag/nested"); + elLayout = page.getByText("Fetched time:"); + newTime = await elLayout.textContent(); + expect(newTime).not.toEqual(time); + + response = await responsePromise; + expect(response.headers()["x-nextjs-cache"]).toEqual("MISS"); + + // If we hit the page again, it should be a hit + responsePromise = page.waitForResponse((response) => { + return response.status() === 200; + }); + await page.goto("/revalidate-tag/nested"); + + response = await responsePromise; + expect(response.headers()["x-nextjs-cache"]).toEqual("HIT"); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2aa5e3a2..26e54d7a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -103,8 +103,8 @@ importers: specifier: workspace:* version: link:../../packages/utils next: - specifier: latest - version: 13.5.3(react-dom@18.2.0)(react@18.2.0) + specifier: ^14.0.3 + version: 14.0.3(react-dom@18.2.0)(react@18.2.0) open-next: specifier: workspace:* version: link:../../packages/open-next @@ -146,8 +146,8 @@ importers: specifier: workspace:* version: link:../../packages/utils next: - specifier: latest - version: 13.5.3(react-dom@18.2.0)(react@18.2.0) + specifier: ^14.0.3 + version: 14.0.3(react-dom@18.2.0)(react@18.2.0) open-next: specifier: workspace:* version: link:../../packages/open-next @@ -198,8 +198,8 @@ importers: specifier: 10.4.15 version: 10.4.15(postcss@8.4.27) next: - specifier: latest - version: 13.5.3(react-dom@18.2.0)(react@18.2.0) + specifier: ^14.0.3 + version: 14.0.3(react-dom@18.2.0)(react@18.2.0) postcss: specifier: 8.4.27 version: 8.4.27 @@ -469,6 +469,9 @@ packages: /@aws-cdk/cloud-assembly-schema@2.101.1: resolution: {integrity: sha512-zP+5eaOcnEMTZHcVSl8oqrzttKpLm4i1yEMkh7mwbVVAcH6ofd4sPKc8LDkJRWruP47Z9yDtiwx+ly2ZRXG58Q==} engines: {node: '>= 14.15.0'} + dependencies: + jsonschema: 1.4.1 + semver: 7.5.4 dev: true bundledDependencies: - jsonschema @@ -516,6 +519,7 @@ packages: '@aws-cdk/cloud-assembly-schema': 2.101.1 dependencies: '@aws-cdk/cloud-assembly-schema': 2.101.1 + semver: 7.5.4 dev: true bundledDependencies: - semver @@ -4727,8 +4731,8 @@ packages: resolution: {integrity: sha512-RmHanbV21saP/6OEPBJ7yJMuys68cIf8OBBWd7+uj40LdpmswVAwe1uzeuFyUsd6SfeITWT3XnQfn6wULeKwDQ==} dev: false - /@next/env@13.5.3: - resolution: {integrity: sha512-X4te86vsbjsB7iO4usY9jLPtZ827Mbx+WcwNBGUOIuswuTAKQtzsuoxc/6KLxCMvogKG795MhrR1LDhYgDvasg==} + /@next/env@14.0.3: + resolution: {integrity: sha512-7xRqh9nMvP5xrW4/+L0jgRRX+HoNRGnfJpD+5Wq6/13j3dsdzxO3BCXn7D3hMqsDb+vjZnJq+vI7+EtgrYZTeA==} dev: false /@next/eslint-plugin-next@13.4.19: @@ -4746,8 +4750,8 @@ packages: dev: false optional: true - /@next/swc-darwin-arm64@13.5.3: - resolution: {integrity: sha512-6hiYNJxJmyYvvKGrVThzo4nTcqvqUTA/JvKim7Auaj33NexDqSNwN5YrrQu+QhZJCIpv2tULSHt+lf+rUflLSw==} + /@next/swc-darwin-arm64@14.0.3: + resolution: {integrity: sha512-64JbSvi3nbbcEtyitNn2LEDS/hcleAFpHdykpcnrstITFlzFgB/bW0ER5/SJJwUPj+ZPY+z3e+1jAfcczRLVGw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -4764,8 +4768,8 @@ packages: dev: false optional: true - /@next/swc-darwin-x64@13.5.3: - resolution: {integrity: sha512-UpBKxu2ob9scbpJyEq/xPgpdrgBgN3aLYlxyGqlYX5/KnwpJpFuIHU2lx8upQQ7L+MEmz+fA1XSgesoK92ppwQ==} + /@next/swc-darwin-x64@14.0.3: + resolution: {integrity: sha512-RkTf+KbAD0SgYdVn1XzqE/+sIxYGB7NLMZRn9I4Z24afrhUpVJx6L8hsRnIwxz3ERE2NFURNliPjJ2QNfnWicQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -4782,8 +4786,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-gnu@13.5.3: - resolution: {integrity: sha512-5AzM7Yx1Ky+oLY6pHs7tjONTF22JirDPd5Jw/3/NazJ73uGB05NqhGhB4SbeCchg7SlVYVBeRMrMSZwJwq/xoA==} + /@next/swc-linux-arm64-gnu@14.0.3: + resolution: {integrity: sha512-3tBWGgz7M9RKLO6sPWC6c4pAw4geujSwQ7q7Si4d6bo0l6cLs4tmO+lnSwFp1Tm3lxwfMk0SgkJT7EdwYSJvcg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -4800,8 +4804,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-musl@13.5.3: - resolution: {integrity: sha512-A/C1shbyUhj7wRtokmn73eBksjTM7fFQoY2v/0rTM5wehpkjQRLOXI8WJsag2uLhnZ4ii5OzR1rFPwoD9cvOgA==} + /@next/swc-linux-arm64-musl@14.0.3: + resolution: {integrity: sha512-v0v8Kb8j8T23jvVUWZeA2D8+izWspeyeDGNaT2/mTHWp7+37fiNfL8bmBWiOmeumXkacM/AB0XOUQvEbncSnHA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -4818,8 +4822,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-gnu@13.5.3: - resolution: {integrity: sha512-FubPuw/Boz8tKkk+5eOuDHOpk36F80rbgxlx4+xty/U71e3wZZxVYHfZXmf0IRToBn1Crb8WvLM9OYj/Ur815g==} + /@next/swc-linux-x64-gnu@14.0.3: + resolution: {integrity: sha512-VM1aE1tJKLBwMGtyBR21yy+STfl0MapMQnNrXkxeyLs0GFv/kZqXS5Jw/TQ3TSUnbv0QPDf/X8sDXuMtSgG6eg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -4836,8 +4840,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-musl@13.5.3: - resolution: {integrity: sha512-DPw8nFuM1uEpbX47tM3wiXIR0Qa+atSzs9Q3peY1urkhofx44o7E1svnq+a5Q0r8lAcssLrwiM+OyJJgV/oj7g==} + /@next/swc-linux-x64-musl@14.0.3: + resolution: {integrity: sha512-64EnmKy18MYFL5CzLaSuUn561hbO1Gk16jM/KHznYP3iCIfF9e3yULtHaMy0D8zbHfxset9LTOv6cuYKJgcOxg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -4854,8 +4858,8 @@ packages: dev: false optional: true - /@next/swc-win32-arm64-msvc@13.5.3: - resolution: {integrity: sha512-zBPSP8cHL51Gub/YV8UUePW7AVGukp2D8JU93IHbVDu2qmhFAn9LWXiOOLKplZQKxnIPUkJTQAJDCWBWU4UWUA==} + /@next/swc-win32-arm64-msvc@14.0.3: + resolution: {integrity: sha512-WRDp8QrmsL1bbGtsh5GqQ/KWulmrnMBgbnb+59qNTW1kVi1nG/2ndZLkcbs2GX7NpFLlToLRMWSQXmPzQm4tog==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -4872,8 +4876,8 @@ packages: dev: false optional: true - /@next/swc-win32-ia32-msvc@13.5.3: - resolution: {integrity: sha512-ONcL/lYyGUj4W37D4I2I450SZtSenmFAvapkJQNIJhrPMhzDU/AdfLkW98NvH1D2+7FXwe7yclf3+B7v28uzBQ==} + /@next/swc-win32-ia32-msvc@14.0.3: + resolution: {integrity: sha512-EKffQeqCrj+t6qFFhIFTRoqb2QwX1mU7iTOvMyLbYw3QtqTw9sMwjykyiMlZlrfm2a4fA84+/aeW+PMg1MjuTg==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] @@ -4890,8 +4894,8 @@ packages: dev: false optional: true - /@next/swc-win32-x64-msvc@13.5.3: - resolution: {integrity: sha512-2Vz2tYWaLqJvLcWbbTlJ5k9AN6JD7a5CN2pAeIzpbecK8ZF/yobA39cXtv6e+Z8c5UJuVOmaTldEAIxvsIux/Q==} + /@next/swc-win32-x64-msvc@14.0.3: + resolution: {integrity: sha512-ERhKPSJ1vQrPiwrs15Pjz/rvDHZmkmvbf/BjPN/UCOI++ODftT0GtasDPi0j+y6PPJi5HsXw+dpRaXUaw4vjuQ==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -7096,7 +7100,17 @@ packages: '@aws-cdk/asset-awscli-v1': 2.2.200 '@aws-cdk/asset-kubectl-v20': 2.1.2 '@aws-cdk/asset-node-proxy-agent-v6': 2.0.1 + '@balena/dockerignore': 1.0.2 + case: 1.6.3 constructs: 10.2.69 + fs-extra: 11.1.1 + ignore: 5.2.4 + jsonschema: 1.4.1 + minimatch: 3.1.2 + punycode: 2.3.0 + semver: 7.5.4 + table: 6.8.1 + yaml: 1.10.2 dev: true bundledDependencies: - '@balena/dockerignore' @@ -12580,9 +12594,9 @@ packages: - babel-plugin-macros dev: false - /next@13.5.3(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-4Nt4HRLYDW/yRpJ/QR2t1v63UOMS55A38dnWv3UDOWGezuY0ZyFO1ABNbD7mulVzs9qVhgy2+ppjdsANpKP1mg==} - engines: {node: '>=16.14.0'} + /next@14.0.3(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-AbYdRNfImBr3XGtvnwOxq8ekVCwbFTv/UJoLwmaX89nk9i051AEY4/HAWzU0YpaTDw8IofUpmuIlvzWF13jxIw==} + engines: {node: '>=18.17.0'} hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 @@ -12595,26 +12609,25 @@ packages: sass: optional: true dependencies: - '@next/env': 13.5.3 + '@next/env': 14.0.3 '@swc/helpers': 0.5.2 busboy: 1.6.0 caniuse-lite: 1.0.30001525 - postcss: 8.4.14 + postcss: 8.4.31 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) styled-jsx: 5.1.1(@babel/core@7.22.11)(react@18.2.0) watchpack: 2.4.0 - zod: 3.21.4 optionalDependencies: - '@next/swc-darwin-arm64': 13.5.3 - '@next/swc-darwin-x64': 13.5.3 - '@next/swc-linux-arm64-gnu': 13.5.3 - '@next/swc-linux-arm64-musl': 13.5.3 - '@next/swc-linux-x64-gnu': 13.5.3 - '@next/swc-linux-x64-musl': 13.5.3 - '@next/swc-win32-arm64-msvc': 13.5.3 - '@next/swc-win32-ia32-msvc': 13.5.3 - '@next/swc-win32-x64-msvc': 13.5.3 + '@next/swc-darwin-arm64': 14.0.3 + '@next/swc-darwin-x64': 14.0.3 + '@next/swc-linux-arm64-gnu': 14.0.3 + '@next/swc-linux-arm64-musl': 14.0.3 + '@next/swc-linux-x64-gnu': 14.0.3 + '@next/swc-linux-x64-musl': 14.0.3 + '@next/swc-win32-arm64-msvc': 14.0.3 + '@next/swc-win32-ia32-msvc': 14.0.3 + '@next/swc-win32-x64-msvc': 14.0.3 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -13261,6 +13274,15 @@ packages: picocolors: 1.0.0 source-map-js: 1.0.2 + /postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.6 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: false + /preact-render-to-string@5.2.6(preact@10.17.1): resolution: {integrity: sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==} peerDependencies: