Skip to content

Add support for node middleware in cloudflare - #38

Open
conico974 wants to merge 18 commits into
mainfrom
conico/node-middleware
Open

Add support for node middleware in cloudflare#38
conico974 wants to merge 18 commits into
mainfrom
conico/node-middleware

Conversation

@conico974

@conico974 conico974 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Add support for node middleware in cloudflare


Open in Devin Review

@pkg-pr-new

pkg-pr-new Bot commented Jul 15, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/opennextjs/adapters-api/@opennextjs/aws@65cdda7
npm i https://pkg.pr.new/opennextjs/adapters-api/@opennextjs/cloudflare@65cdda7
npm i https://pkg.pr.new/opennextjs/adapters-api/@opennextjs/core@65cdda7

commit: 65cdda7

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 1 additional finding in Devin Review.

Open in Devin Review

Comment on lines +95 to +100
...(config.middleware?.external
? [
openNextExternalMiddlewarePlugin(
path.join(buildOpts.openNextDistDir, "core/edgeFunctionHandler.js")
),
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Middleware handler plugin added twice with conflicting target paths in Cloudflare builds

The external-middleware plugin is registered twice with different handler paths (openNextExternalMiddlewarePlugin(...) at packages/core/src/build/middleware/buildNodeMiddleware.ts:137-139 and again via additionalPlugins at packages/cloudflare/src/cli/adapter.ts:95-100), so the first plugin wins and the adapter's intended handler is silently ignored.

Impact: On Cloudflare, the middleware bundle may use the wrong handler module, potentially breaking middleware execution at runtime.

Duplicate esbuild plugin with conflicting resolve targets

The core function buildExternalNodeMiddleware unconditionally adds openNextExternalMiddlewarePlugin pointing to core/nodeMiddlewareHandler.js at packages/core/src/build/middleware/buildNodeMiddleware.ts:137-139. Then it spreads ...additionalPlugins at line 140, which for the Cloudflare adapter includes another openNextExternalMiddlewarePlugin pointing to core/edgeFunctionHandler.js (see packages/cloudflare/src/cli/adapter.ts:95-100).

Since buildExternalNodeMiddleware is only called when config.middleware?.external is true, the conditional guard in the Cloudflare adapter's middlewareBundle.additionalPlugins (config.middleware?.external ? [...] : []) always evaluates to true in this code path, meaning the plugin is always duplicated.

In esbuild, plugins are processed in registration order, so the core's plugin (registered first) resolves the import to nodeMiddlewareHandler.js, and the adapter's plugin (registered second) never gets a chance to redirect it to edgeFunctionHandler.js.

Prompt for agents
The Cloudflare adapter's middlewareBundle.additionalPlugins includes openNextExternalMiddlewarePlugin with edgeFunctionHandler.js, but the core's buildExternalNodeMiddleware (packages/core/src/build/middleware/buildNodeMiddleware.ts:137-139) already unconditionally adds the same plugin type with nodeMiddlewareHandler.js. This results in a duplicate plugin where the core's version takes precedence.

Two possible fixes:
1. Remove the openNextExternalMiddlewarePlugin from the Cloudflare adapter's middlewareBundle.additionalPlugins, since the core already adds it. But this only works if nodeMiddlewareHandler.js is the correct handler for Cloudflare middleware.
2. If the Cloudflare adapter needs edgeFunctionHandler.js instead, then the core's buildExternalNodeMiddleware should NOT hardcode the plugin, and instead let the adapter provide it via additionalPlugins. This would require removing lines 137-139 from buildNodeMiddleware.ts and ensuring all adapters that use external node middleware include the plugin in their middlewareBundle.additionalPlugins.

The right approach depends on whether Cloudflare middleware should use nodeMiddlewareHandler.js or edgeFunctionHandler.js.
Open in Devin Review

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

@evanlong-me

Copy link
Copy Markdown

Independent validation (build + local runtime)

I hit this via Next 16 proxy.ts on Cloudflare (ERROR Node.js middleware is not currently supported) and tested this branch directly from source (conico/node-middleware @ e34ef09), because the published preview tarball is not installable outside the monorepo (@opennextjs/core@0.1.0 is not on npm; https://pkg.pr.new/@opennextjs/cloudflare@38 currently resolves to the old opennextjs-cloudflare PR #38 / v1.5.3).

Baseline (released @opennextjs/cloudflare@1.20.2)

Minimal Next 16.2.11 app with proxy.ts fails at adapter build:

ƒ Proxy (Middleware)
ERROR Node.js middleware is not currently supported. Consider switching to Edge Middleware.

This PR

Built the monorepo packages and ran the existing e2e app that already uses examples-cloudflare/e2e/app-router/proxy.ts:

# from repo root after pnpm install / package build
cd examples-cloudflare/e2e/app-router
node ../../../packages/cloudflare/dist/cli/index.js build
node ../../../packages/cloudflare/dist/cli/index.js preview -- --port 8799

Build: success. Output includes external middleware bundling:

Bundling middleware function...
Copying adapter files for external middleware...
Middleware created
ƒ Proxy (Middleware)
Worker saved in `.open-next/worker.js`

Worker entry wires middleware:

import { handler as middlewareHandler } from "./middleware/handler.mjs";

Runtime checks against local preview (http://127.0.0.1:8799):

Route Result
HEAD / 200, middleware cookies (from=middleware, with=love) + response-header: response-header
HEAD /redirect 307/redirect-destination, Set-Cookie: test=success
HEAD /cookies 200, Set-Cookie: foo=bar
GET /api/middleware 200 {"hello":"middleware"}
HEAD /rewrite 500 SyntaxError: Unexpected end of JSON input (may be HEAD-specific; flagging)

So the core Node/proxy.ts path works end-to-end for cookies / redirect / direct middleware response. This unblocks Next 16 apps that can no longer opt proxy.ts back to Edge.

Merge blockers I can see

  1. PR is stacked on conico/share-build (depends on Make a default overridable adapter in core #35), not main.
  2. GitHub currently reports mergeable_state: dirty vs conico/share-build — needs rebase.
  3. Preview install story is broken for external testers (@opennextjs/core not published; short pkg.pr.new URL points at the wrong repo). Publishing paired @opennextjs/cloudflare + @opennextjs/core previews (or documenting monorepo-only testing) would get more validation.

Happy to re-test after rebase, or run a fuller Playwright pass if useful. Real production apps (including mine) are still stuck on the middleware.ts deprecation workaround until this lands.

…y into core adapter

feat: enhance build process with additional server bundle customization options

chore: update package.json scripts for improved build and testing workflow

test: add unit tests for adapter build process and server bundle generation

fix: ensure proper handling of external dependencies and edge configuration in server bundle
…udflare specific overrides"

This reverts commit 37c4d90.
…ad of throwing

- Extract ValidateConfigResult type with success/message/shouldThrow/level
- Convert validateFunctionOptions and validateSplittedFunctionOptions to return result objects
- Remove logger dependency from validateConfig.ts
- Preserve compatibilityMatrix, TODO comment, @ts-expect-error pragmas
- Add 5 characterization tests in validateConfig.spec.ts
- No caller impact: compileConfig.ts is the sole importer (updated in T3)
- Export OpenNextOutput interface (was internal)
- Extract buildOpenNextOutput(buildOpts) for construction-only (no fs write)
- Keep legacy generateOutput as thin wrapper (construction + file write)
- Preserve all construction logic verbatim, including @ts-expect-error
- Add 3 characterization tests in generateOutput.spec.ts
- Backward compatible: byte-equivalent output to today
- Replace bare validateConfig(config) call with result-handling block
- Throw on shouldThrow:true (bad routes — preserves existing behavior)
- Log at appropriate level on shouldThrow:false (level field from T1)
- All 3 export signatures and edge-runtime detection block unchanged
- Direct callers (aws/build.ts, cloudflare/utils.ts) unaffected
…OpenNextAdapterOptions

- Make OpenNextAdapterOptions<T = OpenNextOutput> and buildAdapter<T> generic
- Add validateConfig override hook (runs after callback in modifyConfig)
- Add generateOutput override hook (returns T, gated by skipGenerateOutput)
- buildAdapter serializes override return via fs.writeFileSync (override never touches fs)
- Default path uses buildOpenNextOutput (extracted in T2)
- Add 5 new tests covering override behaviors + default path + skipGenerateOutput
- All 16 existing adapter tests preserved; AWS/Cloudflare adapters compile with default T
When an adapter config specifies full package-specifier paths (e.g.,
@opennextjs/aws/overrides/wrappers/aws-lambda.js), esbuild cannot
resolve them during bundling. Use createRequire(args.path).resolve()
in the openNextResolvePlugin to convert package specifiers to
filesystem-relative paths at build time, falling back to the original
value if resolution fails.

This fixes the openbuild:local build error:
  ERROR: Could not resolve "@opennextjs/aws/overrides/wrappers/aws-lambda.js"
  ERROR: Could not resolve "@opennextjs/aws/overrides/tagCache/dynamodb.js"

Added test I verifying resolution of a mock package in node_modules.
@conico974
conico974 force-pushed the conico/share-build branch from 4e9a367 to cd8f83e Compare August 2, 2026 09:46
@conico974
conico974 force-pushed the conico/node-middleware branch from e34ef09 to 65cdda7 Compare August 2, 2026 09:54
IsmaelSandoval78 added a commit to IsmaelSandoval78/paddockintel-dashboard that referenced this pull request Aug 25, 2026
…dflare

Next.js 16 forces proxy.ts onto the Node.js middleware runtime with no
opt-out, and @opennextjs/cloudflare (1.20.2) doesn't support that yet —
verified locally, `opennextjs-cloudflare build` fails on a real proxy.ts.
middleware.ts + runtime: 'experimental-edge' builds clean on both next
build and opennextjs-cloudflare build, same routing logic unchanged.

Revert to proxy.ts once opennextjs/adapters-api#38 or
opennextjs/opennextjs-cloudflare#1320 (or their successor) ships on npm —
see docs/CLOUDFLARE-MIGRATION.md for the full investigation and why those
specific PR numbers, not "whenever OpenNext adds support" in general.
Base automatically changed from conico/share-build to main August 28, 2026 10:57
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