Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/gold-clouds-sit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@opennextjs/cloudflare": patch
---

fix `res.revalidate` not working in page router api route
19 changes: 19 additions & 0 deletions examples/e2e/pages-router/e2e/revalidate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { expect, test } from "@playwright/test";

test("`res.revalidate` should revalidate the ssg page", async ({ page, request }) => {
await page.goto("/ssg/");
const initialTime = await page.getByTestId("time").textContent();

await page.reload();
const newTime = await page.getByTestId("time").textContent();

expect(initialTime).toBe(newTime);

const revalidateResult = await request.post("/api/revalidate");
expect(revalidateResult.status()).toBe(200);
expect(await revalidateResult.json()).toEqual({ hello: "OpenNext rocks!" });

await page.reload();
const revalidatedTime = await page.getByTestId("time").textContent();
expect(initialTime).not.toBe(revalidatedTime);
});
11 changes: 11 additions & 0 deletions examples/e2e/pages-router/src/pages/api/revalidate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { NextApiRequest, NextApiResponse } from "next";

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await res.revalidate("/ssg/");
return res.json({ hello: "OpenNext rocks!" });
} catch (e) {
console.error(e);
return res.status(500).json({ error: "An error occurred" });
}
}
21 changes: 21 additions & 0 deletions examples/e2e/pages-router/src/pages/ssg/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { InferGetStaticPropsType } from "next";
import Link from "next/link";

export async function getStaticProps() {
return {
props: {
time: new Date().toISOString(),
},
};
}

export default function Page({ time }: InferGetStaticPropsType<typeof getStaticProps>) {
return (
<div>
<div className="flex" data-testid="time">
Time: {time}
</div>
<Link href="/">Home</Link>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type { FunctionOptions, SplittedFunctionOptions } from "@opennextjs/aws/t
import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";
import type { Plugin } from "esbuild";

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

interface CodeCustomization {
Expand Down Expand Up @@ -186,6 +187,8 @@ async function generateBundle(
patchFetchCacheSetMissingWaitUntil,
patchFetchCacheForISR,
patchUnstableCacheForISR,
// Cloudflare specific patches
patchResRevalidate,
...additionalCodePatches,
]);

Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* This patch will replace code used for `res.revalidate` in page router
* Without the patch it uses `fetch` to make a call to itself, which doesn't work once deployed in cloudflare workers
* This patch will replace this fetch by a call to `NEXT_CACHE_REVALIDATION_WORKER` service binding
*/
import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js";
import type { CodePatcher } from "@opennextjs/aws/build/patch/codePatcher.js";
import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";

export const rule = `
rule:
kind: await_expression
inside:
kind: if_statement
stopBy: end
has:
kind: parenthesized_expression
has: { kind: property_identifier , stopBy: end, regex: trustHostHeader}
has:
kind: call_expression
all:
- has: {kind: identifier, pattern: fetch}
- has:
kind: arguments
all:
- has:
kind: object
all:
- has:
kind: pair
all:
- has: {kind: property_identifier, regex: method }
- has: {kind: string, regex: 'HEAD'}
- has:
kind: pair
all:
- has: {kind: property_identifier, regex: headers}
- has: {kind: identifier, pattern: $HEADERS}
- has:
kind: template_string
all:
- has:
kind: string_fragment
regex: https://
- has:
kind: template_substitution
all:
- has: { kind: identifier, stopBy: end, pattern: $REQ }
- has:
kind: property_identifier
regex: headers
stopBy: end
- has:
kind: property_identifier
regex: host
stopBy: end
- has:
kind: template_substitution
pattern: $URL_PATH
has:
kind: identifier

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})
`;

export const patchResRevalidate: CodePatcher = {
name: "patch-res-revalidate",
patches: [
{
versions: ">=14.2.0",
field: {
pathFilter: getCrossPlatformPathRegex(
String.raw`(pages-api\.runtime\.prod\.js|node/api-resolver\.js)$`,
{
escape: false,
}
),
contentFilter: /\.trustHostHeader/,
patchCode: async ({ code }) => patchCode(code, rule),
},
},
],
};