Skip to content

Commit 1b8ab4e

Browse files
committed
Apply middleware patches
1 parent 58dcf30 commit 1b8ab4e

19 files changed

+494
-59
lines changed

packages/next/build/webpack-config.ts

+8
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,12 @@ export function getDefineEnv({
155155
'process.env.__NEXT_OPTIMISTIC_CLIENT_CACHE': JSON.stringify(
156156
config.experimental.optimisticClientCache
157157
),
158+
'process.env.__NEXT_NO_MIDDLEWARE_URL_NORMALIZE': JSON.stringify(
159+
config.experimental.skipMiddlewareUrlNormalize
160+
),
161+
'process.env.__NEXT_ALLOW_MIDDLEWARE_RESPONSE_BODY': JSON.stringify(
162+
config.experimental.allowMiddlewareResponseBody
163+
),
158164
'process.env.__NEXT_CROSS_ORIGIN': JSON.stringify(config.crossOrigin),
159165
'process.browser': JSON.stringify(isClient),
160166
'process.env.__NEXT_TEST_MODE': JSON.stringify(
@@ -1779,6 +1785,8 @@ export default async function getBaseWebpackConfig(
17791785
new MiddlewarePlugin({
17801786
dev,
17811787
sriEnabled: !dev && !!config.experimental.sri?.algorithm,
1788+
allowMiddlewareResponseBody:
1789+
!!config.experimental.allowMiddlewareResponseBody,
17821790
}),
17831791
isClient &&
17841792
new BuildManifestPlugin({

packages/next/build/webpack/plugins/middleware-plugin.ts

+18-4
Original file line numberDiff line numberDiff line change
@@ -335,12 +335,14 @@ function getCodeAnalyzer(params: {
335335
dev: boolean
336336
compiler: webpack.Compiler
337337
compilation: webpack.Compilation
338+
allowMiddlewareResponseBody: boolean
338339
}) {
339340
return (parser: webpack.javascript.JavascriptParser) => {
340341
const {
341342
dev,
342343
compiler: { webpack: wp },
343344
compilation,
345+
allowMiddlewareResponseBody,
344346
} = params
345347
const { hooks } = parser
346348

@@ -557,8 +559,10 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`,
557559
.for(`${prefix}WebAssembly.instantiate`)
558560
.tap(NAME, handleWrapWasmInstantiateExpression)
559561
}
560-
hooks.new.for('Response').tap(NAME, handleNewResponseExpression)
561-
hooks.new.for('NextResponse').tap(NAME, handleNewResponseExpression)
562+
if (!allowMiddlewareResponseBody) {
563+
hooks.new.for('Response').tap(NAME, handleNewResponseExpression)
564+
hooks.new.for('NextResponse').tap(NAME, handleNewResponseExpression)
565+
}
562566
hooks.callMemberChain.for('process').tap(NAME, handleCallMemberChain)
563567
hooks.expressionMemberChain.for('process').tap(NAME, handleCallMemberChain)
564568
hooks.importCall.tap(NAME, handleImport)
@@ -797,10 +801,19 @@ function getExtractMetadata(params: {
797801
export default class MiddlewarePlugin {
798802
private readonly dev: boolean
799803
private readonly sriEnabled: boolean
800-
801-
constructor({ dev, sriEnabled }: { dev: boolean; sriEnabled: boolean }) {
804+
private readonly allowMiddlewareResponseBody: boolean
805+
constructor({
806+
dev,
807+
sriEnabled,
808+
allowMiddlewareResponseBody,
809+
}: {
810+
dev: boolean
811+
sriEnabled: boolean
812+
allowMiddlewareResponseBody: boolean
813+
}) {
802814
this.dev = dev
803815
this.sriEnabled = sriEnabled
816+
this.allowMiddlewareResponseBody = allowMiddlewareResponseBody
804817
}
805818

806819
public apply(compiler: webpack.Compiler) {
@@ -813,6 +826,7 @@ export default class MiddlewarePlugin {
813826
dev: this.dev,
814827
compiler,
815828
compilation,
829+
allowMiddlewareResponseBody: this.allowMiddlewareResponseBody,
816830
})
817831
hooks.parser.for('javascript/auto').tap(NAME, codeAnalyzer)
818832
hooks.parser.for('javascript/dynamic').tap(NAME, codeAnalyzer)

packages/next/lib/load-custom-routes.ts

+41-39
Original file line numberDiff line numberDiff line change
@@ -624,50 +624,52 @@ export default async function loadCustomRoutes(
624624
)
625625
}
626626

627-
if (config.trailingSlash) {
628-
redirects.unshift(
629-
{
630-
source: '/:file((?!\\.well-known(?:/.*)?)(?:[^/]+/)*[^/]+\\.\\w+)/',
631-
destination: '/:file',
632-
permanent: true,
633-
locale: config.i18n ? false : undefined,
634-
internal: true,
635-
} as Redirect,
636-
{
637-
source: '/:notfile((?!\\.well-known(?:/.*)?)(?:[^/]+/)*[^/\\.]+)',
638-
destination: '/:notfile/',
639-
permanent: true,
640-
locale: config.i18n ? false : undefined,
641-
internal: true,
642-
} as Redirect
643-
)
644-
if (config.basePath) {
645-
redirects.unshift({
646-
source: config.basePath,
647-
destination: config.basePath + '/',
648-
permanent: true,
649-
basePath: false,
650-
locale: config.i18n ? false : undefined,
651-
internal: true,
652-
} as Redirect)
653-
}
654-
} else {
655-
redirects.unshift({
656-
source: '/:path+/',
657-
destination: '/:path+',
658-
permanent: true,
659-
locale: config.i18n ? false : undefined,
660-
internal: true,
661-
} as Redirect)
662-
if (config.basePath) {
627+
if (!config.experimental?.skipTrailingSlashRedirect) {
628+
if (config.trailingSlash) {
629+
redirects.unshift(
630+
{
631+
source: '/:file((?!\\.well-known(?:/.*)?)(?:[^/]+/)*[^/]+\\.\\w+)/',
632+
destination: '/:file',
633+
permanent: true,
634+
locale: config.i18n ? false : undefined,
635+
internal: true,
636+
} as Redirect,
637+
{
638+
source: '/:notfile((?!\\.well-known(?:/.*)?)(?:[^/]+/)*[^/\\.]+)',
639+
destination: '/:notfile/',
640+
permanent: true,
641+
locale: config.i18n ? false : undefined,
642+
internal: true,
643+
} as Redirect
644+
)
645+
if (config.basePath) {
646+
redirects.unshift({
647+
source: config.basePath,
648+
destination: config.basePath + '/',
649+
permanent: true,
650+
basePath: false,
651+
locale: config.i18n ? false : undefined,
652+
internal: true,
653+
} as Redirect)
654+
}
655+
} else {
663656
redirects.unshift({
664-
source: config.basePath + '/',
665-
destination: config.basePath,
657+
source: '/:path+/',
658+
destination: '/:path+',
666659
permanent: true,
667-
basePath: false,
668660
locale: config.i18n ? false : undefined,
669661
internal: true,
670662
} as Redirect)
663+
if (config.basePath) {
664+
redirects.unshift({
665+
source: config.basePath + '/',
666+
destination: config.basePath,
667+
permanent: true,
668+
basePath: false,
669+
locale: config.i18n ? false : undefined,
670+
internal: true,
671+
} as Redirect)
672+
}
671673
}
672674
}
673675

packages/next/server/base-server.ts

+27
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,33 @@ export default abstract class Server<ServerOptions extends Options = Options> {
434434
parsedUrl?: NextUrlWithParsedQuery
435435
): Promise<void> {
436436
try {
437+
// ensure cookies set in middleware are merged and
438+
// not overridden by API routes/getServerSideProps
439+
const _res = (res as any).originalResponse || res
440+
const origSetHeader = _res.setHeader.bind(_res)
441+
442+
_res.setHeader = (name: string, val: string | string[]) => {
443+
if (name.toLowerCase() === 'set-cookie') {
444+
const middlewareValue = getRequestMeta(req, '_nextMiddlewareCookie')
445+
446+
if (
447+
!middlewareValue ||
448+
!Array.isArray(val) ||
449+
!val.every((item, idx) => item === middlewareValue[idx])
450+
) {
451+
val = [
452+
...(middlewareValue || []),
453+
...(typeof val === 'string'
454+
? [val]
455+
: Array.isArray(val)
456+
? val
457+
: []),
458+
]
459+
}
460+
}
461+
return origSetHeader(name, val)
462+
}
463+
437464
const urlParts = (req.url || '').split('?')
438465
const urlNoQuery = urlParts[0]
439466

packages/next/server/config-schema.ts

+9
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,9 @@ const configSchema = {
222222
adjustFontFallbacks: {
223223
type: 'boolean',
224224
},
225+
allowMiddlewareResponseBody: {
226+
type: 'boolean',
227+
},
225228
amp: {
226229
additionalProperties: false,
227230
properties: {
@@ -348,6 +351,12 @@ const configSchema = {
348351
sharedPool: {
349352
type: 'boolean',
350353
},
354+
skipMiddlewareUrlNormalize: {
355+
type: 'boolean',
356+
},
357+
skipTrailingSlashRedirect: {
358+
type: 'boolean',
359+
},
351360
sri: {
352361
properties: {
353362
algorithm: {

packages/next/server/config-shared.ts

+3
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ export interface NextJsWebpackConfig {
7878
}
7979

8080
export interface ExperimentalConfig {
81+
allowMiddlewareResponseBody?: boolean
82+
skipMiddlewareUrlNormalize?: boolean
83+
skipTrailingSlashRedirect?: boolean
8184
optimisticClientCache?: boolean
8285
legacyBrowsers?: boolean
8386
browsersListForSwc?: boolean

packages/next/server/next-server.ts

+24-3
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ import { normalizePagePath } from '../shared/lib/page-path/normalize-page-path'
8383
import { loadComponents } from './load-components'
8484
import isError, { getProperError } from '../lib/is-error'
8585
import { FontManifest } from './font-utils'
86-
import { toNodeHeaders } from './web/utils'
86+
import { splitCookiesString, toNodeHeaders } from './web/utils'
8787
import { relativizeURL } from '../shared/lib/router/utils/relativize-url'
8888
import { prepareDestination } from '../shared/lib/router/utils/prepare-destination'
8989
import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path'
@@ -1051,9 +1051,17 @@ export default class NextNodeServer extends BaseServer {
10511051
name: '_next/data catchall',
10521052
check: true,
10531053
fn: async (req, res, params, _parsedUrl) => {
1054+
const isNextDataNormalizing = getRequestMeta(
1055+
req,
1056+
'_nextDataNormalizing'
1057+
)
1058+
10541059
// Make sure to 404 for /_next/data/ itself and
10551060
// we also want to 404 if the buildId isn't correct
10561061
if (!params.path || params.path[0] !== this.buildId) {
1062+
if (isNextDataNormalizing) {
1063+
return { finished: false }
1064+
}
10571065
await this.render404(req, res, _parsedUrl)
10581066
return {
10591067
finished: true,
@@ -1797,6 +1805,14 @@ export default class NextNodeServer extends BaseServer {
17971805
} else {
17981806
for (let [key, value] of allHeaders) {
17991807
result.response.headers.set(key, value)
1808+
1809+
if (key.toLowerCase() === 'set-cookie') {
1810+
addRequestMeta(
1811+
params.request,
1812+
'_nextMiddlewareCookie',
1813+
splitCookiesString(value)
1814+
)
1815+
}
18001816
}
18011817
}
18021818

@@ -2105,8 +2121,13 @@ export default class NextNodeServer extends BaseServer {
21052121
params.res.statusCode = result.response.status
21062122
params.res.statusMessage = result.response.statusText
21072123

2108-
result.response.headers.forEach((value, key) => {
2109-
params.res.appendHeader(key, value)
2124+
result.response.headers.forEach((value: string, key) => {
2125+
// the append handling is special cased for `set-cookie`
2126+
if (key.toLowerCase() === 'set-cookie') {
2127+
params.res.setHeader(key, value)
2128+
} else {
2129+
params.res.appendHeader(key, value)
2130+
}
21102131
})
21112132

21122133
if (result.response.body) {

packages/next/server/request-meta.ts

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export interface RequestMeta {
2222
_nextHadBasePath?: boolean
2323
_nextRewroteUrl?: string
2424
_protocol?: string
25+
_nextMiddlewareCookie?: string[]
26+
_nextDataNormalizing?: boolean
2527
}
2628

2729
export function getRequestMeta(

packages/next/server/router.ts

+18-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import type {
77
} from '../shared/lib/router/utils/route-matcher'
88
import type { RouteHas } from '../lib/load-custom-routes'
99

10-
import { getNextInternalQuery, NextUrlWithParsedQuery } from './request-meta'
10+
import {
11+
addRequestMeta,
12+
getNextInternalQuery,
13+
NextUrlWithParsedQuery,
14+
} from './request-meta'
1115
import { getPathMatch } from '../shared/lib/router/utils/path-match'
1216
import { removeTrailingSlash } from '../shared/lib/router/utils/remove-trailing-slash'
1317
import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path'
@@ -189,7 +193,11 @@ export default class Router {
189193
...(middlewareCatchAllRoute
190194
? this.fsRoutes
191195
.filter((route) => route.name === '_next/data catchall')
192-
.map((route) => ({ ...route, check: false }))
196+
.map((route) => ({
197+
...route,
198+
name: '_next/data normalizing',
199+
check: false,
200+
}))
193201
: []),
194202
...this.headers,
195203
...this.redirects,
@@ -433,6 +441,11 @@ export default class Router {
433441
}
434442

435443
if (params) {
444+
const isNextDataNormalizing = route.name === '_next/data normalizing'
445+
446+
if (isNextDataNormalizing) {
447+
addRequestMeta(req, '_nextDataNormalizing', true)
448+
}
436449
parsedUrlUpdated.pathname = matchPathname
437450
const result = await route.fn(
438451
req,
@@ -441,6 +454,9 @@ export default class Router {
441454
parsedUrlUpdated,
442455
upgradeHead
443456
)
457+
if (isNextDataNormalizing) {
458+
addRequestMeta(req, '_nextDataNormalizing', false)
459+
}
444460
if (result.finished) {
445461
return true
446462
}

0 commit comments

Comments
 (0)