diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index d826f2bdd4e044..bacb1cc38e4ff9 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -124,6 +124,7 @@ import { } from './web/spec-extension/adapters/next-request' import { matchNextDataPathname } from './lib/match-next-data-pathname' import getRouteFromAssetPath from '../shared/lib/router/utils/get-route-from-asset-path' +import { stripInternalHeaders } from './internal-utils' export type FindComponentsResult = { components: LoadComponentsReturnType @@ -1538,6 +1539,28 @@ export default abstract class Server { ) } + protected stripInternalHeaders(req: BaseNextRequest): void { + // Skip stripping internal headers in test mode while the header stripping + // has been explicitly disabled. This allows tests to verify internal + // routing behavior. + if ( + process.env.__NEXT_TEST_MODE && + process.env.__NEXT_NO_STRIP_INTERNAL_HEADERS === '1' + ) { + return + } + + // Strip the internal headers from both the request and the original + // request. + stripInternalHeaders(req.headers) + if ( + 'originalRequest' in req && + 'headers' in (req as NodeNextRequest).originalRequest + ) { + stripInternalHeaders((req as NodeNextRequest).originalRequest.headers) + } + } + private async renderToResponseWithComponentsImpl( { req, res, pathname, renderOpts: opts }: RequestContext, { components, query }: FindComponentsResult @@ -1546,6 +1569,10 @@ export default abstract class Server { // For edge runtime 404 page, /_not-found needs to be treated as 404 page (process.env.NEXT_RUNTIME === 'edge' && pathname === '/_not-found') || pathname === '/404' + + // Strip the internal headers. + this.stripInternalHeaders(req) + const is500Page = pathname === '/500' const isAppPath = components.isAppPath const hasServerProps = !!components.getServerSideProps diff --git a/packages/next/src/server/internal-utils.ts b/packages/next/src/server/internal-utils.ts index 1ae559ea09db8b..1e712b16379a49 100644 --- a/packages/next/src/server/internal-utils.ts +++ b/packages/next/src/server/internal-utils.ts @@ -1,6 +1,8 @@ -import { NEXT_RSC_UNION_QUERY } from '../client/components/app-router-headers' +import type { IncomingHttpHeaders } from 'http' import type { NextParsedUrlQuery } from './request-meta' +import { NEXT_RSC_UNION_QUERY } from '../client/components/app-router-headers' + const INTERNAL_QUERY_NAMES = [ '__nextFallback', '__nextLocale', @@ -36,3 +38,27 @@ export function stripInternalSearchParams( return (isStringUrl ? instance.toString() : instance) as T } + +/** + * Headers that are set by the Next.js server and should be stripped from the + * request headers going to the user's application. + */ +const INTERNAL_HEADERS = [ + 'x-invoke-path', + 'x-invoke-status', + 'x-invoke-error', + 'x-invoke-query', + 'x-invoke-output', + 'x-middleware-invoke', +] as const + +/** + * Strip internal headers from the request headers. + * + * @param headers the headers to strip of internal headers + */ +export function stripInternalHeaders(headers: IncomingHttpHeaders) { + for (const key of INTERNAL_HEADERS) { + delete headers[key] + } +} diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index 087752225090fa..0fe80339d1a0f0 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -1585,6 +1585,9 @@ export default class NextNodeServer extends BaseServer { ReturnType > + // Strip the internal headers. + this.stripInternalHeaders(req) + try { await this.ensureMiddleware() diff --git a/test/e2e/i18n-data-route/i18n-data-route.test.ts b/test/e2e/i18n-data-route/i18n-data-route.test.ts index a25d8e8adad2da..1231f78f02670c 100644 --- a/test/e2e/i18n-data-route/i18n-data-route.test.ts +++ b/test/e2e/i18n-data-route/i18n-data-route.test.ts @@ -17,6 +17,10 @@ createNextDescribe( 'i18n-data-route', { files: __dirname, + env: { + // Disable internal header stripping so we can test the invoke output. + __NEXT_NO_STRIP_INTERNAL_HEADERS: '1', + }, }, ({ next }) => { describe('with locale prefix', () => {