Skip to content

Commit 8c86b77

Browse files
authored
fix: fail build/deploy when using not yet unsupported Node.js Midleware (#3016)
1 parent 45b43e6 commit 8c86b77

File tree

8 files changed

+105
-3
lines changed

8 files changed

+105
-3
lines changed

src/build/content/server.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { trace } from '@opentelemetry/api'
1717
import { wrapTracer } from '@opentelemetry/api/experimental'
1818
import glob from 'fast-glob'
1919
import type { MiddlewareManifest } from 'next/dist/build/webpack/plugins/middleware-plugin.js'
20+
import type { FunctionsConfigManifest } from 'next-with-cache-handler-v2/dist/build/index.js'
2021
import { prerelease, satisfies, lt as semverLowerThan, lte as semverLowerThanOrEqual } from 'semver'
2122

2223
import type { RunConfig } from '../../run/config.js'
@@ -131,6 +132,10 @@ export const copyNextServerCode = async (ctx: PluginContext): Promise<void> => {
131132
return
132133
}
133134

135+
if (path === 'server/functions-config-manifest.json') {
136+
await verifyFunctionsConfigManifest(join(srcDir, path))
137+
}
138+
134139
await cp(srcPath, destPath, { recursive: true, force: true })
135140
}),
136141
)
@@ -376,6 +381,22 @@ const replaceMiddlewareManifest = async (sourcePath: string, destPath: string) =
376381
await writeFile(destPath, newData)
377382
}
378383

384+
const verifyFunctionsConfigManifest = async (sourcePath: string) => {
385+
const data = await readFile(sourcePath, 'utf8')
386+
const manifest = JSON.parse(data) as FunctionsConfigManifest
387+
388+
// https://github.com/vercel/next.js/blob/8367faedd61501025299e92d43a28393c7bb50e2/packages/next/src/build/index.ts#L2465
389+
// Node.js Middleware has hardcoded /_middleware path
390+
if (manifest.functions['/_middleware']) {
391+
throw new Error(
392+
'Node.js middleware is not yet supported.\n\n' +
393+
'Future @netlify/plugin-nextjs release will support node middleware with following limitations:\n' +
394+
' - 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' +
395+
' - usage of Filesystem (https://nodejs.org/api/fs.html) not supported.',
396+
)
397+
}
398+
}
399+
379400
export const verifyHandlerDirStructure = async (ctx: PluginContext) => {
380401
const { nextConfig } = JSON.parse(
381402
await readFile(join(ctx.serverHandlerDir, RUN_CONFIG_FILE), 'utf-8'),
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export const metadata = {
2+
title: 'Simple Next App',
3+
description: 'Description for Simple Next App',
4+
}
5+
6+
export default function RootLayout({ children }) {
7+
return (
8+
<html lang="en">
9+
<body>{children}</body>
10+
</html>
11+
)
12+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function Home() {
2+
return (
3+
<main>
4+
<h1>Home</h1>
5+
</main>
6+
)
7+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { NextRequest } from 'next/server'
2+
3+
export async function middleware(request: NextRequest) {
4+
console.log('Node.js Middleware request:', request.method, request.nextUrl.pathname)
5+
}
6+
7+
export const config = {
8+
runtime: 'nodejs',
9+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/** @type {import('next').NextConfig} */
2+
const nextConfig = {
3+
output: 'standalone',
4+
eslint: {
5+
ignoreDuringBuilds: true,
6+
},
7+
experimental: {
8+
nodeMiddleware: true,
9+
},
10+
}
11+
12+
module.exports = nextConfig
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "middleware-node",
3+
"version": "0.1.0",
4+
"private": true,
5+
"scripts": {
6+
"postinstall": "next build",
7+
"dev": "next dev",
8+
"build": "next build"
9+
},
10+
"dependencies": {
11+
"next": "latest",
12+
"react": "18.2.0",
13+
"react-dom": "18.2.0"
14+
},
15+
"test": {
16+
"dependencies": {
17+
"next": ">=15.2.0"
18+
}
19+
}
20+
}

tests/integration/edge-handler.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { type FixtureTestContext } from '../utils/contexts.js'
44
import { createFixture, invokeEdgeFunction, runPlugin } from '../utils/fixture.js'
55
import { generateRandomObjectID, startMockBlobStore } from '../utils/helpers.js'
66
import { LocalServer } from '../utils/local-server.js'
7+
import { nextVersionSatisfies } from '../utils/next-version-helpers.mjs'
78

89
beforeEach<FixtureTestContext>(async (ctx) => {
910
// set for each test a new deployID and siteID
@@ -626,3 +627,23 @@ describe('page router', () => {
626627
expect(bodyFr.nextUrlLocale).toBe('fr')
627628
})
628629
})
630+
631+
test.skipIf(!nextVersionSatisfies('>=15.2.0'))<FixtureTestContext>(
632+
'should throw an Not Supported error when node middleware is used',
633+
async (ctx) => {
634+
await createFixture('middleware-node', ctx)
635+
636+
const runPluginPromise = runPlugin(ctx)
637+
638+
await expect(runPluginPromise).rejects.toThrow('Node.js middleware is not yet supported.')
639+
await expect(runPluginPromise).rejects.toThrow(
640+
'Future @netlify/plugin-nextjs release will support node middleware with following limitations:',
641+
)
642+
await expect(runPluginPromise).rejects.toThrow(
643+
' - 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)',
644+
)
645+
await expect(runPluginPromise).rejects.toThrow(
646+
' - usage of Filesystem (https://nodejs.org/api/fs.html) not supported',
647+
)
648+
},
649+
)

tests/utils/next-version-helpers.mjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ export function isNextCanary() {
3333
export function shouldHaveAppRouterNotFoundInPrerenderManifest() {
3434
// https://github.com/vercel/next.js/pull/82199
3535

36-
// The canary versions are out of band, as there stable/latest patch versions higher than base of canary versions
37-
// and change was not backported to stable versions
38-
return nextVersionSatisfies('>=15.4.2-canary.33') && isNextCanary()
36+
// The canary versions are out of band, as there stable/latest patch versions higher than base of canary versions without
37+
// the change included
38+
return nextVersionSatisfies(isNextCanary() ? '>=15.4.2-canary.33' : '>=15.5.0')
3939
}
4040

4141
/**

0 commit comments

Comments
 (0)