From ff5ee51aa60c5d28fcacec89d05ff6ebf79baa75 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 28 Jul 2025 11:08:29 +0200 Subject: [PATCH 1/3] fix: fail build/deploy when using unsupported Node.js Midleware --- src/build/content/server.ts | 18 +++++++++++++++++ tests/fixtures/middleware-node/app/layout.js | 12 +++++++++++ tests/fixtures/middleware-node/app/page.js | 7 +++++++ tests/fixtures/middleware-node/middleware.ts | 9 +++++++++ tests/fixtures/middleware-node/next.config.js | 12 +++++++++++ tests/fixtures/middleware-node/package.json | 20 +++++++++++++++++++ tests/integration/edge-handler.test.ts | 11 ++++++++++ 7 files changed, 89 insertions(+) create mode 100644 tests/fixtures/middleware-node/app/layout.js create mode 100644 tests/fixtures/middleware-node/app/page.js create mode 100644 tests/fixtures/middleware-node/middleware.ts create mode 100644 tests/fixtures/middleware-node/next.config.js create mode 100644 tests/fixtures/middleware-node/package.json diff --git a/src/build/content/server.ts b/src/build/content/server.ts index 40e15663f3..3959dc6dc9 100644 --- a/src/build/content/server.ts +++ b/src/build/content/server.ts @@ -17,6 +17,7 @@ import { trace } from '@opentelemetry/api' import { wrapTracer } from '@opentelemetry/api/experimental' import glob from 'fast-glob' import type { MiddlewareManifest } from 'next/dist/build/webpack/plugins/middleware-plugin.js' +import type { FunctionsConfigManifest } from 'next-with-cache-handler-v2/dist/build/index.js' import { prerelease, satisfies, lt as semverLowerThan, lte as semverLowerThanOrEqual } from 'semver' import type { RunConfig } from '../../run/config.js' @@ -131,6 +132,10 @@ export const copyNextServerCode = async (ctx: PluginContext): Promise => { return } + if (path === 'server/functions-config-manifest.json') { + await verifyFunctionsConfigManifest(join(srcDir, path)) + } + await cp(srcPath, destPath, { recursive: true, force: true }) }), ) @@ -376,6 +381,19 @@ const replaceMiddlewareManifest = async (sourcePath: string, destPath: string) = await writeFile(destPath, newData) } +const verifyFunctionsConfigManifest = async (sourcePath: string) => { + const data = await readFile(sourcePath, 'utf8') + const manifest = JSON.parse(data) as FunctionsConfigManifest + + // https://github.com/vercel/next.js/blob/8367faedd61501025299e92d43a28393c7bb50e2/packages/next/src/build/index.ts#L2465 + // Node.js Middleware has hardcoded /_middleware path + if (manifest.functions['/_middleware']) { + throw new Error( + 'Only Edge Runtime Middleware is supported. Node.js Middleware is not supported.', + ) + } +} + export const verifyHandlerDirStructure = async (ctx: PluginContext) => { const { nextConfig } = JSON.parse( await readFile(join(ctx.serverHandlerDir, RUN_CONFIG_FILE), 'utf-8'), diff --git a/tests/fixtures/middleware-node/app/layout.js b/tests/fixtures/middleware-node/app/layout.js new file mode 100644 index 0000000000..6565e7bafd --- /dev/null +++ b/tests/fixtures/middleware-node/app/layout.js @@ -0,0 +1,12 @@ +export const metadata = { + title: 'Simple Next App', + description: 'Description for Simple Next App', +} + +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} diff --git a/tests/fixtures/middleware-node/app/page.js b/tests/fixtures/middleware-node/app/page.js new file mode 100644 index 0000000000..1a9fe06903 --- /dev/null +++ b/tests/fixtures/middleware-node/app/page.js @@ -0,0 +1,7 @@ +export default function Home() { + return ( +
+

Home

+
+ ) +} diff --git a/tests/fixtures/middleware-node/middleware.ts b/tests/fixtures/middleware-node/middleware.ts new file mode 100644 index 0000000000..064f5bb6c3 --- /dev/null +++ b/tests/fixtures/middleware-node/middleware.ts @@ -0,0 +1,9 @@ +import type { NextRequest } from 'next/server' + +export async function middleware(request: NextRequest) { + console.log('Node.js Middleware request:', request.method, request.nextUrl.pathname) +} + +export const config = { + runtime: 'nodejs', +} diff --git a/tests/fixtures/middleware-node/next.config.js b/tests/fixtures/middleware-node/next.config.js new file mode 100644 index 0000000000..24a4bdfa44 --- /dev/null +++ b/tests/fixtures/middleware-node/next.config.js @@ -0,0 +1,12 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + output: 'standalone', + eslint: { + ignoreDuringBuilds: true, + }, + experimental: { + nodeMiddleware: true, + }, +} + +module.exports = nextConfig diff --git a/tests/fixtures/middleware-node/package.json b/tests/fixtures/middleware-node/package.json new file mode 100644 index 0000000000..ce0360a5f4 --- /dev/null +++ b/tests/fixtures/middleware-node/package.json @@ -0,0 +1,20 @@ +{ + "name": "middleware-node", + "version": "0.1.0", + "private": true, + "scripts": { + "postinstall": "next build", + "dev": "next dev", + "build": "next build" + }, + "dependencies": { + "next": "latest", + "react": "18.2.0", + "react-dom": "18.2.0" + }, + "test": { + "dependencies": { + "next": ">=15.2.0" + } + } +} diff --git a/tests/integration/edge-handler.test.ts b/tests/integration/edge-handler.test.ts index 825ed6fac1..b3632a279c 100644 --- a/tests/integration/edge-handler.test.ts +++ b/tests/integration/edge-handler.test.ts @@ -4,6 +4,7 @@ import { type FixtureTestContext } from '../utils/contexts.js' import { createFixture, invokeEdgeFunction, runPlugin } from '../utils/fixture.js' import { generateRandomObjectID, startMockBlobStore } from '../utils/helpers.js' import { LocalServer } from '../utils/local-server.js' +import { nextVersionSatisfies } from '../utils/next-version-helpers.mjs' beforeEach(async (ctx) => { // set for each test a new deployID and siteID @@ -626,3 +627,13 @@ describe('page router', () => { expect(bodyFr.nextUrlLocale).toBe('fr') }) }) + +test.skipIf(!nextVersionSatisfies('>=15.2.0'))( + 'should throw an Not Supported error when node middleware is used', + async (ctx) => { + await createFixture('middleware-node', ctx) + await expect(runPlugin(ctx)).rejects.toThrow( + 'Only Edge Runtime Middleware is supported. Node.js Middleware is not supported.', + ) + }, +) From 3609d317cf49b19de4829a7df0bc1203fde96cab Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 20 Aug 2025 17:48:30 +0200 Subject: [PATCH 2/3] test: adjust version matching for not found in prerender manifest --- tests/utils/next-version-helpers.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/utils/next-version-helpers.mjs b/tests/utils/next-version-helpers.mjs index b9bffcc28c..26a8df1b7f 100644 --- a/tests/utils/next-version-helpers.mjs +++ b/tests/utils/next-version-helpers.mjs @@ -33,9 +33,9 @@ export function isNextCanary() { export function shouldHaveAppRouterNotFoundInPrerenderManifest() { // https://github.com/vercel/next.js/pull/82199 - // The canary versions are out of band, as there stable/latest patch versions higher than base of canary versions - // and change was not backported to stable versions - return nextVersionSatisfies('>=15.4.2-canary.33') && isNextCanary() + // The canary versions are out of band, as there stable/latest patch versions higher than base of canary versions without + // the change included + return nextVersionSatisfies(isNextCanary() ? '>=15.4.2-canary.33' : '>=15.5.0') } /** From 88febdd4958c107372d7b61ca5f018db87b98296 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 20 Aug 2025 17:58:47 +0200 Subject: [PATCH 3/3] fix: adjust messaging to mention future support --- src/build/content/server.ts | 5 ++++- tests/integration/edge-handler.test.ts | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/build/content/server.ts b/src/build/content/server.ts index 3959dc6dc9..da754d67cc 100644 --- a/src/build/content/server.ts +++ b/src/build/content/server.ts @@ -389,7 +389,10 @@ const verifyFunctionsConfigManifest = async (sourcePath: string) => { // Node.js Middleware has hardcoded /_middleware path if (manifest.functions['/_middleware']) { throw new Error( - 'Only Edge Runtime Middleware is supported. Node.js Middleware is not supported.', + 'Node.js middleware is not yet supported.\n\n' + + 'Future @netlify/plugin-nextjs release will support node middleware with following limitations:\n' + + ' - usage of C++ Addons (https://nodejs.org/api/addons.html) not supported (for example `bcrypt` npm module will not be supported, but `bcryptjs` will be supported),\n' + + ' - usage of Filesystem (https://nodejs.org/api/fs.html) not supported.', ) } } diff --git a/tests/integration/edge-handler.test.ts b/tests/integration/edge-handler.test.ts index b3632a279c..0b33db7936 100644 --- a/tests/integration/edge-handler.test.ts +++ b/tests/integration/edge-handler.test.ts @@ -632,8 +632,18 @@ test.skipIf(!nextVersionSatisfies('>=15.2.0'))( 'should throw an Not Supported error when node middleware is used', async (ctx) => { await createFixture('middleware-node', ctx) - await expect(runPlugin(ctx)).rejects.toThrow( - 'Only Edge Runtime Middleware is supported. Node.js Middleware is not supported.', + + const runPluginPromise = runPlugin(ctx) + + await expect(runPluginPromise).rejects.toThrow('Node.js middleware is not yet supported.') + await expect(runPluginPromise).rejects.toThrow( + 'Future @netlify/plugin-nextjs release will support node middleware with following limitations:', + ) + await expect(runPluginPromise).rejects.toThrow( + ' - usage of C++ Addons (https://nodejs.org/api/addons.html) not supported (for example `bcrypt` npm module will not be supported, but `bcryptjs` will be supported)', + ) + await expect(runPluginPromise).rejects.toThrow( + ' - usage of Filesystem (https://nodejs.org/api/fs.html) not supported', ) }, )