Skip to content

Commit 9b0db4d

Browse files
authored
fix res.revalidate (#481)
1 parent e9dc621 commit 9b0db4d

File tree

7 files changed

+290
-0
lines changed

7 files changed

+290
-0
lines changed

.changeset/gold-clouds-sit.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@opennextjs/cloudflare": patch
3+
---
4+
5+
fix `res.revalidate` not working in page router api route
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { expect, test } from "@playwright/test";
2+
3+
test("`res.revalidate` should revalidate the ssg page", async ({ page, request }) => {
4+
await page.goto("/ssg/");
5+
const initialTime = await page.getByTestId("time").textContent();
6+
7+
await page.reload();
8+
const newTime = await page.getByTestId("time").textContent();
9+
10+
expect(initialTime).toBe(newTime);
11+
12+
const revalidateResult = await request.post("/api/revalidate");
13+
expect(revalidateResult.status()).toBe(200);
14+
expect(await revalidateResult.json()).toEqual({ hello: "OpenNext rocks!" });
15+
16+
await page.reload();
17+
const revalidatedTime = await page.getByTestId("time").textContent();
18+
expect(initialTime).not.toBe(revalidatedTime);
19+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { NextApiRequest, NextApiResponse } from "next";
2+
3+
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
4+
try {
5+
await res.revalidate("/ssg/");
6+
return res.json({ hello: "OpenNext rocks!" });
7+
} catch (e) {
8+
console.error(e);
9+
return res.status(500).json({ error: "An error occurred" });
10+
}
11+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { InferGetStaticPropsType } from "next";
2+
import Link from "next/link";
3+
4+
export async function getStaticProps() {
5+
return {
6+
props: {
7+
time: new Date().toISOString(),
8+
},
9+
};
10+
}
11+
12+
export default function Page({ time }: InferGetStaticPropsType<typeof getStaticProps>) {
13+
return (
14+
<div>
15+
<div className="flex" data-testid="time">
16+
Time: {time}
17+
</div>
18+
<Link href="/">Home</Link>
19+
</div>
20+
);
21+
}

packages/cloudflare/src/cli/build/open-next/createServerBundle.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import type { FunctionOptions, SplittedFunctionOptions } from "@opennextjs/aws/t
2828
import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";
2929
import type { Plugin } from "esbuild";
3030

31+
import { patchResRevalidate } from "../patches/plugins/res-revalidate.js";
3132
import { normalizePath } from "../utils/index.js";
3233

3334
interface CodeCustomization {
@@ -186,6 +187,8 @@ async function generateBundle(
186187
patchFetchCacheSetMissingWaitUntil,
187188
patchFetchCacheForISR,
188189
patchUnstableCacheForISR,
190+
// Cloudflare specific patches
191+
patchResRevalidate,
189192
...additionalCodePatches,
190193
]);
191194

packages/cloudflare/src/cli/build/patches/plugins/res-revalidate.spec.ts

Lines changed: 148 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/**
2+
* This patch will replace code used for `res.revalidate` in page router
3+
* Without the patch it uses `fetch` to make a call to itself, which doesn't work once deployed in cloudflare workers
4+
* This patch will replace this fetch by a call to `NEXT_CACHE_REVALIDATION_WORKER` service binding
5+
*/
6+
import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js";
7+
import type { CodePatcher } from "@opennextjs/aws/build/patch/codePatcher.js";
8+
import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";
9+
10+
export const rule = `
11+
rule:
12+
kind: await_expression
13+
inside:
14+
kind: if_statement
15+
stopBy: end
16+
has:
17+
kind: parenthesized_expression
18+
has: { kind: property_identifier , stopBy: end, regex: trustHostHeader}
19+
has:
20+
kind: call_expression
21+
all:
22+
- has: {kind: identifier, pattern: fetch}
23+
- has:
24+
kind: arguments
25+
all:
26+
- has:
27+
kind: object
28+
all:
29+
- has:
30+
kind: pair
31+
all:
32+
- has: {kind: property_identifier, regex: method }
33+
- has: {kind: string, regex: 'HEAD'}
34+
- has:
35+
kind: pair
36+
all:
37+
- has: {kind: property_identifier, regex: headers}
38+
- has: {kind: identifier, pattern: $HEADERS}
39+
- has:
40+
kind: template_string
41+
all:
42+
- has:
43+
kind: string_fragment
44+
regex: https://
45+
- has:
46+
kind: template_substitution
47+
all:
48+
- has: { kind: identifier, stopBy: end, pattern: $REQ }
49+
- has:
50+
kind: property_identifier
51+
regex: headers
52+
stopBy: end
53+
- has:
54+
kind: property_identifier
55+
regex: host
56+
stopBy: end
57+
- has:
58+
kind: template_substitution
59+
pattern: $URL_PATH
60+
has:
61+
kind: identifier
62+
63+
fix: await (await import("@opennextjs/cloudflare")).getCloudflareContext().env.NEXT_CACHE_REVALIDATION_WORKER.fetch(\`\${$REQ.headers.host.includes("localhost") ? "http":"https" }://\${$REQ.headers.host}$URL_PATH\`,{method:'HEAD', headers:$HEADERS})
64+
`;
65+
66+
export const patchResRevalidate: CodePatcher = {
67+
name: "patch-res-revalidate",
68+
patches: [
69+
{
70+
versions: ">=14.2.0",
71+
field: {
72+
pathFilter: getCrossPlatformPathRegex(
73+
String.raw`(pages-api\.runtime\.prod\.js|node/api-resolver\.js)$`,
74+
{
75+
escape: false,
76+
}
77+
),
78+
contentFilter: /\.trustHostHeader/,
79+
patchCode: async ({ code }) => patchCode(code, rule),
80+
},
81+
},
82+
],
83+
};

0 commit comments

Comments
 (0)