Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5728b12
middleware/proxy fixes for next.js 15/16
leoortizz Dec 17, 2025
c7a59fa
Merge branch 'main' into leoortizz_nextjsproxyfixes
leoortizz Dec 17, 2025
f66edfe
changelog
leoortizz Dec 17, 2025
6afc826
Merge branch 'main' into leoortizz_nextjsproxyfixes
leoortizz Dec 18, 2025
5cb5555
Merge branch 'main' into leoortizz_nextjsproxyfixes
leoortizz Dec 19, 2025
4e0b90b
check src directory
leoortizz Dec 22, 2025
8cf8a3a
document middleware check in dev mode
leoortizz Dec 22, 2025
ba19ea4
update middleware types docs
leoortizz Dec 22, 2025
10b3e21
Apply suggestion from @leoortizz
leoortizz Dec 22, 2025
077c3db
remove extra space
leoortizz Dec 22, 2025
02cb654
Merge branch 'main' into leoortizz_nextjsproxyfixes
leoortizz Dec 22, 2025
64501f1
Merge branch 'main' into leoortizz_nextjsproxyfixes
leoortizz Dec 23, 2025
0b3bf10
Merge branch 'main' into leoortizz_nextjsproxyfixes
leoortizz Jan 9, 2026
06a9e1f
Merge branch 'main' into leoortizz_nextjsproxyfixes
leoortizz Jan 12, 2026
12b6303
Merge branch 'main' into leoortizz_nextjsproxyfixes
leoortizz Jan 13, 2026
46cd958
Merge branch 'main' into leoortizz_nextjsproxyfixes
leoortizz Jan 14, 2026
9a100b9
Merge branch 'main' into leoortizz_nextjsproxyfixes
leoortizz Jan 21, 2026
72d980f
Merge branch 'main' into leoortizz_nextjsproxyfixes
leoortizz Feb 4, 2026
600e783
Merge branch 'main' into leoortizz_nextjsproxyfixes
leoortizz Mar 9, 2026
f426f13
Merge branch 'main' into leoortizz_nextjsproxyfixes
leoortizz Mar 12, 2026
e6a8915
Merge branch 'main' into leoortizz_nextjsproxyfixes
leoortizz Mar 12, 2026
b332e0d
Merge branch 'main' into leoortizz_nextjsproxyfixes
leoortizz Apr 8, 2026
8506fce
update changelog
leoortizz Apr 8, 2026
ea0a1e9
Merge branch 'main' into leoortizz_nextjsproxyfixes
leoortizz Apr 8, 2026
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
@@ -1,2 +1,3 @@
- Fixed an issue where functions deployments would silently fail (#6989)
- Add support for Next.js 16 middleware (`proxy.ts`/`proxy.js`) (#9631)
- Updates the default region for new App Hosting backends to us-east4 (#10271)
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
107 changes: 105 additions & 2 deletions src/frameworks/next/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,114 @@ export interface ExportMarker {
isNextImageImported: boolean;
}

export type MiddlewareManifest = MiddlewareManifestV1 | MiddlewareManifestV2FromNext;
export type MiddlewareManifest =
| MiddlewareManifestV1
| MiddlewareManifestV2FromNext
| MiddlewareManifestV3;
Comment on lines +87 to +89
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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.

Copy link
Copy Markdown
Member Author

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?

Deferring to @jamesdaniels, but I don't think we would remove support for old Next.js versions as they just work?

In which case would some of these manifest versions would no longer be needed?

The manifests won't be needed anymore as soon as we migrate to use the Adapters API. That's also why I chose to copy the V3 types instead of bumping the Next.js dependency, as it would require more changes that would not be necessary when we switch to the Adapters API.

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.

The version of manifests does not match the Next.js version necessarily. I updated the comments to say in which versions they were used. I realized the manifest-middleware version 3 changed in Next.js 14.2.0, so I updated the comment. It's just that in Next.js 16 the proxy route matchers are in functions-config-manifest instead.


/**
* Middleware manifest type used between Next.js 12.2.0 - 14.1.4
*
* @see https://github.com/vercel/next.js/blob/v14.1.4/packages/next/src/build/webpack/plugins/middleware-plugin.ts#L45-L50
*/
export type MiddlewareManifestV2 = MiddlewareManifestV2FromNext;

// See: https://github.com/vercel/next.js/blob/b188fab3360855c28fd9407bd07c4ee9f5de16a6/packages/next/build/webpack/plugins/middleware-plugin.ts#L15-L29
/**
* Middleware manifest types used since Next.js 14.2.0
*
* @see https://github.com/vercel/next.js/blob/v14.2.0/packages/next/src/build/webpack/plugins/middleware-plugin.ts#L51-L56
*/
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[];
}>;
}
>;
}

/**
* Middleware manifest type used before Next.js 12.2, when middleware became stable.
*
* @see https://github.com/vercel/next.js/blob/b188fab3360855c28fd9407bd07c4ee9f5de16a6/packages/next/build/webpack/plugins/middleware-plugin.ts#L15-L29
*/
export interface MiddlewareManifestV1 {
version: 1;
sortedMiddleware: string[];
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