Skip to content

Commit 45ef6ae

Browse files
authored
fix: matching on data routes when middleware matcher is using allowed paths (#3506)
1 parent 5d080ac commit 45ef6ae

19 files changed

Lines changed: 472 additions & 98 deletions

edge-runtime/lib/next-request.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
normalizeDataUrl,
88
normalizeLocalePath,
99
removeBasePath,
10+
rewriteDataPath,
1011
} from './util.ts'
1112

1213
interface I18NConfig {
@@ -82,18 +83,32 @@ export const localizeRequest = (
8283
},
8384
): { localizedUrl: URL; locale?: string } => {
8485
const localizedUrl = new URL(url)
85-
localizedUrl.pathname = removeBasePath(localizedUrl.pathname, nextConfig?.basePath)
86+
const pathnameWithoutBasePath = removeBasePath(localizedUrl.pathname, nextConfig?.basePath)
87+
88+
const isDataRequest = pathnameWithoutBasePath.startsWith('/_next/data/')
89+
90+
const normalizedPath = isDataRequest
91+
? normalizeDataUrl(pathnameWithoutBasePath)
92+
: pathnameWithoutBasePath
8693

8794
// Detect the locale from the URL
88-
const { detectedLocale } = normalizeLocalePath(localizedUrl.pathname, nextConfig?.i18n?.locales)
95+
const { detectedLocale } = normalizeLocalePath(normalizedPath, nextConfig?.i18n?.locales)
8996

9097
// Add the locale to the URL if not already present
91-
localizedUrl.pathname = addLocale(
92-
localizedUrl.pathname,
98+
const normalizedPathWithLocale = addLocale(
99+
normalizedPath,
93100
detectedLocale ?? nextConfig?.i18n?.defaultLocale,
94101
)
95102

96-
localizedUrl.pathname = addBasePath(localizedUrl.pathname, nextConfig?.basePath)
103+
localizedUrl.pathname = addBasePath(
104+
isDataRequest
105+
? rewriteDataPath({
106+
dataUrl: pathnameWithoutBasePath,
107+
newRoute: normalizedPathWithLocale,
108+
})
109+
: normalizedPathWithLocale,
110+
nextConfig?.basePath,
111+
)
97112

98113
return {
99114
localizedUrl,

edge-runtime/middleware.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,12 @@ export async function handleMiddleware(
4848
if (
4949
!matchesMiddleware(localizedUrl.pathname, request, searchParamsToUrlQuery(url.searchParams))
5050
) {
51-
reqLogger.debug('Aborting middleware due to runtime rules')
51+
reqLogger
52+
.withFields({
53+
next_original_url: url,
54+
next_localized_url: localizedUrl.href,
55+
})
56+
.debug('Aborting middleware due to runtime rules')
5257

5358
return
5459
}

package-lock.json

Lines changed: 1 addition & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@
9191
"os": "^0.1.2",
9292
"outdent": "^0.8.0",
9393
"p-limit": "^6.0.0",
94-
"path-to-regexp": "^6.2.1",
9594
"picomatch": "^4.0.2",
9695
"prettier": "^3.2.5",
9796
"semver": "^7.6.0",

src/build/functions/edge.ts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import type { Manifest, ManifestFunction } from '@netlify/edge-functions'
55
import { glob } from 'fast-glob'
66
import type { FunctionsConfigManifest } from 'next-with-cache-handler-v2/dist/build/index.js'
77
import type { EdgeFunctionDefinition as EdgeMiddlewareDefinition } from 'next-with-cache-handler-v2/dist/build/webpack/plugins/middleware-plugin.js'
8-
import { pathToRegexp } from 'path-to-regexp'
98

109
import { EDGE_HANDLER_NAME, PluginContext } from '../plugin-context.js'
1110

@@ -56,6 +55,20 @@ const copyRuntime = async (ctx: PluginContext, handlerDirectory: string): Promis
5655
)
5756
}
5857

58+
const fixEdgeRuntimeTurbopackMatcherJsonPart = (matchers: EdgeMiddlewareDefinition['matchers']) => {
59+
return matchers.map((matcher) => {
60+
if (matcher.regexp) {
61+
return {
62+
...matcher,
63+
// Next.js in some versions produces "\\\\.json" for edge runtime middleware when built with turbopack
64+
// with too many escapes preventing proper matching
65+
regexp: matcher.regexp.replaceAll('\\\\.json', '\\.json'),
66+
}
67+
}
68+
return matcher
69+
})
70+
}
71+
5972
/**
6073
* When i18n is enabled the matchers assume that paths _always_ include the
6174
* locale. We manually add an extra matcher for the original path without
@@ -80,13 +93,18 @@ const augmentMatchers = (
8093
// Next is producing pretty broad matcher for i18n locale. Presumably rest of their infrastructure protects this broad matcher
8194
// from matching on non-locale paths. For us this becomes request entry point, so we need to narrow it down to just defined locales
8295
// otherwise users might get unexpected matches on paths like `/api*`
83-
regexp: matcher.regexp.replace(/\[\^\/\.]+/g, `(${i18NConfig.locales.join('|')})`),
96+
// additionally we don't have a way to normalize i18n paths for request without locale information, so we need to adjust the regexp to mark locale part as optional
97+
regexp: matcher.regexp
98+
// replace i18n part matching:
99+
// - target locales only
100+
// - make it optional to allow matching both with and without locale
101+
// (?:\\/((?!_next\\/)[^/.]{1,}))
102+
.replace(
103+
'(?:\\/((?!_next\\/)[^/.]{1,}))',
104+
`(?:\\/((?!_next\\/)(${i18NConfig.locales.join('|')}){1,}))?`,
105+
),
84106
}
85107
: matcher,
86-
{
87-
...matcher,
88-
regexp: pathToRegexp(matcher.originalSource).source,
89-
},
90108
]
91109
}
92110
return matcher
@@ -330,7 +348,7 @@ export const createEdgeHandlers = async (ctx: PluginContext) => {
330348
runtime: 'edge',
331349
functionDefinition: edgeDefinition,
332350
name: edgeDefinition.name,
333-
matchers: edgeDefinition.matchers,
351+
matchers: fixEdgeRuntimeTurbopackMatcherJsonPart(edgeDefinition.matchers),
334352
}
335353
})
336354

0 commit comments

Comments
 (0)