Skip to content

Commit

Permalink
fix: handle locales in middleware redirects (#198)
Browse files Browse the repository at this point in the history
  • Loading branch information
ascorbic authored Jan 23, 2024
1 parent abd7509 commit 97af130
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 16 deletions.
30 changes: 25 additions & 5 deletions edge-runtime/lib/next-request.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Context } from '@netlify/edge-functions'

import { normalizeDataUrl, removeBasePath, removeLocaleFromPath } from './util.ts'
import { normalizeDataUrl, removeBasePath, normalizeLocalePath } from './util.ts'

interface I18NConfig {
defaultLocale: string
Expand Down Expand Up @@ -31,13 +31,27 @@ export interface RequestData {
}
url: string
body?: ReadableStream<Uint8Array>
detectedLocale?: string
}

const normalizeRequestURL = (originalURL: string, nextConfig?: RequestData['nextConfig']) => {
const normalizeRequestURL = (
originalURL: string,
nextConfig?: RequestData['nextConfig'],
): { url: string; detectedLocale?: string } => {
const url = new URL(originalURL)

url.pathname = removeBasePath(url.pathname, nextConfig?.basePath)
url.pathname = removeLocaleFromPath(url.pathname, nextConfig)

let detectedLocale: string | undefined

if (nextConfig?.i18n) {
const { pathname, detectedLocale: detected } = normalizeLocalePath(
url.pathname,
nextConfig?.i18n?.locales,
)
url.pathname = pathname
detectedLocale = detected
}

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

return url.toString()
return {
url: url.toString(),
detectedLocale,
}
}

export const buildNextRequest = (
Expand All @@ -69,13 +86,16 @@ export const buildNextRequest = (
timezone,
}

const { detectedLocale, url: normalizedUrl } = normalizeRequestURL(url, nextConfig)

return {
headers: Object.fromEntries(headers.entries()),
geo,
url: normalizeRequestURL(url, nextConfig),
url: normalizedUrl,
method,
ip: context.ip,
body: body ?? undefined,
nextConfig,
detectedLocale,
}
}
21 changes: 19 additions & 2 deletions edge-runtime/lib/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { HTMLRewriter } from '../vendor/deno.land/x/[email protected]/

import { updateModifiedHeaders } from './headers.ts'
import type { StructuredLogger } from './logging.ts'
import { normalizeDataUrl, relativizeURL, rewriteDataPath } from './util.ts'
import { normalizeDataUrl, normalizeLocalePath, relativizeURL, rewriteDataPath } from './util.ts'
import { addMiddlewareHeaders, isMiddlewareRequest, isMiddlewareResponse } from './middleware.ts'
import { RequestData } from './next-request.ts'

Expand All @@ -18,6 +18,7 @@ interface BuildResponseOptions {
request: Request
result: FetchEventResult
nextConfig?: RequestData['nextConfig']
requestLocale?: string
}

export const buildResponse = async ({
Expand All @@ -26,6 +27,7 @@ export const buildResponse = async ({
request,
result,
nextConfig,
requestLocale,
}: BuildResponseOptions): Promise<Response | void> => {
logger
.withFields({ is_nextresponse_next: result.response.headers.has('x-middleware-next') })
Expand Down Expand Up @@ -168,7 +170,22 @@ export const buildResponse = async ({
return addMiddlewareHeaders(fetch(new Request(rewriteUrl, request)), res)
}

const redirect = res.headers.get('Location')
let redirect = res.headers.get('location')

// If we are redirecting a request that had a locale in the URL, we need to add it back in
if (redirect && requestLocale) {
const redirectUrl = new URL(redirect, request.url)

const normalizedRedirect = normalizeLocalePath(redirectUrl.pathname, nextConfig?.i18n?.locales)

const locale = normalizedRedirect.detectedLocale ?? requestLocale
// Pages router API routes don't have a locale in the URL
if (locale && !redirectUrl.pathname.startsWith(`/api/`)) {
redirectUrl.pathname = `/${locale}${normalizedRedirect.pathname}`
redirect = redirectUrl.toString()
res.headers.set('location', redirect)
}
}

// Data requests shouldn't automatically redirect in the browser (they might be HTML pages): they're handled by the router
if (redirect && isDataReq) {
Expand Down
40 changes: 32 additions & 8 deletions edge-runtime/lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,40 @@ export const removeBasePath = (path: string, basePath?: string) => {
return path
}

export const removeLocaleFromPath = (path: string, nextConfig: RequestData['nextConfig']) => {
if (nextConfig?.i18n) {
for (const locale of nextConfig.i18n.locales) {
const regexp = new RegExp(`^/${locale}($|/)`, 'i')
if (path.match(regexp)) {
return path.replace(regexp, '/') || '/'
}
// https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/i18n/normalize-locale-path.ts

export interface PathLocale {
detectedLocale?: string
pathname: string
}

/**
* For a pathname that may include a locale from a list of locales, it
* removes the locale from the pathname returning it alongside with the
* detected locale.
*
* @param pathname A pathname that may include a locale.
* @param locales A list of locales.
* @returns The detected locale and pathname without locale
*/
export function normalizeLocalePath(pathname: string, locales?: string[]): PathLocale {
let detectedLocale: string | undefined
// first item will be empty string from splitting at first char
const pathnameParts = pathname.split('/')

;(locales || []).some((locale) => {
if (pathnameParts[1] && pathnameParts[1].toLowerCase() === locale.toLowerCase()) {
detectedLocale = locale
pathnameParts.splice(1, 1)
pathname = pathnameParts.join('/') || '/'
return true
}
return false
})
return {
pathname,
detectedLocale,
}
return path
}

/**
Expand Down
8 changes: 7 additions & 1 deletion edge-runtime/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,13 @@ export async function handleMiddleware(

try {
const result = await nextHandler({ request: nextRequest })
const response = await buildResponse({ context, logger: reqLogger, request, result })
const response = await buildResponse({
context,
logger: reqLogger,
request,
result,
requestLocale: nextRequest.detectedLocale,
})

return response
} catch (error) {
Expand Down

0 comments on commit 97af130

Please sign in to comment.