diff --git a/docs/src/pages/blog/_meta.tsx b/docs/src/pages/blog/_meta.tsx
index d2289e251..74bed960d 100644
--- a/docs/src/pages/blog/_meta.tsx
+++ b/docs/src/pages/blog/_meta.tsx
@@ -2,6 +2,10 @@ export default {
index: {
title: 'Overview'
},
+ 'nextjs-root-params': {
+ title: 'New in Next.js 15.X: rootParams',
+ display: 'hidden'
+ },
'next-intl-4-0': {
title: 'next-intl 4.0 beta',
display: 'hidden'
diff --git a/docs/src/pages/blog/index.mdx b/docs/src/pages/blog/index.mdx
index 5bbe075db..af7f9b3c0 100644
--- a/docs/src/pages/blog/index.mdx
+++ b/docs/src/pages/blog/index.mdx
@@ -4,6 +4,12 @@ import StayUpdated from '@/components/StayUpdated.mdx';
# next-intl blog
+
Dec XX, 2024 · by Jan Amann
+
+(this post is still a draft)
+
+Next.js v15.X was just released and comes with a new feature: [`rootParams`](https://github.com/vercel/next.js/pull/72837).
+
+This new API fills in the [missing piece](https://github.com/vercel/next.js/discussions/58862) that allows apps that use top-level dynamic segments like `[locale]` to read segment values deeply in Server Components:
+
+```tsx
+import {unstable_rootParams as rootParams} from 'next/server';
+
+async function Component() {
+ // The ability to read params deeply in
+ // Server Components ... finally!
+ const {locale} = await rootParams();
+}
+```
+
+This addition is a game-changer for `next-intl`.
+
+While the library previously relied on workarounds to provide a locale to Server Components, this API now provides native support in Next.js for this use case, allowing the library to integrate much tighter with Next.js.
+
+Practically, for users of `next-intl` this means:
+
+1. Being able to support static rendering of apps with i18n routing without `setRequestLocale`
+2. Improved integration with Next.js cache mechanisms
+3. Preparation for upcoming rendering modes in Next.js like [Partial Prerendering](https://nextjs.org/docs/app/api-reference/next-config-js/ppr) and [`dynamicIO`](https://nextjs.org/docs/canary/app/api-reference/config/next-config-js/dynamicIO) (although there seems to be more work necessary here on the Next.js side)
+
+But first, let's have a look at how this API works in practice.
+
+## Root layouts
+
+Previously, Next.js required a [root layout](https://nextjs.org/docs/app/api-reference/file-conventions/layout#root-layouts) to be present at `app/layout.tsx`.
+
+Now, you can move a root layout to a nested folder that can be a dynamic segment, e.g.:
+
+```
+src
+└── app
+ └── [locale]
+ ├── layout.tsx (root layout)
+ └── page.tsx
+```
+
+A root layout is a layout that has no other layouts located above it.
+
+In contrast, layouts that do have other layout ancestors are regular layouts:
+
+```
+src
+└── app
+ └── [locale]
+ ├── layout.tsx (root layout)
+ ├── (...)
+ └── news
+ ├── layout.tsx (regular layout)
+ └── page.tsx
+```
+
+With the addition of `rootParams`, you can now read param values of a root layout in all Server Components that render within it:
+
+```tsx filename=src/components/LocaleSwitcher.tsx
+import {unstable_rootParams as rootParams} from 'next/server';
+
+export async function LocaleSwitcher() {
+ // Read the value of `[locale]`
+ const {locale} = await rootParams();
+
+ // ...
+}
+```
+
+## Multiple root layouts
+
+Here's where it gets interesting: With [route groups](https://nextjs.org/docs/app/building-your-application/routing/route-groups), you can provide another layout for pages that are not located in the `[locale]` segment:
+
+```
+src
+└── app
+ ├── [locale]
+ │ ├── layout.tsx
+ │ └── page.tsx
+ └── (unlocalized)
+ ├── layout.tsx
+ └── page.tsx
+```
+
+The layout at `[locale]/layout.tsx` as well as the layout at `(unlocalized)/layout.tsx` both have no other layouts located above them, therefore both qualify as root layouts. Due to this, in this case the returned value of `rootParams` will depend on where the component that calls the function is being rendered from.
+
+If you call `rootParams` in shared code that is used by both root layouts, this allows for a pattern like this:
+
+```tsx filename="src/utils/getLocale.tsx"
+import {unstable_rootParams as rootParams} from 'next/server';
+
+export default async function getLocale() {
+ // Try to read the locale in case we're in `[locale]/layout.tsx`
+ let {locale} = await rootParams();
+
+ // If we're in `(unlocalized)/layout.tsx`, let's use a fallback
+ if (!locale) {
+ locale = 'en';
+ }
+
+ return locale;
+}
+```
+
+With this, you can use the `getLocale` function across your codebase to read the current locale without having to worry about where it's being called from.
+
+In an internationalized app, this can for example be useful to implement a country selection page at the root where you have to rely on a default locale. Once the user is within the `[locale]` segment, this param value can be used instead for localizing page content.
+
+## Static rendering
+
+In case we know all possible values for the `[locale]` segment ahead of time, we can provide them to Next.js using the [`generateStaticParams`](https://nextjs.org/docs/app/api-reference/functions/generate-static-params) function to enable static rendering:
+
+```tsx filename="src/app/[locale]/layout.tsx"
+const locales = ['en', 'de'];
+
+// Pre-render all available locales at build time
+export async function generateStaticParams() {
+ return locales.map((locale) => ({locale}));
+}
+
+// ...
+```
+
+In combination with [`dynamicParams`](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamicparams), we can furthermore instruct Next.js to disallow values that are encountered at runtime and do not match the values we've provided to `generateStaticParams`:
+
+```tsx filename="src/app/[locale]/layout.tsx"
+// Return a 404 for any unknown locales
+export const dynamicParams = false;
+
+// ...
+```
+
+## Leveraging `rootParams` in `next-intl`
+
+So, how can you use this feature in `next-intl`?
+
+Similarly to how we've defined the `getLocale` function above, we do in fact already have a shared place that is called by all server-side functions that require the current locale of the user: [`i18n/request.ts`](/docs/usage/configuration#server-client-components).
+
+So let's use `rootParams` here:
+
+```tsx filename="src/i18n/request.ts"
+import {unstable_rootParams as rootParams} from 'next/server';
+import {getRequestConfig} from 'next-intl/server';
+import {hasLocale} from 'next-intl';
+import {routing} from './routing';
+
+export default getRequestConfig(async () => {
+ const params = await rootParams();
+ const locale = hasLocale(routing.locales, params.locale)
+ ? params.locale
+ : routing.defaultLocale;
+
+ return {
+ locale,
+ messages: (await import(`../../messages/${locale}.json`)).default
+ };
+});
+```
+
+`hasLocale` is a new addition scheduled for [`next-intl@4.0`](/blog/next-intl-4-0), but practically simply checks if the provided `locales` array contains a given `locale`. If it doesn't, typically because we're not in the `[locale]` segment, a default locale is used instead.
+
+That's it—a single change to `i18n/request.ts` is all you need to do to start using `rootParams`!
+
+## Time for spring cleaning
+
+With this change, you can now simplify your codebase in various ways:
+
+### Removing a pass-through root layout
+
+For certain patterns like global 404 pages, you might have used a pass-through root layout so far:
+
+```tsx filename="src/app/layout.tsx"
+type Props = {
+ children: React.ReactNode;
+};
+
+export default function RootLayout({children}: Props) {
+ return children;
+}
+```
+
+This needs to be removed now as otherwise this will qualify as a root layout instead of the one defined at `src/app/[locale]/layout.tsx`.
+
+### Avoid reading the `[locale]` segment
+
+Since `next-intl` provides the current locale via [`useLocale` and `getLocale`](/docs/usage/configuration#locale), you can seamlessly read the locale from these APIs instead of `params` now:
+
+```diff filename="src/app/[locale]/layout.tsx"
++ import {getLocale} from 'next-intl/server';
+
+type Props = {
+ children: React.ReactNode;
+- params: {locale: string};
+};
+
+export default async function RootLayout({
+ children,
+- params
+}: Props) {
+- const {locale} = await params;
++ const locale = await getLocale();
+
+ return (
+
+ {children}
+
+ );
+}
+```
+
+Behind the scenes, if you call `useLocale` or `getLocale` in a Server Component, your `i18n/request.ts` config will be consulted, potentially using a fallback that you have defined.
+
+### Remove manual locale overrides [#locale-override]
+
+If you're using async APIs like `getTranslations`, you might have previously passed the locale manually, typically to enable static rendering in the Metadata API.
+
+Now, you can remove this and rely on the locale that is returned from `i18n/request.ts`:
+
+```diff filename="src/app/[locale]/page.tsx"
+- type Props = {
+- params: Promise<{locale: string}>;
+- };
+
+export async function generateMetadata(
+- {params}: Props
+) {
+- const {locale} = await params;
+- const t = await getTranslations({locale, namespace: 'HomePage'});
++ const t = await getTranslations('HomePage');
+
+ // ...
+}
+```
+
+The only rare case where you might still want to pass a locale to `getTranslations` is if your UI renders messages from multiple locales in parallel:
+
+```tsx
+// Use messages from the current locale
+const t = getTranslations();
+
+// Use messages from 'en', regardless of what the current user locale is
+const t = getTranslations({locale: 'en'});
+```
+
+In this case, you should make sure to accept an override in your `i18n/request.ts` config:
+
+```tsx filename="src/i18n/request.ts"
+import {unstable_rootParams as rootParams} from 'next/server';
+import {getRequestConfig} from 'next-intl/server';
+import {hasLocale} from 'next-intl';
+import {routing} from './routing';
+
+export default getRequestConfig(async ({locale}) => {
+ // Use a locale based on these priorities:
+ // 1. An override passed to the function
+ // 2. A locale from the `[locale]` segment
+ // 3. A default locale
+ if (!locale) {
+ const params = await rootParams();
+ locale = hasLocale(routing.locales, params.locale)
+ ? params.locale
+ : routing.defaultLocale;
+ }
+
+ // ...
+});
+```
+
+This is a very rare case, so if you're unsure, you very likely don't need this.
+
+### Static rendering
+
+If you've previously used `setRequestLocale` to enable static rendering, you can now remove it:
+
+```diff filename="src/[locale]/page.tsx"
+- import {setRequestLocale} from 'next-intl/server';
+
+- type Props = {
+- params: Promise<{locale: string}>;
+- }
+
+- export default function Page({params}: Props) {
+- setRequestLocale(params.locale);
++ export default function Page() {
+ // ...
+}
+```
+
+Note that `generateStaticParams` is naturally still required though.
+
+### Handling unknown locales
+
+Not strictly a new feature of Next.js, but in case you're using `generateStaticParams`, the easiest way to ensure that only the locales you've defined are allowed is to configure [`dynamicParams`](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamicparams) in your root layout:
+
+```tsx filename="src/app/[locale]/layout.tsx"
+// Return a 404 for any unknown locales
+export const dynamicParams = false;
+```
+
+If you don't use `generateStaticParams`, you can still disallow unknown locales by manually calling `notFound()` based on `params` in your root layout:
+
+```tsx filename="src/app/[locale]/layout.tsx"
+import {hasLocale} from 'next-intl';
+import {notFound} from 'next/navigation';
+import {routing} from '@/i18n/routing';
+
+type Props = {
+ children: React.ReactNode;
+ params: Promise<{locale: string}>;
+};
+
+export default async function RootLayout({children, params}: Props) {
+ const {locale} = await params;
+ if (!hasLocale(routing.locales, locale)) {
+ return notFound();
+ }
+
+ // ...
+}
+```
+
+## Try `rootParams` today!
+
+While this article mentions an upcoming `hasLocale` API from `next-intl@4.0` that simplifies working with `rootParams`, you can already try out the API today even in the `3.0` range.
+
+The one rare case where a change from `next-intl@4.0` is required, is if you need to [manually pass a locale](#locale-override) to async APIs like `getTranslations` in case your UI renders multiple locales in parallel.
+
+If you're giving `rootParams` a go with `next-intl`, let me know how it works for you by joining the discussion here: [Experiences with `rootParams`](https://github.com/amannn/next-intl/discussions/1627). I'm curious to hear how it simplifies your codebase!
+
+—Jan
+
+