diff --git a/.changeset/evil-rabbits-relax.md b/.changeset/evil-rabbits-relax.md
new file mode 100644
index 00000000..7eeaa752
--- /dev/null
+++ b/.changeset/evil-rabbits-relax.md
@@ -0,0 +1,5 @@
+---
+"@opennextjs/cloudflare": patch
+---
+
+fix: Respect trailing slash config for \_next/image route in worker
diff --git a/examples/playground15/app/api/signal/revalidate/route.ts b/examples/playground15/app/api/signal/revalidate/route.ts
index 18128538..fc4de326 100644
--- a/examples/playground15/app/api/signal/revalidate/route.ts
+++ b/examples/playground15/app/api/signal/revalidate/route.ts
@@ -3,7 +3,7 @@ import { revalidatePath } from "next/cache";
export const dynamic = "force-dynamic";
export async function GET() {
- revalidatePath("/signal");
+ revalidatePath("/signal/");
return new Response("ok");
}
diff --git a/examples/playground15/app/image/page.tsx b/examples/playground15/app/image/page.tsx
new file mode 100644
index 00000000..7613f59c
--- /dev/null
+++ b/examples/playground15/app/image/page.tsx
@@ -0,0 +1,11 @@
+import Image from "next/image";
+
+import tomineImg from "../../public/tomine.webp";
+
+export default function Page() {
+ return (
+
+
+
+ );
+}
diff --git a/examples/playground15/e2e/base.spec.ts b/examples/playground15/e2e/base.spec.ts
index f67b1504..be2435c6 100644
--- a/examples/playground15/e2e/base.spec.ts
+++ b/examples/playground15/e2e/base.spec.ts
@@ -46,9 +46,9 @@ test.describe("playground/base", () => {
});
test("returns correct information about the request from a route handler", async ({ page, baseURL }) => {
- const res = await page.request.get("/api/request");
+ const res = await page.request.get("/api/request/");
// Next.js can fall back to `localhost:3000` or `n` if it doesn't get the host - neither of these are expected.
- const expectedURL = `${baseURL}/api/request`;
+ const expectedURL = `${baseURL}/api/request/`;
await expect(res.json()).resolves.toEqual({ nextUrl: expectedURL, url: expectedURL });
});
diff --git a/examples/playground15/e2e/image.test.ts b/examples/playground15/e2e/image.test.ts
new file mode 100644
index 00000000..46ed08c2
--- /dev/null
+++ b/examples/playground15/e2e/image.test.ts
@@ -0,0 +1,10 @@
+import { test, expect } from "@playwright/test";
+
+test.describe("next/image with trailing slash", () => {
+ test("next/image with trailing slash", async ({ page }) => {
+ await page.goto("/image");
+ await expect(page.getByAltText("Picture of Tomine")).toBeVisible();
+ // The trailing slash should only be there if trailingSlash is enabled in next.config.ts
+ expect(await page.getAttribute("img", "src")).toMatch(/^\/_next\/image\//);
+ });
+});
diff --git a/examples/playground15/next.config.mjs b/examples/playground15/next.config.ts
similarity index 81%
rename from examples/playground15/next.config.mjs
rename to examples/playground15/next.config.ts
index 0d2f9dfd..5a9fdb88 100644
--- a/examples/playground15/next.config.mjs
+++ b/examples/playground15/next.config.ts
@@ -1,9 +1,9 @@
import { initOpenNextCloudflareForDev, getDeploymentId } from "@opennextjs/cloudflare";
+import { NextConfig } from "next";
initOpenNextCloudflareForDev();
-/** @type {import('next').NextConfig} */
-const nextConfig = {
+const nextConfig: NextConfig = {
typescript: { ignoreBuildErrors: true },
eslint: { ignoreDuringBuilds: true },
experimental: {
@@ -11,6 +11,7 @@ const nextConfig = {
serverSourceMaps: true,
},
deploymentId: getDeploymentId(),
+ trailingSlash: true,
};
export default nextConfig;
diff --git a/examples/playground15/public/tomine.webp b/examples/playground15/public/tomine.webp
new file mode 100644
index 00000000..7b994ae3
Binary files /dev/null and b/examples/playground15/public/tomine.webp differ
diff --git a/packages/cloudflare/src/cli/build/open-next/compile-init.ts b/packages/cloudflare/src/cli/build/open-next/compile-init.ts
index 5dc2f9fa..6ccf4ffd 100644
--- a/packages/cloudflare/src/cli/build/open-next/compile-init.ts
+++ b/packages/cloudflare/src/cli/build/open-next/compile-init.ts
@@ -17,6 +17,7 @@ export async function compileInit(options: BuildOptions, wranglerConfig: Unstabl
const nextConfig = loadConfig(path.join(options.appBuildOutputPath, ".next"));
const basePath = nextConfig.basePath ?? "";
const deploymentId = nextConfig.deploymentId ?? "";
+ const trailingSlash = nextConfig.trailingSlash ?? false;
await build({
entryPoints: [initPath],
@@ -31,6 +32,7 @@ export async function compileInit(options: BuildOptions, wranglerConfig: Unstabl
__NEXT_BASE_PATH__: JSON.stringify(basePath),
__ASSETS_RUN_WORKER_FIRST__: JSON.stringify(wranglerConfig.assets?.run_worker_first ?? false),
__DEPLOYMENT_ID__: JSON.stringify(deploymentId),
+ __TRAILING_SLASH__: JSON.stringify(trailingSlash),
},
});
}
diff --git a/packages/cloudflare/src/cli/templates/init.ts b/packages/cloudflare/src/cli/templates/init.ts
index 49ac9fff..fc0a1d11 100644
--- a/packages/cloudflare/src/cli/templates/init.ts
+++ b/packages/cloudflare/src/cli/templates/init.ts
@@ -97,6 +97,7 @@ function initRuntime() {
__BUILD_TIMESTAMP_MS__,
__NEXT_BASE_PATH__,
__ASSETS_RUN_WORKER_FIRST__,
+ __TRAILING_SLASH__,
// The external middleware will use the convertTo function of the `edge` converter
// by default it will try to fetch the request, but since we are running everything in the same worker
// we need to use the request as is.
@@ -155,4 +156,6 @@ declare global {
var __ASSETS_RUN_WORKER_FIRST__: boolean | string[] | undefined;
// Deployment ID
var __DEPLOYMENT_ID__: string;
+ // Next trailingSlash config
+ var __TRAILING_SLASH__: boolean;
}
diff --git a/packages/cloudflare/src/cli/templates/worker.ts b/packages/cloudflare/src/cli/templates/worker.ts
index 892b957f..6d2c3fdf 100644
--- a/packages/cloudflare/src/cli/templates/worker.ts
+++ b/packages/cloudflare/src/cli/templates/worker.ts
@@ -39,7 +39,10 @@ export default {
}
// Fallback for the Next default image loader.
- if (url.pathname === `${globalThis.__NEXT_BASE_PATH__}/_next/image`) {
+ if (
+ url.pathname ===
+ `${globalThis.__NEXT_BASE_PATH__}/_next/image${globalThis.__TRAILING_SLASH__ ? "/" : ""}`
+ ) {
const imageUrl = url.searchParams.get("url") ?? "";
return await fetchImage(env.ASSETS, imageUrl, ctx);
}