Skip to content

Commit 97af130

Browse files
authoredJan 23, 2024
fix: handle locales in middleware redirects (#198)
1 parent abd7509 commit 97af130

File tree

4 files changed

+83
-16
lines changed

4 files changed

+83
-16
lines changed
 

‎edge-runtime/lib/next-request.ts

+25-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Context } from '@netlify/edge-functions'
22

3-
import { normalizeDataUrl, removeBasePath, removeLocaleFromPath } from './util.ts'
3+
import { normalizeDataUrl, removeBasePath, normalizeLocalePath } from './util.ts'
44

55
interface I18NConfig {
66
defaultLocale: string
@@ -31,13 +31,27 @@ export interface RequestData {
3131
}
3232
url: string
3333
body?: ReadableStream<Uint8Array>
34+
detectedLocale?: string
3435
}
3536

36-
const normalizeRequestURL = (originalURL: string, nextConfig?: RequestData['nextConfig']) => {
37+
const normalizeRequestURL = (
38+
originalURL: string,
39+
nextConfig?: RequestData['nextConfig'],
40+
): { url: string; detectedLocale?: string } => {
3741
const url = new URL(originalURL)
3842

3943
url.pathname = removeBasePath(url.pathname, nextConfig?.basePath)
40-
url.pathname = removeLocaleFromPath(url.pathname, nextConfig)
44+
45+
let detectedLocale: string | undefined
46+
47+
if (nextConfig?.i18n) {
48+
const { pathname, detectedLocale: detected } = normalizeLocalePath(
49+
url.pathname,
50+
nextConfig?.i18n?.locales,
51+
)
52+
url.pathname = pathname
53+
detectedLocale = detected
54+
}
4155

4256
// We want to run middleware for data requests and expose the URL of the
4357
// corresponding pages, so we have to normalize the URLs before running
@@ -50,7 +64,10 @@ const normalizeRequestURL = (originalURL: string, nextConfig?: RequestData['next
5064
url.pathname = `${url.pathname}/`
5165
}
5266

53-
return url.toString()
67+
return {
68+
url: url.toString(),
69+
detectedLocale,
70+
}
5471
}
5572

5673
export const buildNextRequest = (
@@ -69,13 +86,16 @@ export const buildNextRequest = (
6986
timezone,
7087
}
7188

89+
const { detectedLocale, url: normalizedUrl } = normalizeRequestURL(url, nextConfig)
90+
7291
return {
7392
headers: Object.fromEntries(headers.entries()),
7493
geo,
75-
url: normalizeRequestURL(url, nextConfig),
94+
url: normalizedUrl,
7695
method,
7796
ip: context.ip,
7897
body: body ?? undefined,
7998
nextConfig,
99+
detectedLocale,
80100
}
81101
}

‎edge-runtime/lib/response.ts

+19-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { HTMLRewriter } from '../vendor/deno.land/x/html_rewriter@v0.1.0-pre.17/
33

44
import { updateModifiedHeaders } from './headers.ts'
55
import type { StructuredLogger } from './logging.ts'
6-
import { normalizeDataUrl, relativizeURL, rewriteDataPath } from './util.ts'
6+
import { normalizeDataUrl, normalizeLocalePath, relativizeURL, rewriteDataPath } from './util.ts'
77
import { addMiddlewareHeaders, isMiddlewareRequest, isMiddlewareResponse } from './middleware.ts'
88
import { RequestData } from './next-request.ts'
99

@@ -18,6 +18,7 @@ interface BuildResponseOptions {
1818
request: Request
1919
result: FetchEventResult
2020
nextConfig?: RequestData['nextConfig']
21+
requestLocale?: string
2122
}
2223

2324
export const buildResponse = async ({
@@ -26,6 +27,7 @@ export const buildResponse = async ({
2627
request,
2728
result,
2829
nextConfig,
30+
requestLocale,
2931
}: BuildResponseOptions): Promise<Response | void> => {
3032
logger
3133
.withFields({ is_nextresponse_next: result.response.headers.has('x-middleware-next') })
@@ -168,7 +170,22 @@ export const buildResponse = async ({
168170
return addMiddlewareHeaders(fetch(new Request(rewriteUrl, request)), res)
169171
}
170172

171-
const redirect = res.headers.get('Location')
173+
let redirect = res.headers.get('location')
174+
175+
// If we are redirecting a request that had a locale in the URL, we need to add it back in
176+
if (redirect && requestLocale) {
177+
const redirectUrl = new URL(redirect, request.url)
178+
179+
const normalizedRedirect = normalizeLocalePath(redirectUrl.pathname, nextConfig?.i18n?.locales)
180+
181+
const locale = normalizedRedirect.detectedLocale ?? requestLocale
182+
// Pages router API routes don't have a locale in the URL
183+
if (locale && !redirectUrl.pathname.startsWith(`/api/`)) {
184+
redirectUrl.pathname = `/${locale}${normalizedRedirect.pathname}`
185+
redirect = redirectUrl.toString()
186+
res.headers.set('location', redirect)
187+
}
188+
}
172189

173190
// Data requests shouldn't automatically redirect in the browser (they might be HTML pages): they're handled by the router
174191
if (redirect && isDataReq) {

‎edge-runtime/lib/util.ts

+32-8
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,40 @@ export const removeBasePath = (path: string, basePath?: string) => {
2424
return path
2525
}
2626

27-
export const removeLocaleFromPath = (path: string, nextConfig: RequestData['nextConfig']) => {
28-
if (nextConfig?.i18n) {
29-
for (const locale of nextConfig.i18n.locales) {
30-
const regexp = new RegExp(`^/${locale}($|/)`, 'i')
31-
if (path.match(regexp)) {
32-
return path.replace(regexp, '/') || '/'
33-
}
27+
// https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/i18n/normalize-locale-path.ts
28+
29+
export interface PathLocale {
30+
detectedLocale?: string
31+
pathname: string
32+
}
33+
34+
/**
35+
* For a pathname that may include a locale from a list of locales, it
36+
* removes the locale from the pathname returning it alongside with the
37+
* detected locale.
38+
*
39+
* @param pathname A pathname that may include a locale.
40+
* @param locales A list of locales.
41+
* @returns The detected locale and pathname without locale
42+
*/
43+
export function normalizeLocalePath(pathname: string, locales?: string[]): PathLocale {
44+
let detectedLocale: string | undefined
45+
// first item will be empty string from splitting at first char
46+
const pathnameParts = pathname.split('/')
47+
48+
;(locales || []).some((locale) => {
49+
if (pathnameParts[1] && pathnameParts[1].toLowerCase() === locale.toLowerCase()) {
50+
detectedLocale = locale
51+
pathnameParts.splice(1, 1)
52+
pathname = pathnameParts.join('/') || '/'
53+
return true
3454
}
55+
return false
56+
})
57+
return {
58+
pathname,
59+
detectedLocale,
3560
}
36-
return path
3761
}
3862

3963
/**

‎edge-runtime/middleware.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,13 @@ export async function handleMiddleware(
5353

5454
try {
5555
const result = await nextHandler({ request: nextRequest })
56-
const response = await buildResponse({ context, logger: reqLogger, request, result })
56+
const response = await buildResponse({
57+
context,
58+
logger: reqLogger,
59+
request,
60+
result,
61+
requestLocale: nextRequest.detectedLocale,
62+
})
5763

5864
return response
5965
} catch (error) {

0 commit comments

Comments
 (0)
Please sign in to comment.