Skip to content

Commit 6c590a0

Browse files
Support Next.js in autoconfig and also delegate to open-next for wrangler deploy runs executed in open-next projects (#11301)
* Support Next.js in autoconfig * Update packages/wrangler/src/autoconfig/frameworks/next.ts Co-authored-by: Somhairle MacLeòid <smacleod@cloudflare.com> * update `AutoConfigFrameworkConfigurationError` class to extend `UserError` * add WORKER_SELF_REFERENCE service config * delegate deploy to open-next for open-next projects * move next to the right framework position * move scripts to `packageJsonScriptsOverrides` * fix packageJsonScriptsOverrides not being passed to buildOperationsSummary * add OpenNext to changesets * improve jsdoc comment for `maybeDelegateToOpenNextDeployCommand` * update OpenNext log * test whole `std` with single `toMatchInlineSnapshot` * rename probeForNextConfigPath to findNextConfigPath * Update packages/wrangler/src/deploy/open-next.ts Co-authored-by: Somhairle MacLeòid <smacleod@cloudflare.com> * move `OPEN_NEXT_DEPLOY` to `misc-variables` * move openNext delegation after autoconfig logic * simplify comment --------- Co-authored-by: Somhairle MacLeòid <smacleod@cloudflare.com>
1 parent afbd82e commit 6c590a0

File tree

18 files changed

+465
-11
lines changed

18 files changed

+465
-11
lines changed

.changeset/fair-words-repair.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"create-cloudflare": patch
3+
---
4+
5+
Support Next.js (via OpenNext) in `--experimental` mode

.changeset/loose-dragons-thank.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": minor
3+
---
4+
5+
Make `wrangler deploy` run `opennextjs-cloudflare deploy` when executed in an open-next project

.changeset/yellow-taxes-spend.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": minor
3+
---
4+
5+
Support Next.js (via OpenNext) projects in autoconfig

packages/create-cloudflare/e2e/tests/cli/cli.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,7 @@ describe("Create Cloudflare CLI", () => {
555555
npm create cloudflare -- --framework svelte -- --types=ts
556556
pnpm create cloudflare --framework svelte -- --types=ts
557557
Allowed Values:
558-
analog, angular, astro, docusaurus, gatsby, nuxt, qwik, react, react-router, redwood, solid, svelte, tanstack-start, vue
558+
analog, angular, astro, docusaurus, gatsby, next, nuxt, qwik, react, react-router, redwood, solid, svelte, tanstack-start, vue
559559
--platform=<value>
560560
Whether the application should be deployed to Pages or Workers. This is only applicable for Frameworks templates that support both Pages and Workers.
561561
Allowed Values:

packages/create-cloudflare/e2e/tests/frameworks/test-config.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,25 @@ function getExperimentalFrameworkTestConfig(
973973
flags: ["--skipTailwind"],
974974
verifyTypes: false,
975975
},
976+
{
977+
name: "next",
978+
argv: ["--platform", "workers"],
979+
flags: ["--yes"],
980+
testCommitMessage: true,
981+
unsupportedOSs: ["win32"],
982+
unsupportedPms: ["npm", "yarn"],
983+
verifyDeploy: {
984+
route: "/",
985+
expectedText: "Generated by create next app",
986+
},
987+
verifyPreview: {
988+
previewArgs: ["--inspector-port=0"],
989+
route: "/",
990+
expectedText: "Generated by create next app",
991+
},
992+
nodeCompat: true,
993+
verifyTypes: false,
994+
},
976995
];
977996
}
978997

packages/create-cloudflare/src/templates.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import workflowsTemplate from "templates/hello-world-workflows/c3";
3232
import helloWorldWorkerTemplate from "templates/hello-world/c3";
3333
import honoTemplate from "templates/hono/c3";
3434
import nextTemplate from "templates/next/c3";
35+
import nextExperimentalTemplate from "templates/next/experimental_c3";
3536
import nuxtTemplate from "templates/nuxt/c3";
3637
import openapiTemplate from "templates/openapi/c3";
3738
import preExistingTemplate from "templates/pre-existing/c3";
@@ -242,6 +243,7 @@ export function getFrameworkMap({ experimental = false }): TemplateMap {
242243
astro: astroTemplate,
243244
docusaurus: docusaurusTemplate,
244245
gatsby: gatsbyTemplate,
246+
next: nextExperimentalTemplate,
245247
nuxt: nuxtTemplate,
246248
qwik: qwikTemplate,
247249
react: reactTemplate,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { runFrameworkGenerator } from "frameworks/index";
2+
import type { TemplateConfig } from "../../src/templates";
3+
import type { C3Context } from "types";
4+
5+
const generate = async (ctx: C3Context) => {
6+
await runFrameworkGenerator(ctx, [
7+
ctx.project.name,
8+
"--skip-install",
9+
]);
10+
};
11+
12+
const envInterfaceName = "CloudflareEnv";
13+
const typesPath = "./cloudflare-env.d.ts";
14+
export default {
15+
configVersion: 1,
16+
id: "next",
17+
frameworkCli: "create-next-app",
18+
platform: "workers",
19+
displayName: "Next.js",
20+
generate,
21+
devScript: "dev",
22+
previewScript: "preview",
23+
deployScript: "deploy",
24+
typesPath,
25+
envInterfaceName,
26+
} as TemplateConfig;

packages/workers-utils/src/environment-variables/factory.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,10 @@ type VariableNames =
114114
/** Docker host configuration (handled separately from environment variable factory). */
115115
| "WRANGLER_DOCKER_HOST"
116116
/** Docker host configuration (handled separately from environment variable factory). */
117-
| "DOCKER_HOST";
117+
| "DOCKER_HOST"
118+
119+
/** Environment variable used to signal that the current process is being run by the open-next deploy command. */
120+
| "OPEN_NEXT_DEPLOY";
118121

119122
type DeprecatedNames =
120123
| "CF_ACCOUNT_ID"

packages/workers-utils/src/environment-variables/misc-variables.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,3 +328,11 @@ export const getWranglerHideBanner = getBooleanEnvironmentVariableFactory({
328328
export const getCloudflareEnv = getEnvironmentVariableFactory({
329329
variableName: "CLOUDFLARE_ENV",
330330
});
331+
332+
/**
333+
* `OPEN_NEXT_DEPLOY` is an environment variables that indicates that the current process is being
334+
* run by the open-next deploy command
335+
*/
336+
export const getOpenNextDeployFromEnv = getEnvironmentVariableFactory({
337+
variableName: "OPEN_NEXT_DEPLOY",
338+
});

packages/wrangler/src/__tests__/deploy.test.ts

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
vi,
2828
} from "vitest";
2929
import { getDetailsForAutoConfig } from "../autoconfig/details";
30+
import { getInstalledPackageVersion } from "../autoconfig/frameworks";
3031
import { Static } from "../autoconfig/frameworks/static";
3132
import { runAutoConfig } from "../autoconfig/run";
3233
import { printBundleSize } from "../deployment-bundle/bundle-reporter";
@@ -93,7 +94,7 @@ import type {
9394
WorkerMetadataBinding,
9495
} from "@cloudflare/workers-utils";
9596
import type { FormData } from "undici";
96-
import type { Mock } from "vitest";
97+
import type { Mock, MockInstance } from "vitest";
9798

9899
vi.mock("command-exists");
99100
vi.mock("../check/commands", async (importOriginal) => {
@@ -121,6 +122,9 @@ vi.mock("../package-manager", async (importOriginal) => ({
121122
vi.mock("../autoconfig/details");
122123
vi.mock("../autoconfig/run");
123124

125+
vi.mock("../autoconfig/frameworks");
126+
vi.mock("../autoconfig/c3-vendor/command");
127+
124128
describe("deploy", () => {
125129
mockAccountId();
126130
mockApiToken();
@@ -152,6 +156,7 @@ describe("deploy", () => {
152156
})
153157
);
154158
vi.mocked(fetchSecrets).mockResolvedValue([]);
159+
vi.mocked(getInstalledPackageVersion).mockReturnValue(undefined);
155160
});
156161

157162
afterEach(() => {
@@ -15905,6 +15910,123 @@ export default{
1590515910

1590615911
expect(deployOutputEntry.autoconfig_summary).toBeUndefined();
1590715912
});
15913+
15914+
describe("open-next delegation", () => {
15915+
async function mockOpenNextLikeProject() {
15916+
vi.mocked(getInstalledPackageVersion).mockReturnValue("1.14.4");
15917+
15918+
fs.mkdirSync("./.open-next/assets", { recursive: true });
15919+
fs.writeFileSync(
15920+
"./.open-next/worker.js",
15921+
"export default { fetch() { return new Response(''); } };"
15922+
);
15923+
15924+
await mockAUSRequest([]);
15925+
15926+
writeWorkerSource();
15927+
mockGetServiceByName("test-name", "production", "dash");
15928+
writeWranglerConfig(
15929+
{
15930+
main: ".open-next/worker.js",
15931+
compatibility_date: "2024-04-24",
15932+
compatibility_flags: [
15933+
"nodejs_compat",
15934+
"global_fetch_strictly_public",
15935+
],
15936+
assets: {
15937+
binding: "ASSETS",
15938+
directory: ".open-next/assets",
15939+
},
15940+
},
15941+
"./wrangler.jsonc"
15942+
);
15943+
mockSubDomainRequest();
15944+
mockUploadWorkerRequest({ expectedMainModule: "worker.js" });
15945+
mockGetServiceBindings("test-name", []);
15946+
mockGetServiceRoutes("test-name", []);
15947+
mockGetServiceCustomDomainRecords([]);
15948+
mockGetServiceSubDomainData("test-name", {
15949+
enabled: true,
15950+
previews_enabled: true,
15951+
});
15952+
mockGetServiceSchedules("test-name", { schedules: [] });
15953+
mockGetServiceMetadata("test-name", {
15954+
created_on: "2025-08-07T09:34:47.846308Z",
15955+
modified_on: "2025-08-08T10:48:12.688997Z",
15956+
script: {
15957+
created_on: "2025-08-07T09:34:47.846308Z",
15958+
modified_on: "2025-08-08T10:48:12.688997Z",
15959+
id: "my-worker-id",
15960+
compatibility_date: "2024-04-24",
15961+
},
15962+
} as unknown as ServiceMetadataRes["default_environment"]);
15963+
}
15964+
15965+
it("should delegate to open-next when run in an open-next project and set OPEN_NEXT_DEPLOY", async () => {
15966+
const runCommandSpy = (await import("../autoconfig/c3-vendor/command"))
15967+
.runCommand;
15968+
15969+
await mockOpenNextLikeProject();
15970+
15971+
await runWrangler("deploy");
15972+
15973+
expect(runCommandSpy).toHaveBeenCalledOnce();
15974+
const call = (runCommandSpy as unknown as MockInstance).mock.calls[0];
15975+
const [command, options] = call;
15976+
expect(command).toEqual(["npx", "opennextjs-cloudflare", "deploy"]);
15977+
expect(options).toMatchObject({
15978+
env: {
15979+
// Note: we want to ensure that OPEN_NEXT_DEPLOY has been set, this is not strictly necessary but it helps us
15980+
// ensure that we can't end up in an infinite wrangler<>open-next invokation loop
15981+
OPEN_NEXT_DEPLOY: "true",
15982+
},
15983+
});
15984+
15985+
expect(std).toMatchInlineSnapshot(`
15986+
Object {
15987+
"debug": "",
15988+
"err": "",
15989+
"info": "",
15990+
"out": "
15991+
⛅️ wrangler x.x.x
15992+
──────────────────
15993+
OpenNext project detected, calling \`opennextjs-cloudflare deploy\`",
15994+
"warn": "",
15995+
}
15996+
`);
15997+
});
15998+
15999+
it("should not delegate to open-next deploy when run in an open-next project and OPEN_NEXT_DEPLOY is set", async () => {
16000+
vi.stubEnv("OPEN_NEXT_DEPLOY", "1");
16001+
16002+
const runCommandSpy = (await import("../autoconfig/c3-vendor/command"))
16003+
.runCommand;
16004+
16005+
await mockOpenNextLikeProject();
16006+
16007+
await runWrangler("deploy");
16008+
16009+
expect(runCommandSpy).not.toHaveBeenCalledOnce();
16010+
16011+
expect(std.out).toMatchInlineSnapshot(`
16012+
"
16013+
⛅️ wrangler x.x.x
16014+
──────────────────
16015+
Total Upload: xx KiB / gzip: xx KiB
16016+
Worker Startup Time: 100 ms
16017+
Your Worker has access to the following bindings:
16018+
Binding Resource
16019+
env.ASSETS Assets
16020+
16021+
Uploaded test-name (TIMINGS)
16022+
Deployed test-name triggers (TIMINGS)
16023+
https://test-name.test-sub-domain.workers.dev
16024+
Current Version ID: Galaxy-Class"
16025+
`);
16026+
expect(std.err).toMatchInlineSnapshot(`""`);
16027+
expect(std.warn).toMatchInlineSnapshot(`""`);
16028+
});
16029+
});
1590816030
});
1590916031

1591016032
/** Write mock assets to the file system so they can be uploaded. */

0 commit comments

Comments
 (0)