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/smooth-cups-allow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@opennextjs/cloudflare": patch
---

Switch to bundled middleware
12 changes: 6 additions & 6 deletions packages/cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@
"url": "https://github.com/opennextjs/opennextjs-cloudflare/issues"
},
"homepage": "https://github.com/opennextjs/opennextjs-cloudflare",
"dependencies": {
"@dotenvx/dotenvx": "catalog:",
"@opennextjs/aws": "https://pkg.pr.new/@opennextjs/aws@798",
"enquirer": "^2.4.1",
"glob": "catalog:"
},
"devDependencies": {
"@cloudflare/workers-types": "catalog:",
"@eslint/js": "catalog:",
Expand All @@ -70,12 +76,6 @@
"typescript-eslint": "catalog:",
"vitest": "catalog:"
},
"dependencies": {
"@dotenvx/dotenvx": "catalog:",
"@opennextjs/aws": "^3.5.3",
"enquirer": "^2.4.1",
"glob": "catalog:"
},
"peerDependencies": {
"wrangler": "catalog:"
}
Expand Down
10 changes: 7 additions & 3 deletions packages/cloudflare/src/api/cloudflare-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ import { DOShardedTagCache } from "./durable-objects/sharded-tag-cache";

declare global {
interface CloudflareEnv {
// Asset binding
ASSETS?: Fetcher;

// Environment to use when loading Next `.env` files
// Default to "production"
NEXTJS_ENV?: string;

// KV used for the incremental cache
NEXT_CACHE_WORKERS_KV?: KVNamespace;
// D1 db used for the tag cache
Expand All @@ -24,9 +31,6 @@ declare global {
// Durables object namespace to use for the sharded tag cache
NEXT_CACHE_D1_SHARDED?: DurableObjectNamespace<DOShardedTagCache>;

// Asset binding
ASSETS?: Fetcher;

// Below are the potential environment variables that can be set by the user to configure the durable object queue handler
// The max number of revalidations that can be processed by the durable worker at the same time
MAX_REVALIDATION_BY_DURABLE_OBJECT?: string;
Expand Down
10 changes: 1 addition & 9 deletions packages/cloudflare/src/api/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,12 @@ export function defineCloudflareConfig(config: CloudflareOverrides = {}): OpenNe
override: {
wrapper: "cloudflare-node",
converter: "edge",
proxyExternalRequest: "fetch",
incrementalCache: resolveIncrementalCache(incrementalCache),
tagCache: resolveTagCache(tagCache),
queue: resolveQueue(queue),
},
},

middleware: {
external: true,
override: {
wrapper: "cloudflare-edge",
converter: "edge",
proxyExternalRequest: "fetch",
},
},
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,10 @@ async function generateBundle(
overrides,
}),

openNextExternalMiddlewarePlugin(path.join(options.openNextDistDir, "core/edgeFunctionHandler.js")),
// `openNextExternalMiddlewarePlugin` should only be used with an external middleware
...(config.middleware?.external
? [openNextExternalMiddlewarePlugin(path.join(options.openNextDistDir, "core/edgeFunctionHandler.js"))]
: []),

openNextEdgePlugins({
nextDir: path.join(options.appBuildOutputPath, ".next"),
Expand All @@ -247,6 +250,7 @@ async function generateBundle(
{
entryPoints: [path.join(options.openNextDistDir, "adapters", "server-adapter.js")],
outfile: path.join(outputPath, packagePath, `index.${outfileExt}`),
external: ["./middleware.mjs"],
banner: {
js: [
`globalThis.monorepoPackagePath = "${normalizePath(packagePath)}";`,
Expand Down
16 changes: 3 additions & 13 deletions packages/cloudflare/src/cli/build/utils/ensure-cf-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export function ensureCloudflareConfig(config: OpenNextConfig) {
const requirements = {
dftUseCloudflareWrapper: config.default?.override?.wrapper === "cloudflare-node",
dftUseEdgeConverter: config.default?.override?.converter === "edge",
dftUseFetchProxy: config.default?.override?.proxyExternalRequest === "fetch",
dftMaybeUseCache:
config.default?.override?.incrementalCache === "dummy" ||
typeof config.default?.override?.incrementalCache === "function",
Expand All @@ -20,10 +21,7 @@ export function ensureCloudflareConfig(config: OpenNextConfig) {
config.default?.override?.queue === "dummy" ||
config.default?.override?.queue === "direct" ||
typeof config.default?.override?.queue === "function",
mwIsMiddlewareExternal: config.middleware?.external == true,
mwUseCloudflareWrapper: config.middleware?.override?.wrapper === "cloudflare-edge",
mwUseEdgeConverter: config.middleware?.override?.converter === "edge",
mwUseFetchProxy: config.middleware?.override?.proxyExternalRequest === "fetch",
mwIsMiddlewareIntegrated: config.middleware === undefined,
};

if (config.default?.override?.queue === "direct") {
Expand All @@ -38,20 +36,12 @@ export function ensureCloudflareConfig(config: OpenNextConfig) {
override: {
wrapper: "cloudflare-node",
converter: "edge",
proxyExternalRequest: "fetch",
incrementalCache: "dummy" | function,
tagCache: "dummy",
queue: "dummy" | "direct" | function,
},
},

middleware: {
external: true,
override: {
wrapper: "cloudflare-edge",
converter: "edge",
proxyExternalRequest: "fetch",
},
},
}\n\n`.replace(/^ {8}/gm, "")
);
}
Expand Down
4 changes: 3 additions & 1 deletion packages/cloudflare/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ async function runCommand(args: Arguments) {
const openNextDistDir = path.dirname(require.resolve("@opennextjs/aws/index.js"));

await createOpenNextConfigIfNotExistent(baseDir);
const { config, buildDir } = await compileOpenNextConfig(baseDir);
const { config, buildDir } = await compileOpenNextConfig(baseDir, undefined, {
compileEdge: true,
});

ensureCloudflareConfig(config);

Expand Down
37 changes: 16 additions & 21 deletions packages/cloudflare/src/cli/templates/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ import { AsyncLocalStorage } from "node:async_hooks";
import type { CloudflareContext } from "../../api";
// @ts-expect-error: resolved by wrangler build
import * as nextEnvVars from "./env/next-env.mjs";
// @ts-expect-error: resolved by wrangler build
import { handler as middlewareHandler } from "./middleware/handler.mjs";
// @ts-expect-error: resolved by wrangler build
import { handler as serverHandler } from "./server-functions/default/handler.mjs";

const cloudflareContextALS = new AsyncLocalStorage<CloudflareContext>();

Expand All @@ -30,7 +26,7 @@ export default {
return cloudflareContextALS.run({ env, ctx, cf: request.cf }, async () => {
const url = new URL(request.url);

populateProcessEnv(url, env.NEXTJS_ENV);
populateProcessEnv(url, env);

// Serve images in development.
// Note: "/cdn-cgi/image/..." requests do not reach production workers.
Expand All @@ -42,45 +38,44 @@ export default {
const imageUrl = m.groups!.url!;
return imageUrl.match(/^https?:\/\//)
? fetch(imageUrl, { cf: { cacheEverything: true } })
: env.ASSETS.fetch(new URL(`/${imageUrl}`, url));
: env.ASSETS?.fetch(new URL(`/${imageUrl}`, url));
}

// Fallback for the Next default image loader.
if (url.pathname === "/_next/image") {
const imageUrl = url.searchParams.get("url") ?? "";
return imageUrl.startsWith("/")
? env.ASSETS.fetch(new URL(imageUrl, request.url))
? env.ASSETS?.fetch(new URL(imageUrl, request.url))
: fetch(imageUrl, { cf: { cacheEverything: true } });
}

// The Middleware handler can return either a `Response` or a `Request`:
// - `Response`s should be returned early
// - `Request`s are handled by the Next server
const reqOrResp = await middlewareHandler(request, env, ctx);

if (reqOrResp instanceof Response) {
return reqOrResp;
}
// @ts-expect-error: resolved by wrangler build
const { handler } = await import("./server-functions/default/handler.mjs");

return serverHandler(reqOrResp, env, ctx);
return handler(request, env, ctx);
});
},
} as ExportedHandler<{ ASSETS: Fetcher; NEXTJS_ENV?: string }>;
} as ExportedHandler<CloudflareEnv>;

/**
* Populate process.env with:
* - the environment variables and secrets from the cloudflare platform
* - the variables from Next .env* files
* - the origin resolver information
*
* Note that cloudflare env string values are copied by the middleware handler.
*/
function populateProcessEnv(url: URL, nextJsEnv?: string) {
function populateProcessEnv(url: URL, env: CloudflareEnv) {
if (processEnvPopulated) {
return;
}
processEnvPopulated = true;
const mode = nextJsEnv ?? "production";

for (const [key, value] of Object.entries(env)) {
if (typeof value === "string") {
process.env[key] = value;
}
}

const mode = env.NEXTJS_ENV ?? "production";
if (nextEnvVars[mode]) {
for (const key in nextEnvVars[mode]) {
process.env[key] = nextEnvVars[mode][key];
Expand Down
Loading