Skip to content

feat(c3): scaffold Next.js apps with vinext instead of OpenNext - #14896

Open
scottbuscemi wants to merge 2 commits into
cloudflare:mainfrom
scottbuscemi:c3-next-use-vinext
Open

feat(c3): scaffold Next.js apps with vinext instead of OpenNext#14896
scottbuscemi wants to merge 2 commits into
cloudflare:mainfrom
scottbuscemi:c3-next-use-vinext

Conversation

@scottbuscemi

@scottbuscemi scottbuscemi commented Jul 28, 2026

Copy link
Copy Markdown

Summary

Makes create-cloudflare --framework=next configure vinext, while keeping opennext available with a flag.

Today C3's Next template only downloads the OpenNext remote template (github:opennextjs/opennextjs-cloudflare/create-cloudflare/next). The docs PR at cloudflare/cloudflare-docs#31887 recommends vinext as the default. This PR makes C3 do the same, with OpenNext retained as an opt-in variant.

Behavior

Interactive (--framework=next) prompts:

  1. vinext (recommended) — default
  2. OpenNext adapter — previous path

Non-interactive:

npm create cloudflare@latest my-app -- --framework=next --variant=vinext
npm create cloudflare@latest my-app -- --framework=next --variant=opennext

-y / --accept-defaults selects vinext.

Changes

  • templates/next/c3.ts (and experimental re-export):
    • Prompt / --variant for vinext | opennext
    • vinextcreate-vinext-app via runFrameworkGenerator with --platform cloudflare --data-cache none --yes --skip-install --disable-git + matching package-manager flag
    • opennext → existing OpenNext remote template (unchanged)
    • vinext gets a generated preview script (build && start) so C3's shared previewScript: "preview" works for both variants
  • Pin [email protected] in src/frameworks/package.json (keep create-next-app pin as well)
  • E2E: next (vinext) + next:opennext in both stable and experimental matrices; getFrameworkConfig tolerates non-platform labels like next:opennext
  • Changeset included

Generated project shapes

vinext (default)

{
  "scripts": {
    "dev": "vinext dev",
    "build": "vinext build",
    "start": "wrangler dev --config dist/server/wrangler.json",
    "preview": "<pm> run build && <pm> run start",
    "deploy": "vinext-cloudflare deploy --config dist/server/wrangler.json"
  }
}

opennext — same as today's template (opennextjs-cloudflare build/deploy/preview, etc.)

Notes / follow-ups for reviewers


Note

This is a contribution from an AI agent: opencode, xai/grok-4.5. Opened as a draft for human review before any merge consideration.

Point create-cloudflare --framework=next at create-vinext-app so the
generated project matches the recommended Next.js-on-Workers path
(vinext dev/build + vinext-cloudflare deploy) instead of the OpenNext
remote template.
@changeset-bot

changeset-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d4a4cba

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
create-cloudflare Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Prompt for vinext (default) vs OpenNext when scaffolding Next.js.
--variant=vinext|opennext and -y/--accept-defaults (vinext) cover the
non-interactive paths so OpenNext stays one flag away.
@scottbuscemi

Copy link
Copy Markdown
Author

Update: OpenNext kept as an opt-in variant

Per review feedback, C3 no longer drops OpenNext entirely. --framework=next now prompts:

  1. vinext (recommended) — default; also selected by -y / --accept-defaults
  2. OpenNext adapter — previous remote-template path

Non-interactive:

npm create cloudflare@latest my-app -- --framework=next --variant=vinext
npm create cloudflare@latest my-app -- --framework=next --variant=opennext

E2E covers both (next and next:opennext). Docs can now truthfully say OpenNext remains available via --variant=opennext (in addition to the manual adapter guide).

@scottbuscemi
scottbuscemi marked this pull request as ready for review July 28, 2026 20:23
@workers-devprod

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • @cloudflare/wrangler
Show detailed file reviewers
  • .changeset/c3-next-vinext.md: [@cloudflare/wrangler]
  • packages/create-cloudflare/e2e/helpers/framework-helpers.ts: [@cloudflare/wrangler]
  • packages/create-cloudflare/e2e/tests/frameworks/frameworks.test.ts: [@cloudflare/wrangler]
  • packages/create-cloudflare/e2e/tests/frameworks/test-config.ts: [@cloudflare/wrangler]
  • packages/create-cloudflare/src/frameworks/package.json: [@cloudflare/wrangler]
  • packages/create-cloudflare/templates/next/c3.ts: [@cloudflare/wrangler]
  • packages/create-cloudflare/templates/next/experimental_c3.ts: [@cloudflare/wrangler]

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

View 2 additional findings in Devin Review.

Open in Devin Review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 OpenNext Next.js end-to-end test fails on every run

The OpenNext framework test leaves type verification enabled while pointing at a type file the OpenNext scaffold never creates (typesPath "worker-configuration.d.ts", verifyTypes left at its default), so the test throws while reading a missing file and fails every run (packages/create-cloudflare/e2e/tests/frameworks/test-config.ts:366-381).
Impact: The stable OpenNext Next.js e2e test fails deterministically instead of passing.

Why verifyTypes always throws for this config

verifyTypes in packages/create-cloudflare/e2e/helpers/framework-helpers.ts:337-341 only skips when workersTypes === "none" or verify === false; neither holds here, so it runs and calls readFile(join(projectPath, typesPath)) with typesPath = "./worker-configuration.d.ts". The OpenNext scaffold generates cloudflare-env.d.ts, not worker-configuration.d.ts, so readFile throws and the test's outer catch calls expect.fail.

The experimental OpenNext test explicitly sets verifyTypes: false (with a comment) for exactly this reason, but this stable config omits it. Previously the template's typesPath was ./cloudflare-env.d.ts, so the old test passed.

(Refers to lines 380-381)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

const typesPath = "./cloudflare-env.d.ts";
// vinext generates wrangler types into worker-configuration.d.ts; OpenNext's
// remote template ships cloudflare-env.d.ts and its own cf-typegen script.
const typesPath = "./worker-configuration.d.ts";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 OpenNext Next.js projects hit a broken type-setup step

The location of the generated type file is hard-coded to a filename the OpenNext path never creates (typesPath = "./worker-configuration.d.ts" at packages/create-cloudflare/templates/next/c3.ts:122), so scaffolding an OpenNext project fails to update its TypeScript config and prints a failure warning.
Impact: Users who scaffold the OpenNext Next.js variant see a spurious "Failed to update tsconfig.json" warning and their project's TypeScript config may not reference the generated Cloudflare types.

Why the shared typesPath breaks OpenNext

The single typesPath value is used for both variants, but the two variants emit different files: vinext produces worker-configuration.d.ts while the OpenNext remote template ships/generates cloudflare-env.d.ts (see the comment at packages/create-cloudflare/templates/next/c3.ts:120-121).

During the non-experimental configure phase (packages/create-cloudflare/src/cli.ts:199-201), addTypesupdateTsConfig runs with ctx.template.typesPath. At packages/create-cloudflare/src/workers.ts:149-151 it does readFile("./worker-configuration.d.ts"), which throws for an OpenNext project (the file does not exist), and the surrounding try/catch at packages/create-cloudflare/src/workers.ts:184-186 swallows it as warn("Failed to update tsconfig.json."), skipping the tsconfig update entirely (including the node types addition for nodeCompat).

Before this PR, typesPath was ./cloudflare-env.d.ts, which matched the OpenNext output, so this step worked.

Prompt for agents
The Next.js template now serves two variants (vinext and opennext) that generate different type-declaration files: vinext produces worker-configuration.d.ts, while the OpenNext remote template ships/generates cloudflare-env.d.ts. However templates/next/c3.ts exports a single static typesPath = "./worker-configuration.d.ts" used by C3 core (updateTsConfig in src/workers.ts reads this file to update tsconfig types). For OpenNext projects this file does not exist, so readFile throws and updateTsConfig aborts with a 'Failed to update tsconfig.json' warning, skipping the tsconfig update. Consider making typesPath (and envInterfaceName) variant-aware, or guard the OpenNext path so C3 does not try to read/inject a worker-configuration.d.ts that OpenNext never produces (OpenNext already ships its own cf-typegen and tsconfig). Note the same typesPath is also consumed by the e2e verifyTypes helper, which reads join(projectPath, typesPath).
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants