Skip to content

Commit d871cd7

Browse files
committed
fix: match edge runtime pages with optional trailing slash
1 parent b83c2c3 commit d871cd7

File tree

2 files changed

+82
-52
lines changed

2 files changed

+82
-52
lines changed

packages/runtime/src/helpers/edge.ts

Lines changed: 81 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -202,27 +202,16 @@ const writeEdgeFunction = async ({
202202
edgeFunctionDefinition,
203203
edgeFunctionRoot,
204204
netlifyConfig,
205-
pageRegexMap,
206-
appPathRoutesManifest = {},
207-
nextConfig,
208-
cache,
205+
functionName,
206+
matchers,
209207
}: {
210208
edgeFunctionDefinition: EdgeFunctionDefinition
211209
edgeFunctionRoot: string
212210
netlifyConfig: NetlifyConfig
213-
pageRegexMap?: Map<string, string>
214-
appPathRoutesManifest?: Record<string, string>
215-
nextConfig: NextConfig
216-
cache?: 'manual'
217-
}): Promise<
218-
Array<{
219-
function: string
220-
name: string
221-
pattern: string
222-
}>
223-
> => {
224-
const name = sanitizeName(edgeFunctionDefinition.name)
225-
const edgeFunctionDir = join(edgeFunctionRoot, name)
211+
functionName: string
212+
matchers: Array<MiddlewareMatcher>
213+
}) => {
214+
const edgeFunctionDir = join(edgeFunctionRoot, functionName)
226215

227216
const bundle = await getMiddlewareBundle({
228217
edgeFunctionDefinition,
@@ -238,39 +227,45 @@ const writeEdgeFunction = async ({
238227
target: 'index.ts',
239228
})
240229

241-
const matchers: EdgeFunctionDefinitionV2['matchers'] = []
230+
await writeJson(join(edgeFunctionDir, 'matchers.json'), matchers)
231+
}
242232

233+
const generateEdgeFunctionMiddlewareMatchers = ({
234+
edgeFunctionDefinition,
235+
nextConfig,
236+
}: {
237+
edgeFunctionDefinition: EdgeFunctionDefinition
238+
edgeFunctionRoot: string
239+
nextConfig: NextConfig
240+
cache?: 'manual'
241+
}): Array<MiddlewareMatcher> => {
243242
// The v1 middleware manifest has a single regexp, but the v2 has an array of matchers
244243
if ('regexp' in edgeFunctionDefinition) {
245-
matchers.push({ regexp: edgeFunctionDefinition.regexp })
246-
} else if (nextConfig.i18n) {
247-
matchers.push(
248-
...edgeFunctionDefinition.matchers.map((matcher) => ({
249-
...matcher,
250-
regexp: makeLocaleOptional(matcher.regexp),
251-
})),
252-
)
253-
} else {
254-
matchers.push(...edgeFunctionDefinition.matchers)
244+
return [{ regexp: edgeFunctionDefinition.regexp }]
255245
}
256-
257-
// If the EF matches a page, it's an app dir page so needs a matcher too
258-
// The object will be empty if appDir isn't enabled in the Next config
259-
if (pageRegexMap && edgeFunctionDefinition.page in appPathRoutesManifest) {
260-
const regexp = pageRegexMap.get(appPathRoutesManifest[edgeFunctionDefinition.page])
261-
if (regexp) {
262-
matchers.push({ regexp })
263-
}
246+
if (nextConfig.i18n) {
247+
return edgeFunctionDefinition.matchers.map((matcher) => ({
248+
...matcher,
249+
regexp: makeLocaleOptional(matcher.regexp),
250+
}))
264251
}
252+
return edgeFunctionDefinition.matchers
253+
}
265254

266-
await writeJson(join(edgeFunctionDir, 'matchers.json'), matchers)
267-
268-
// We add a defintion for each matching path
269-
return matchers.map((matcher) => {
270-
const pattern = transformCaptureGroups(stripLookahead(matcher.regexp))
271-
return { function: name, pattern, name: edgeFunctionDefinition.name, cache }
272-
})
255+
const middlewareMatcherToEdgeFunctionDefinition = (
256+
matcher: MiddlewareMatcher,
257+
name: string,
258+
cache?: 'manual',
259+
): {
260+
function: string
261+
name?: string
262+
pattern: string
263+
cache?: 'manual'
264+
} => {
265+
const pattern = transformCaptureGroups(stripLookahead(matcher.regexp))
266+
return { function: name, pattern, name, cache }
273267
}
268+
274269
export const cleanupEdgeFunctions = ({
275270
INTERNAL_EDGE_FUNCTIONS_SRC = '.netlify/edge-functions',
276271
}: NetlifyPluginConstants) => emptyDir(INTERNAL_EDGE_FUNCTIONS_SRC)
@@ -348,9 +343,27 @@ export const writeRscDataEdgeFunction = async ({
348343
]
349344
}
350345

346+
const getEdgeFunctionPatternForPage = ({
347+
edgeFunctionDefinition,
348+
pageRegexMap,
349+
appPathRoutesManifest,
350+
}: {
351+
edgeFunctionDefinition: EdgeFunctionDefinitionV2
352+
pageRegexMap: Map<string, string>
353+
appPathRoutesManifest?: Record<string, string>
354+
}): string => {
355+
// We don't just use the matcher from the edge function definition, because it doesn't handle trailing slashes
356+
357+
// appDir functions have a name that _isn't_ the route name, but rather the route with `/page` appended
358+
const regexp = pageRegexMap.get(appPathRoutesManifest?.[edgeFunctionDefinition.page] ?? edgeFunctionDefinition.page)
359+
return regexp ?? edgeFunctionDefinition.matchers[0].regexp
360+
}
361+
351362
/**
352363
* Writes Edge Functions for the Next middleware
353364
*/
365+
366+
// eslint-disable-next-line max-lines-per-function
354367
export const writeEdgeFunctions = async ({
355368
netlifyConfig,
356369
routesManifest,
@@ -415,16 +428,24 @@ export const writeEdgeFunctions = async ({
415428
for (const middleware of middlewareManifest.sortedMiddleware) {
416429
usesEdge = true
417430
const edgeFunctionDefinition = middlewareManifest.middleware[middleware]
418-
const functionDefinitions = await writeEdgeFunction({
431+
const functionName = sanitizeName(edgeFunctionDefinition.name)
432+
const matchers = generateEdgeFunctionMiddlewareMatchers({
419433
edgeFunctionDefinition,
420434
edgeFunctionRoot,
421-
netlifyConfig,
422435
nextConfig,
423436
})
424-
manifest.functions.push(...functionDefinitions)
437+
await writeEdgeFunction({
438+
edgeFunctionDefinition,
439+
edgeFunctionRoot,
440+
netlifyConfig,
441+
functionName,
442+
matchers,
443+
})
444+
445+
manifest.functions.push(
446+
...matchers.map((matcher) => middlewareMatcherToEdgeFunctionDefinition(matcher, functionName)),
447+
)
425448
}
426-
// Older versions of the manifest format don't have the functions field
427-
// No, the version field was not incremented
428449
if (typeof middlewareManifest.functions === 'object') {
429450
// When using the app dir, we also need to check if the EF matches a page
430451
const appPathRoutesManifest = await loadAppPathRoutesManifest(netlifyConfig)
@@ -438,17 +459,26 @@ export const writeEdgeFunctions = async ({
438459

439460
for (const edgeFunctionDefinition of Object.values(middlewareManifest.functions)) {
440461
usesEdge = true
441-
const functionDefinitions = await writeEdgeFunction({
462+
const functionName = sanitizeName(edgeFunctionDefinition.name)
463+
await writeEdgeFunction({
442464
edgeFunctionDefinition,
443465
edgeFunctionRoot,
444466
netlifyConfig,
467+
functionName,
468+
matchers: edgeFunctionDefinition.matchers,
469+
})
470+
const pattern = getEdgeFunctionPatternForPage({
471+
edgeFunctionDefinition,
445472
pageRegexMap,
446473
appPathRoutesManifest,
447-
nextConfig,
474+
})
475+
manifest.functions.push({
476+
function: functionName,
477+
name: edgeFunctionDefinition.name,
478+
pattern,
448479
// cache: "manual" is currently experimental, so we restrict it to sites that use experimental appDir
449480
cache: usesAppDir ? 'manual' : undefined,
450481
})
451-
manifest.functions.push(...functionDefinitions)
452482
}
453483
}
454484
if (usesEdge) {

test/e2e/modified-tests/streaming-ssr/index.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ describe('react 18 streaming SSR with custom next configs', () => {
4343
expect(html).toContain('color:blue')
4444
})
4545
// NTL Skip
46-
usuallySkip('should redirect paths without trailing-slash and render when slash is appended', async () => {
46+
it('should redirect paths without trailing-slash and render when slash is appended', async () => {
4747
const page = '/hello'
4848
const redirectRes = await fetchViaHTTP(next.url, page, {}, { redirect: 'manual' })
4949
const res = await fetchViaHTTP(next.url, page + '/')

0 commit comments

Comments
 (0)