Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Fix proxy.js/proxy.ts in Next.js 16 (#9631)
3 changes: 3 additions & 0 deletions src/frameworks/next/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
ROUTES_MANIFEST as ROUTES_MANIFEST_TYPE,
APP_PATHS_MANIFEST as APP_PATHS_MANIFEST_TYPE,
SERVER_REFERENCE_MANIFEST as SERVER_REFERENCE_MANIFEST_TYPE,
FUNCTIONS_CONFIG_MANIFEST as FUNCTIONS_CONFIG_MANIFEST_TYPE,
} from "next/constants";
import type { WEBPACK_LAYERS as NEXTJS_WEBPACK_LAYERS } from "next/dist/lib/constants";

Expand All @@ -16,6 +17,8 @@ export const APP_PATH_ROUTES_MANIFEST: typeof APP_PATH_ROUTES_MANIFEST_TYPE =
export const EXPORT_MARKER: typeof EXPORT_MARKER_TYPE = "export-marker.json";
export const IMAGES_MANIFEST: typeof IMAGES_MANIFEST_TYPE = "images-manifest.json";
export const MIDDLEWARE_MANIFEST: typeof MIDDLEWARE_MANIFEST_TYPE = "middleware-manifest.json";
export const FUNCTIONS_CONFIG_MANIFEST: typeof FUNCTIONS_CONFIG_MANIFEST_TYPE =
"functions-config-manifest.json";
export const PAGES_MANIFEST: typeof PAGES_MANIFEST_TYPE = "pages-manifest.json";
export const PRERENDER_MANIFEST: typeof PRERENDER_MANIFEST_TYPE = "prerender-manifest.json";
export const ROUTES_MANIFEST: typeof ROUTES_MANIFEST_TYPE = "routes-manifest.json";
Expand Down
11 changes: 10 additions & 1 deletion src/frameworks/next/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
MiddlewareManifest,
ActionManifest,
CustomBuildOptions,
FunctionsConfigManifest,
} from "./interfaces";
import {
MIDDLEWARE_MANIFEST,
Expand All @@ -82,6 +83,7 @@
APP_PATHS_MANIFEST,
SERVER_REFERENCE_MANIFEST,
ESBUILD_VERSION,
FUNCTIONS_CONFIG_MANIFEST,
} from "./constants";
import { getAllSiteDomains, getDeploymentDomain } from "../../hosting/api";
import { logger } from "../../logger";
Expand All @@ -100,13 +102,13 @@
const DEFAULT_NUMBER_OF_REASONS_TO_LIST = 5;

function getReactVersion(cwd: string): string | undefined {
return findDependency("react-dom", { cwd, omitDev: false })?.version;

Check warning on line 105 in src/frameworks/next/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe member access .version on an `any` value

Check warning on line 105 in src/frameworks/next/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe return of an `any` typed value
}

/**
* Returns whether this codebase is a Next.js backend.
*/
export async function discover(dir: string) {

Check warning on line 111 in src/frameworks/next/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function
if (!(await pathExists(join(dir, "package.json")))) return;
const version = getNextVersion(dir);
if (!(await whichNextConfigFile(dir)) && !version) return;
Expand Down Expand Up @@ -163,10 +165,10 @@

const nextBuild = new Promise((resolve, reject) => {
const buildProcess = spawn(cli, ["build"], { cwd: dir, env });
buildProcess.stdout?.on("data", (data) => logger.info(data.toString()));

Check warning on line 168 in src/frameworks/next/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe call of an `any` typed value

Check warning on line 168 in src/frameworks/next/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe member access .toString on an `any` value

Check warning on line 168 in src/frameworks/next/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe argument of type `any` assigned to a parameter of type `Error`
buildProcess.stderr?.on("data", (data) => logger.info(data.toString()));

Check warning on line 169 in src/frameworks/next/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe call of an `any` typed value

Check warning on line 169 in src/frameworks/next/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe member access .toString on an `any` value

Check warning on line 169 in src/frameworks/next/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe argument of type `any` assigned to a parameter of type `Error`
buildProcess.on("error", (err) => {
reject(new FirebaseError(`Unable to build your Next.js app: ${err}`));

Check warning on line 171 in src/frameworks/next/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Invalid type "Error" of template literal expression
});
buildProcess.on("exit", (code) => {
resolve(code);
Expand Down Expand Up @@ -454,6 +456,7 @@
pagesManifest,
appPathRoutesManifest,
serverReferenceManifest,
functionsConfigManifest,
] = await Promise.all([
readJSON<MiddlewareManifest>(join(sourceDir, distDir, "server", MIDDLEWARE_MANIFEST)),
readJSON<PrerenderManifest>(join(sourceDir, distDir, PRERENDER_MANIFEST)),
Expand All @@ -465,11 +468,17 @@
readJSON<ActionManifest>(join(sourceDir, distDir, "server", SERVER_REFERENCE_MANIFEST)).catch(
() => ({ node: {}, edge: {}, encryptionKey: "" }),
),
readJSON<FunctionsConfigManifest>(
join(sourceDir, distDir, "server", FUNCTIONS_CONFIG_MANIFEST),
).catch(() => ({ version: 0, functions: {} })),
]);

const appPathRoutesEntries = Object.entries(appPathRoutesManifest);

const middlewareMatcherRegexes = getMiddlewareMatcherRegexes(middlewareManifest);
const middlewareMatcherRegexes = getMiddlewareMatcherRegexes(
middlewareManifest,
functionsConfigManifest,
);

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

Expand Down
96 changes: 95 additions & 1 deletion src/frameworks/next/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,104 @@ export interface ExportMarker {
isNextImageImported: boolean;
}

export type MiddlewareManifest = MiddlewareManifestV1 | MiddlewareManifestV2FromNext;
export type MiddlewareManifest =
| MiddlewareManifestV1
| MiddlewareManifestV2FromNext
| MiddlewareManifestV3;
Comment on lines +87 to +89
Copy link
Contributor

Choose a reason for hiding this comment

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

Are there reasonable more descriptive names that could be used instead of V1 or V3?

Are these all just different manifest types introduced at different nextjs versions?

For example could MiddlewareManifestNext16 or something work for MiddlewareManifestV3 ?

Copy link
Contributor

Choose a reason for hiding this comment

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

ah nvm, these are literally the versions on the interface itself.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, would we some day remove support for very old nextjs versions? In which case would some of these manifest versions would no longer be needed? could those versions be in the docstrings for MiddlewareManifestv2 and v1 ?
I think the "Middleware manifest types for Next.js 16" you added for v3 is very helpful.


export type MiddlewareManifestV2 = MiddlewareManifestV2FromNext;

/**
* Middleware manifest types for Next.js 16
*
* @see https://github.com/vercel/next.js/blob/3352f9ee9342b40aaded91c340e7e11650aa4867/packages/next/src/build/webpack/plugins/middleware-plugin.ts#L55
*/
export type MiddlewareManifestV3 = {
version: 3;
sortedMiddleware: string[];
middleware: { [page: string]: EdgeFunctionDefinition };
functions: { [page: string]: EdgeFunctionDefinition };
};

/**
* Type required for MiddlewareManifestV3
*
* @see https://github.com/vercel/next.js/blob/3352f9ee9342b40aaded91c340e7e11650aa4867/packages/next/src/build/webpack/plugins/middleware-plugin.ts#L44-L53
*/
interface EdgeFunctionDefinition {
files: string[];
name: string;
page: string;
matchers: ProxyMatcherNext16[];
env: Record<string, string>;
wasm?: AssetBinding[];
assets?: AssetBinding[];
regions?: string[] | string;
}

/**
* Type required for MiddlewareManifestV3
*
* @see https://github.com/vercel/next.js/blob/3352f9ee9342b40aaded91c340e7e11650aa4867/packages/next/src/build/analysis/get-page-static-info.ts#L48-L54
*/
type ProxyMatcherNext16 = {
regexp: string;
locale?: false;
has?: RouteHasNext16[];
missing?: RouteHasNext16[];
originalSource: string;
};

/**
* Type required for MiddlewareManifestV3
*
* @see https://github.com/vercel/next.js/blob/3352f9ee9342b40aaded91c340e7e11650aa4867/packages/next/src/lib/load-custom-routes.ts#L10-L20
*/
type RouteHasNext16 =
| {
type: "header" | "cookie" | "query";
key: string;
value?: string;
}
| {
type: "host";
key?: undefined;
value: string;
};

/**
* Type required for MiddlewareManifestV3
*
* @see https://github.com/vercel/next.js/blob/3352f9ee9342b40aaded91c340e7e11650aa4867/packages/next/src/build/webpack/loaders/get-module-build-info.ts#L59
*/
interface AssetBinding {
filePath: string;
name: string;
}

/**
* Manifest used to detect proxy path matchers in Next.js 16+
*
* @see https://github.com/vercel/next.js/blob/3352f9ee9342b40aaded91c340e7e11650aa4867/packages/next/src/build/index.ts#L576-L588
*/
export interface FunctionsConfigManifest {
version: number;
functions: Record<
string,
{
maxDuration?: number;
runtime?: "nodejs";
regions?: string[] | string;
matchers?: Array<{
regexp: string;
originalSource: string;
has?: RouteHas[];
missing?: RouteHas[];
}>;
}
>;
}

// See: https://github.com/vercel/next.js/blob/b188fab3360855c28fd9407bd07c4ee9f5de16a6/packages/next/build/webpack/plugins/middleware-plugin.ts#L15-L29
export interface MiddlewareManifestV1 {
version: 1;
Expand Down
1 change: 1 addition & 0 deletions src/frameworks/next/testing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from "./images";
export * from "./middleware";
export * from "./npm";
export * from "./app";
export * from "./i18n";
73 changes: 72 additions & 1 deletion src/frameworks/next/testing/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,75 @@
import type { MiddlewareManifestV1, MiddlewareManifestV2 } from "../interfaces";
import type {
MiddlewareManifestV1,
MiddlewareManifestV2,
MiddlewareManifestV3,
FunctionsConfigManifest,
} from "../interfaces";

export const middlewareV3ManifestWhenUsed: MiddlewareManifestV3 = {
sortedMiddleware: [],
middleware: {},
functions: {},
version: 3,
};

export const functionsConfigManifestWhenUsed: FunctionsConfigManifest = {
version: 1,
functions: {
"/_middleware": {
runtime: "nodejs",
matchers: [
{
regexp: "^(?:\\/(_next\\/data\\/[^/]{1,}))?\\/(\\.json)?[\\/#\\?]?$",
originalSource: "/",
},
],
},
},
};

export const middlewareV3ManifestWhenNotUsed: MiddlewareManifestV3 = {
version: 3,
middleware: {},
sortedMiddleware: [],
functions: {},
};

export const functionsConfigManifestWhenNotUsed: FunctionsConfigManifest = {
version: 1,
functions: {},
};

export const middlewareV3ManifestWithDeprecatedMiddleware: MiddlewareManifestV3 = {
version: 3,
middleware: {
"/": {
files: [
"server/edge/chunks/[root-of-the-server]__123._.js",
"server/edge/chunks/node_modules_next_dist_123._.js",
"server/edge/chunks/turbopack-edge-wrapper_123.js",
],
name: "middleware",
page: "/",
matchers: [
{
regexp: "^(?:\\/(_next\\/data\\/[^/]{1,}))?\\/(\\\\.json)?[\\/#\\?]?$",
originalSource: "/",
},
],
wasm: [],
assets: [],
env: {
__NEXT_BUILD_ID: "1",
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: "1",
__NEXT_PREVIEW_MODE_ID: "1",
__NEXT_PREVIEW_MODE_ENCRYPTION_KEY: "1",
__NEXT_PREVIEW_MODE_SIGNING_KEY: "1",
},
},
},
sortedMiddleware: ["/"],
functions: {},
};

export const middlewareV2ManifestWhenUsed: MiddlewareManifestV2 = {
sortedMiddleware: ["/"],
Expand Down
Loading
Loading