forked from amannn/next-intl
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add async
requestLocale
param to getRequestConfig
for Next.…
…js 15 support (amannn#1383) Since [Next.js is switching `headers()` to be `async`](vercel/next.js#68812), the `locale` that is passed to `getRequestConfig` needs to be replaced by an awaitable alternative. Note that this is only relevant for your app in case you're using i18n routing. ## tldr; Switch to the new API and call it a day: ```diff export default getRequestConfig(async ({ - locale + requestLocale }) => { + const locale = await requestLocale; // ... }); ``` If your app worked well before, then this is a 1:1 switch and will get your app in shape for Next.js 15. ## Details The new `requestLocale` parameter also offered a chance to get in some enhancements for edge cases that were previously harder to support. Therefore, the following migration is generally recommended: **Before:** ```tsx import {notFound} from 'next/navigation'; import {getRequestConfig} from 'next-intl/server'; import {routing} from './routing'; export default getRequestConfig(async ({locale}) => { // Validate that the incoming `locale` parameter is valid if (!routing.locales.includes(locale as any)) notFound(); return { // ... }; }); ``` **After:** ```tsx filename="src/i18n/request.ts" import {getRequestConfig} from 'next-intl/server'; import {routing} from './routing'; export default getRequestConfig(async ({requestLocale}) => { // This typically corresponds to the `[locale]` segment let locale = await requestLocale; // Ensure that the incoming locale is valid if (!locale || !routing.locales.includes(locale as any)) { locale = routing.defaultLocale; } return { locale, // ... }; }); ``` The differences are: 1. `requestLocale` is a promise that needs to be awaited 2. The resolved value can be `undefined`—therefore a default should be supplied. The default assignment allows handling cases where an error would be thrown previously, e.g. when using APIs like `useTranslations` on a global language selection page at `app/page.tsx`. 3. The `locale` should be returned (since you can now adjust it in the function body). 4. We now recommend calling `notFound()` in response to an invalid `[locale]` param in [`app/[locale]/layout.tsx`](https://next-intl-docs-git-feat-async-request-locale-next-intl.vercel.app/docs/getting-started/app-router/with-i18n-routing#layout) instead of in `i18n/request.ts`. This unlocks another use case, where APIs like `useTranslations` can now be used on a global `app/not-found.tsx` page. See also the [updated getting started docs](https://next-intl-docs-git-feat-async-request-locale-next-intl.vercel.app/docs/getting-started/app-router/with-i18n-routing#i18n-request). Note that this change is non-breaking, but the synchronously available `locale` is now considered deprecated and will be removed in a future major version. Contributes to amannn#1375 Addresses amannn#1355
- Loading branch information
Showing
15 changed files
with
157 additions
and
73 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,51 +1,43 @@ | ||
import {headers} from 'next/headers'; | ||
import {notFound} from 'next/navigation'; | ||
import {cache} from 'react'; | ||
import {HEADER_LOCALE_NAME} from '../../shared/constants'; | ||
import {getCachedRequestLocale} from './RequestLocaleCache'; | ||
|
||
function getLocaleFromHeaderImpl() { | ||
async function getHeadersImpl(): Promise<Headers> { | ||
const promiseOrValue = headers(); | ||
|
||
// Compatibility with Next.js <15 | ||
return promiseOrValue instanceof Promise | ||
? await promiseOrValue | ||
: promiseOrValue; | ||
} | ||
const getHeaders = cache(getHeadersImpl); | ||
|
||
async function getLocaleFromHeaderImpl(): Promise<string | undefined> { | ||
let locale; | ||
|
||
try { | ||
locale = headers().get(HEADER_LOCALE_NAME); | ||
locale = (await getHeaders()).get(HEADER_LOCALE_NAME) || undefined; | ||
} catch (error) { | ||
if ( | ||
error instanceof Error && | ||
(error as any).digest === 'DYNAMIC_SERVER_USAGE' | ||
) { | ||
throw new Error( | ||
const wrappedError = new Error( | ||
'Usage of next-intl APIs in Server Components currently opts into dynamic rendering. This limitation will eventually be lifted, but as a stopgap solution, you can use the `unstable_setRequestLocale` API to enable static rendering, see https://next-intl-docs.vercel.app/docs/getting-started/app-router/with-i18n-routing#static-rendering', | ||
{cause: error} | ||
); | ||
(wrappedError as any).digest = (error as any).digest; | ||
throw wrappedError; | ||
} else { | ||
throw error; | ||
} | ||
} | ||
|
||
if (!locale) { | ||
if (process.env.NODE_ENV !== 'production') { | ||
console.error( | ||
`\nUnable to find \`next-intl\` locale because the middleware didn't run on this request. See https://next-intl-docs.vercel.app/docs/routing/middleware#unable-to-find-locale. The \`notFound()\` function will be called as a result.\n` | ||
); | ||
} | ||
notFound(); | ||
} | ||
|
||
return locale; | ||
} | ||
const getLocaleFromHeader = cache(getLocaleFromHeaderImpl); | ||
|
||
// Workaround until `createServerContext` is available | ||
function getCacheImpl() { | ||
const value: {locale?: string} = {locale: undefined}; | ||
return value; | ||
} | ||
const getCache = cache(getCacheImpl); | ||
|
||
export function setRequestLocale(locale: string) { | ||
getCache().locale = locale; | ||
} | ||
|
||
export function getRequestLocale(): string { | ||
return getCache().locale || getLocaleFromHeader(); | ||
export async function getRequestLocale() { | ||
return getCachedRequestLocale() || (await getLocaleFromHeader()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import {cache} from 'react'; | ||
|
||
// See https://github.com/vercel/next.js/discussions/58862 | ||
function getCacheImpl() { | ||
const value: {locale?: string} = {locale: undefined}; | ||
return value; | ||
} | ||
|
||
const getCache = cache(getCacheImpl); | ||
|
||
export function getCachedRequestLocale() { | ||
return getCache().locale; | ||
} | ||
|
||
export function setCachedRequestLocale(locale: string) { | ||
getCache().locale = locale; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import {headers} from 'next/headers'; | ||
import {notFound} from 'next/navigation'; | ||
import {cache} from 'react'; | ||
import {HEADER_LOCALE_NAME} from '../../shared/constants'; | ||
import {getCachedRequestLocale} from './RequestLocaleCache'; | ||
|
||
// This was originally built for Next.js <14, where `headers()` was not async. | ||
// With https://github.com/vercel/next.js/pull/68812, the API became async. | ||
// This file can be removed once we remove the legacy navigation APIs. | ||
function getHeaders() { | ||
return headers(); | ||
} | ||
|
||
function getLocaleFromHeaderImpl() { | ||
let locale; | ||
|
||
try { | ||
locale = getHeaders().get(HEADER_LOCALE_NAME); | ||
} catch (error) { | ||
if ( | ||
error instanceof Error && | ||
(error as any).digest === 'DYNAMIC_SERVER_USAGE' | ||
) { | ||
throw new Error( | ||
'Usage of next-intl APIs in Server Components currently opts into dynamic rendering. This limitation will eventually be lifted, but as a stopgap solution, you can use the `unstable_setRequestLocale` API to enable static rendering, see https://next-intl-docs.vercel.app/docs/getting-started/app-router/with-i18n-routing#static-rendering', | ||
{cause: error} | ||
); | ||
} else { | ||
throw error; | ||
} | ||
} | ||
|
||
if (!locale) { | ||
if (process.env.NODE_ENV !== 'production') { | ||
console.error( | ||
`\nUnable to find \`next-intl\` locale because the middleware didn't run on this request. See https://next-intl-docs.vercel.app/docs/routing/middleware#unable-to-find-locale. The \`notFound()\` function will be called as a result.\n` | ||
); | ||
} | ||
notFound(); | ||
} | ||
|
||
return locale; | ||
} | ||
const getLocaleFromHeader = cache(getLocaleFromHeaderImpl); | ||
|
||
export function getRequestLocale(): string { | ||
return getCachedRequestLocale() || getLocaleFromHeader(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,7 @@ | ||
// eslint-disable-next-line import/no-extraneous-dependencies | ||
import getRuntimeConfig from 'next-intl/config'; | ||
import type {IntlConfig} from 'use-intl/core'; | ||
import type {GetRequestConfigParams} from './getRequestConfig'; | ||
import type {GetRequestConfigParams, RequestConfig} from './getRequestConfig'; | ||
|
||
export default getRuntimeConfig as unknown as ( | ||
params: GetRequestConfigParams | ||
) => IntlConfig | Promise<IntlConfig>; | ||
) => RequestConfig | Promise<RequestConfig>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.