Skip to content

Commit 4d6da7e

Browse files
authored
Fix regex page matcher for Next.js middlewares version 1 (#5496)
* fix regex matcher for middleware version 1 * MiddlewareManifest type * changelog * get middleware matcher regexes as util + unit tests * prettier in changelog
1 parent 85ea802 commit 4d6da7e

File tree

6 files changed

+130
-24
lines changed

6 files changed

+130
-24
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99
- Resolve timeouts when bundling Next.js applications for Cloud Functions (#5691)
1010
- Fixes bug where the functions emulator would attempt to call to prod for 'demo-' projects (#5170)
1111
- Address issues starting the Firebase Hosting emulator with some versions of Next.js (#5781)
12+
- Fix regex page matcher for Next.js middlewares version 1 (#5496)

src/frameworks/next/index.ts

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { mkdir, copyFile } from "fs/promises";
44
import { basename, dirname, join } from "path";
55
import type { NextConfig } from "next";
66
import type { PrerenderManifest } from "next/dist/build";
7-
import type { MiddlewareManifest } from "next/dist/build/webpack/plugins/middleware-plugin";
87
import type { PagesManifest } from "next/dist/build/webpack/plugins/pages-manifest-plugin";
98
import { copy, mkdirp, pathExists, pathExistsSync } from "fs-extra";
109
import { pathToFileURL, parse } from "url";
@@ -18,9 +17,19 @@ import { pick } from "stream-json/filters/Pick";
1817
import { streamObject } from "stream-json/streamers/StreamObject";
1918
import { fileExistsSync, dirExistsSync } from "../../fsutils";
2019

21-
import { BuildResult, FrameworkType, SupportLevel } from "../interfaces";
2220
import { promptOnce } from "../../prompt";
2321
import { FirebaseError } from "../../error";
22+
import { NODE_VERSION, NPM_COMMAND_TIMEOUT_MILLIES } from "../constants";
23+
import type { EmulatorInfo } from "../../emulator/types";
24+
import {
25+
readJSON,
26+
simpleProxy,
27+
warnIfCustomBuildScript,
28+
relativeRequire,
29+
findDependency,
30+
} from "../utils";
31+
import { BuildResult, FrameworkType, SupportLevel } from "../interfaces";
32+
2433
import {
2534
cleanEscapedChars,
2635
getNextjsRewritesToUse,
@@ -30,27 +39,22 @@ import {
3039
isUsingImageOptimization,
3140
isUsingMiddleware,
3241
allDependencyNames,
42+
getMiddlewareMatcherRegexes,
3343
getNonStaticRoutes,
3444
getNonStaticServerComponents,
3545
getHeadersFromMetaFiles,
46+
usesAppDirRouter,
47+
usesNextImage,
48+
hasUnoptimizedImage,
3649
} from "./utils";
37-
import { NODE_VERSION, NPM_COMMAND_TIMEOUT_MILLIES } from "../constants";
3850
import type {
3951
AppPathRoutesManifest,
4052
AppPathsManifest,
4153
HostingHeadersWithSource,
4254
Manifest,
4355
NpmLsDepdendency,
56+
MiddlewareManifest,
4457
} from "./interfaces";
45-
import {
46-
readJSON,
47-
simpleProxy,
48-
warnIfCustomBuildScript,
49-
relativeRequire,
50-
findDependency,
51-
} from "../utils";
52-
import type { EmulatorInfo } from "../../emulator/types";
53-
import { usesAppDirRouter, usesNextImage, hasUnoptimizedImage } from "./utils";
5458
import {
5559
MIDDLEWARE_MANIFEST,
5660
PAGES_MANIFEST,
@@ -325,10 +329,7 @@ export async function ɵcodegenPublicDirectory(sourceDir: string, destDir: strin
325329

326330
const appPathRoutesEntries = Object.entries(appPathRoutesManifest);
327331

328-
const middlewareMatcherRegexes = Object.values(middlewareManifest.middleware)
329-
.map((it) => it.matchers)
330-
.flat()
331-
.map((it) => new RegExp(it.regexp));
332+
const middlewareMatcherRegexes = getMiddlewareMatcherRegexes(middlewareManifest);
332333

333334
const { redirects = [], rewrites = [], headers = [] } = routesManifest;
334335

src/frameworks/next/interfaces.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Header, Rewrite, Redirect } from "next/dist/lib/load-custom-routes";
22
import type { ImageConfigComplete } from "next/dist/shared/lib/image-config";
3+
import type { MiddlewareManifest as MiddlewareManifestV2FromNext } from "next/dist/build/webpack/plugins/middleware-plugin";
34
import type { HostingHeaders } from "../../firebaseConfig";
45

56
export interface RoutesManifestRewrite extends Rewrite {
@@ -39,6 +40,27 @@ export interface ExportMarker {
3940
isNextImageImported: boolean;
4041
}
4142

43+
export type MiddlewareManifest = MiddlewareManifestV1 | MiddlewareManifestV2FromNext;
44+
45+
export type MiddlewareManifestV2 = MiddlewareManifestV2FromNext;
46+
47+
// See: https://github.com/vercel/next.js/blob/b188fab3360855c28fd9407bd07c4ee9f5de16a6/packages/next/build/webpack/plugins/middleware-plugin.ts#L15-L29
48+
export interface MiddlewareManifestV1 {
49+
version: 1;
50+
sortedMiddleware: string[];
51+
clientInfo: [location: string, isSSR: boolean][];
52+
middleware: {
53+
[page: string]: {
54+
env: string[];
55+
files: string[];
56+
name: string;
57+
page: string;
58+
regexp: string;
59+
wasm?: any[]; // WasmBinding isn't exported from next
60+
};
61+
};
62+
}
63+
4264
export interface ImagesManifest {
4365
version: number;
4466
images: ImageConfigComplete & {

src/frameworks/next/utils.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { existsSync } from "fs";
22
import { pathExists } from "fs-extra";
33
import { basename, extname, join } from "path";
44
import type { Header, Redirect, Rewrite } from "next/dist/lib/load-custom-routes";
5-
import type { MiddlewareManifest } from "next/dist/build/webpack/plugins/middleware-plugin";
65
import type { PagesManifest } from "next/dist/build/webpack/plugins/pages-manifest-plugin";
76

87
import { isUrl, readJSON } from "../utils";
@@ -12,6 +11,9 @@ import type {
1211
ExportMarker,
1312
ImagesManifest,
1413
NpmLsDepdendency,
14+
MiddlewareManifest,
15+
MiddlewareManifestV1,
16+
MiddlewareManifestV2,
1517
AppPathsManifest,
1618
AppPathRoutesManifest,
1719
HostingHeadersWithSource,
@@ -239,6 +241,27 @@ export function allDependencyNames(mod: NpmLsDepdendency): string[] {
239241
return dependencyNames;
240242
}
241243

244+
/**
245+
* Get regexes from middleware matcher manifest
246+
*/
247+
export function getMiddlewareMatcherRegexes(middlewareManifest: MiddlewareManifest): RegExp[] {
248+
const middlewareObjectValues = Object.values(middlewareManifest.middleware);
249+
250+
let middlewareMatchers: Record<"regexp", string>[];
251+
252+
if (middlewareManifest.version === 1) {
253+
middlewareMatchers = middlewareObjectValues.map(
254+
(page: MiddlewareManifestV1["middleware"]["page"]) => ({ regexp: page.regexp })
255+
);
256+
} else {
257+
middlewareMatchers = middlewareObjectValues
258+
.map((page: MiddlewareManifestV2["middleware"]["page"]) => page.matchers)
259+
.flat();
260+
}
261+
262+
return middlewareMatchers.map((matcher) => new RegExp(matcher.regexp));
263+
}
264+
242265
/**
243266
* Get non static routes based on pages-manifest, prerendered and dynamic routes
244267
*/

src/test/frameworks/next/helpers/middleware.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
import type { MiddlewareManifest } from "next/dist/build/webpack/plugins/middleware-plugin";
1+
import type {
2+
MiddlewareManifestV1,
3+
MiddlewareManifestV2,
4+
} from "../../../../frameworks/next/interfaces";
25

3-
export const middlewareManifestWhenUsed: MiddlewareManifest = {
6+
export const middlewareV2ManifestWhenUsed: MiddlewareManifestV2 = {
47
sortedMiddleware: ["/"],
58
middleware: {
69
"/": {
@@ -22,9 +25,32 @@ export const middlewareManifestWhenUsed: MiddlewareManifest = {
2225
version: 2,
2326
};
2427

25-
export const middlewareManifestWhenNotUsed: MiddlewareManifest = {
28+
export const middlewareV2ManifestWhenNotUsed: MiddlewareManifestV2 = {
2629
sortedMiddleware: [],
2730
middleware: {},
2831
functions: {},
2932
version: 2,
3033
};
34+
35+
export const middlewareV1ManifestWhenUsed: MiddlewareManifestV1 = {
36+
sortedMiddleware: ["/"],
37+
clientInfo: [["/", false]],
38+
middleware: {
39+
"/": {
40+
env: [],
41+
files: ["server/edge-runtime-webpack.js", "server/pages/_middleware.js"],
42+
name: "pages/_middleware",
43+
page: "/",
44+
regexp: "^/(?!_next).*$",
45+
wasm: [],
46+
},
47+
},
48+
version: 1,
49+
};
50+
51+
export const middlewareV1ManifestWhenNotUsed: MiddlewareManifestV1 = {
52+
sortedMiddleware: [],
53+
clientInfo: [],
54+
middleware: {},
55+
version: 1,
56+
};

src/test/frameworks/next/utils.spec.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
isUsingImageOptimization,
2323
isUsingAppDirectory,
2424
allDependencyNames,
25+
getMiddlewareMatcherRegexes,
2526
getNonStaticRoutes,
2627
getNonStaticServerComponents,
2728
getHeadersFromMetaFiles,
@@ -34,8 +35,8 @@ import {
3435
exportMarkerWithoutImage,
3536
imagesManifest,
3637
imagesManifestUnoptimized,
37-
middlewareManifestWhenNotUsed,
38-
middlewareManifestWhenUsed,
38+
middlewareV2ManifestWhenNotUsed,
39+
middlewareV2ManifestWhenUsed,
3940
pathsAsGlobs,
4041
pathsWithEscapedChars,
4142
pathsWithRegex,
@@ -48,6 +49,8 @@ import {
4849
unsupportedRedirects,
4950
unsupportedRewritesArray,
5051
npmLsReturn,
52+
middlewareV1ManifestWhenUsed,
53+
middlewareV1ManifestWhenNotUsed,
5154
pagesManifest,
5255
prerenderManifest,
5356
appPathsManifest,
@@ -266,12 +269,12 @@ describe("Next.js utils", () => {
266269
});
267270

268271
it("should return true if using middleware in production", async () => {
269-
sandbox.stub(fsExtra, "readJSON").resolves(middlewareManifestWhenUsed);
272+
sandbox.stub(fsExtra, "readJSON").resolves(middlewareV2ManifestWhenUsed);
270273
expect(await isUsingMiddleware("", false)).to.be.true;
271274
});
272275

273276
it("should return false if not using middleware in production", async () => {
274-
sandbox.stub(fsExtra, "readJSON").resolves(middlewareManifestWhenNotUsed);
277+
sandbox.stub(fsExtra, "readJSON").resolves(middlewareV2ManifestWhenNotUsed);
275278
expect(await isUsingMiddleware("", false)).to.be.false;
276279
});
277280
});
@@ -374,6 +377,36 @@ describe("Next.js utils", () => {
374377
});
375378
});
376379

380+
describe("getMiddlewareMatcherRegexes", () => {
381+
it("should return regexes when using version 1", () => {
382+
const middlewareMatcherRegexes = getMiddlewareMatcherRegexes(middlewareV1ManifestWhenUsed);
383+
384+
for (const regex of middlewareMatcherRegexes) {
385+
expect(regex).to.be.an.instanceOf(RegExp);
386+
}
387+
});
388+
389+
it("should return empty array when using version 1 but not using middleware", () => {
390+
const middlewareMatcherRegexes = getMiddlewareMatcherRegexes(middlewareV1ManifestWhenNotUsed);
391+
392+
expect(middlewareMatcherRegexes).to.eql([]);
393+
});
394+
395+
it("should return regexes when using version 2", () => {
396+
const middlewareMatcherRegexes = getMiddlewareMatcherRegexes(middlewareV2ManifestWhenUsed);
397+
398+
for (const regex of middlewareMatcherRegexes) {
399+
expect(regex).to.be.an.instanceOf(RegExp);
400+
}
401+
});
402+
403+
it("should return empty array when using version 2 but not using middleware", () => {
404+
const middlewareMatcherRegexes = getMiddlewareMatcherRegexes(middlewareV2ManifestWhenNotUsed);
405+
406+
expect(middlewareMatcherRegexes).to.eql([]);
407+
});
408+
});
409+
377410
describe("getNonStaticRoutes", () => {
378411
it("should get non-static routes", () => {
379412
expect(

0 commit comments

Comments
 (0)