next-intl nextjs 16: Component Caching with root-params
Master next-intl nextjs 16 integration by leveraging next/root-params to safely enable component caching without prop-drilling your locale.

Many engineering teams struggle to balance server rendering efficiency with robust internationalization. When upgrading to modern Next.js versions, developers often discover that request-bound locale resolution can conflict with static rendering and cached Server Component scopes.
This guide explains how to configure next-intl with Next.js 16 Cache Components using next/root-params, so nested Server Components can resolve the active locale without passing it through every layout, page, and component.
next/root-params: Recent Next.js releases expose root route segments like[locale]as async getter functions such aslocale(), so Server Components can read the active locale without prop-drilling.cacheComponents: Enables Next.js Cache Components and the Reactuse cachedirective.proxy.ts: In Next.js 16,middleware.tsis deprecated and renamed toproxy.ts.
Also worth knowing:
requestLocale: In next-intl v4,getRequestConfigreceivesrequestLocale, which should be awaited, and the returned config should includelocale.- No prop-drilling: Nested Server Components can resolve the locale directly instead of receiving it from parent layouts.
- Current limits:
next/root-paramsis intended for Server Components. Client Components cannot import or call it directly; pass the locale as a prop, or use client-side routing APIs when needed.
What is next-intl caching in Next.js 16?
In this setup, next-intl caching means using localized Server Components together with the React use cache directive, while resolving the active locale through next/root-params instead of request headers.
Traditionally, internationalization libraries often relied on the incoming request to determine the active language. They might read the Accept-Language header, cookies, or other request data, then pass the locale through the app.
That works for dynamic rendering, but it becomes problematic when you want to cache or prerender parts of the UI. Cached Server Component scopes should avoid uncached request-bound APIs such as headers() and cookies().
Next.js Cache Components make this boundary more explicit. If a component is cached, it should depend on cache-safe inputs. For localized routes, the URL segment itself is a better cache key than request headers.
The problem: caching and locale resolution
Before next/root-params, developers using next-intl with static rendering often relied on setRequestLocale(locale) to make the locale available without forcing dynamic rendering.
That approach works, but it adds boilerplate. The function needs to be called in every layout and page that should render statically, because Next.js can render layouts and pages independently.
next/root-params provides a cleaner path. It allows Server Components to read root route params like [locale] directly, without passing the locale through props.
next/root-params exposes generated async getters for root params, such as locale() or lang().
| Architecture approach | Locale resolution | Compatible with use cache? |
Boilerplate |
|---|---|---|---|
| Request-bound rendering | headers() / cookies() |
Poor fit inside cached scopes | Low |
| Static forcing | setRequestLocale(locale) |
Yes | High |
| Root params pattern | next/root-params |
Yes, for Server Components | Low |
Versions and current limitations
Use this pattern only when your installed versions support the APIs below:
- Next.js 16 with
cacheComponents - A Next.js release that includes
next/root-params - A recent next-intl v4 release
- React 19
next/root-params moved through experimental and documented stages during the Next.js 16 release line. Verify that your installed Next.js version exposes next/root-params before replacing setRequestLocale() in production.
next/root-params is the successor to the earlier unstable_rootParams API. It allows async getter functions to be generated from root route params such as locale() or lang().
Current limitations:
- Client Components cannot import or call
next/root-paramsdirectly. - Server Actions are not supported.
- Route Handlers are not supported.
next/root-paramsis intended for Server Components.- If you need the locale in a Client Component, pass it from a Server Component or read route params with client-side routing APIs such as
useParams. - Always test your exact Next.js and next-intl versions before shipping this pattern in production.
Enabling Cache Components and root params
Enable Cache Components and root params in next.config.ts.
// next.config.ts
import createNextIntlPlugin from 'next-intl/plugin';
import type { NextConfig } from 'next';
const withNextIntl = createNextIntlPlugin();
const nextConfig: NextConfig = {
cacheComponents: true,
experimental: {
rootParams: true,
},
};
export default withNextIntl(nextConfig);
cacheComponents is a top-level option. In Next.js 16, it controls the Cache Components programming model and unifies the previous ppr, useCache, and dynamicIO configuration.
rootParams can be enabled explicitly under experimental, while cacheComponents enables the caching model that makes this pattern useful.
Required app structure
For next/root-params to expose [locale], the locale segment must be part of the root layout tree. That means your root layout should live inside app/[locale]/layout.tsx.
Recommended structure:
src/
├── i18n/
│ ├── routing.ts
│ └── request.ts
├── proxy.ts
└── app/
└── [locale]/
├── layout.tsx
└── page.tsx
For this pattern, avoid placing a root app/layout.tsx above [locale], because [locale] must be part of the root layout tree to be exposed through next/root-params.
Route parameters deeper in the tree are still accessed through the params prop. For example, in app/[locale]/blog/[slug]/page.tsx, locale can be a root param, while slug remains a regular route param.
Configuring proxy.ts
In Next.js 16, middleware.ts is deprecated and renamed to proxy.ts. The exported function should be named proxy, or you can use the supported default export form.
// src/proxy.ts
import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';
export const proxy = createMiddleware(routing);
export const config = {
matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)',
};
The matcher excludes common internal paths, API routes, Vercel internals, and files with extensions such as favicon.ico.
Runtime caveat
proxy.ts uses the Node.js runtime. The runtime config option is not available in Proxy files.
If you specifically need Edge runtime behavior, middleware.ts still works in Next.js 16, but it is deprecated. It is expected to be removed in a future version.
Configuring next-intl request config
In next-intl v4, getRequestConfig receives requestLocale when you use locale-based routing. You should await it and return the final locale.
You can also fall back to next/root-params when requestLocale is unavailable in cached Server Component scopes.
// src/i18n/request.ts
import { locale as getRootLocale } from 'next/root-params';
import { hasLocale } from 'next-intl';
import { getRequestConfig } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { routing } from './routing';
export default getRequestConfig(async ({ requestLocale }) => {
let locale = await requestLocale;
if (!locale) {
const paramValue = await getRootLocale();
if (hasLocale(routing.locales, paramValue)) {
locale = paramValue;
} else {
notFound();
}
}
return {
locale,
messages: (await import(`../../messages/${locale}.json`)).default,
};
});
This removes the need to call setRequestLocale(locale) across every layout and page for this caching-focused use case.
Outside this setup, setRequestLocale() remains the documented next-intl approach for enabling static rendering.
Simplifying the localized root layout
Now your localized root layout can read the active locale directly from next/root-params.
// src/app/[locale]/layout.tsx
import { locale as getRootLocale } from 'next/root-params';
import { hasLocale } from 'next-intl';
import { notFound } from 'next/navigation';
import type { ReactNode } from 'react';
import { routing } from '@/i18n/routing';
export function generateStaticParams() {
return routing.locales.map((locale) => ({ locale }));
}
type LocaleLayoutProps = {
children: ReactNode;
};
export default async function LocaleLayout({ children }: LocaleLayoutProps) {
const locale = await getRootLocale();
if (!hasLocale(routing.locales, locale)) {
notFound();
}
return (
<html lang={locale}>
<body>{children}</body>
</html>
);
}
generateStaticParams() is still important for prerendering localized routes. With Cache Components enabled, root parameters need static values for prerenderable shells.
Using use cache with localized components
Once the locale is available through next/root-params, cached Server Components can use it as part of their cache-safe input.
// src/app/[locale]/pricing/PricingSection.tsx
import { locale as getRootLocale } from 'next/root-params';
import { getTranslations } from 'next-intl/server';
async function fetchPricingTiers(locale: string) {
return [
{ id: '1', title: locale === 'de' ? 'Basis' : 'Basic' },
{ id: '2', title: locale === 'de' ? 'Profi' : 'Pro' },
];
}
export default async function PricingSection() {
'use cache';
const locale = await getRootLocale();
const t = await getTranslations('PricingSection');
const tiers = await fetchPricingTiers(locale);
return (
<section className="rounded-xl border border-neutral-200 p-6">
<h2 className="text-xl font-bold">{t('heading')}</h2>
<ul className="mt-4 space-y-2">
{tiers.map((tier) => (
<li key={tier.id} className="text-neutral-600">
{tier.title}
</li>
))}
</ul>
</section>
);
}
This lets deeply nested components fetch translations and localized data without receiving locale as a prop from the page or layout.
Next.js can track which root parameter getters a cached function uses. That means the cached output can be separated by the relevant root parameter, such as locale, instead of relying on request headers.
Verifying the production build
Run a production build:
npm run build
Routes that are fully prerenderable should appear as static or prerendered in the build output.
If a localized route becomes dynamic, inspect the render path for:
headers()cookies()- uncached data access
- request-bound APIs
- missing
generateStaticParams() - route logic that prevents prerendering
Cached functions cannot directly access cookies(), headers(), or searchParams; read them outside the cached scope and pass them in.
Not every localized route must be static. The goal is to make static and cached routes possible without prop-drilling the locale.
Common pitfalls
1. Keeping app/layout.tsx above [locale]
If [locale] is not part of the root layout tree, next/root-params cannot expose it as a root param.
Move the root layout to:
app/[locale]/layout.tsx
2. Treating searchParams like a function
In modern Next.js App Router pages, searchParams is a Promise prop. Await it directly.
Correct:
const params = await searchParams;
Wrong:
const params = await searchParams();
For Client Components, use React’s use() function to read the Promise instead of making the component async.
3. Using old middleware conventions
middleware.ts still runs in Next.js 16, but it is deprecated.
Use:
proxy.ts
And export either a default function or a named proxy function:
export const proxy = createMiddleware(routing);
4. Using outdated next-intl versions
Use a recent next-intl v4 release. next-intl v3 does not provide the same request config behavior needed for this pattern.
Before publishing an exact package pin, verify the current package version with your package manager.
5. Expecting direct Client Component, Server Action, or Route Handler support
next/root-params is for Server Components. Client Components cannot import or call it directly, and it does not currently support Server Actions or Route Handlers.
If you need the locale in a Client Component, pass it from a Server Component or use client-side routing APIs such as useParams.
If you need the locale in a Server Action, pass it explicitly as an argument from the component that calls the action.
FAQ
How do I use use cache with next-intl?
Enable cacheComponents: true in next.config.ts.
If your Next.js version includes next/root-params, also enable root params and configure getRequestConfig to resolve the locale from requestLocale, with next/root-params as a fallback when needed.
// next.config.ts
const nextConfig = {
cacheComponents: true,
experimental: {
rootParams: true,
},
};
Why can locale handling break static rendering?
Locale handling can break static rendering when it depends on request-bound APIs like headers() or cookies(). Those APIs make rendering request-specific, which conflicts with static rendering and cached Server Component scopes.
What is next/root-params?
next/root-params is a Next.js server API that exposes root route params as async getter functions.
For example, if your root dynamic segment is [locale], you can use:
import { locale } from 'next/root-params';
const value = await locale();
If your segment is [lang], the generated getter is lang().
Root parameter names must be valid JavaScript function identifiers, so avoid names like [post-slug] for root params you want to access this way.
Do I still need setRequestLocale?
For this next/root-params and Cache Components pattern, you generally do not need to call setRequestLocale() in every page and layout.
Outside this setup, or on Next.js versions where next/root-params is not available, setRequestLocale() remains the documented next-intl approach for enabling static rendering.
Can I use next/root-params inside unstable_cache?
No. Use the React use cache directive with Cache Components instead.
next/root-params is designed to work with use cache, where Next.js can track the root parameter getters used by the cached function.
Conclusion
Next.js 16 Cache Components make rendering boundaries more explicit, and internationalized apps need locale resolution that works inside those boundaries.
By combining cacheComponents, next/root-params, and next-intl v4 request configuration, you can remove repetitive locale prop-drilling, avoid request-header-based locale resolution in cached Server Components, and keep localized routes eligible for prerendering.
If you are building multilingual SaaS dashboards, admin panels, or template products, this pattern gives you a cleaner foundation for localized Server Components in Next.js 16.
Build faster with Aniq UI
Aniq UI templates give you production-ready Next.js foundations with TypeScript, Tailwind CSS, App Router layouts, Server Components, animations, and modern React patterns.
If you are building a SaaS dashboard, landing page, or admin panel, starting from a polished template can save setup time on routing, layouts, UI states, and rendering patterns.
References
- Next.js Proxy docs
- Next.js 16 upgrade guide
- Next.js Cache Components docs
- Next.js
use cachedocs - Next.js
page.jsfile convention docs - Next.js
useParamsdocs - next-intl routing setup docs
- next-intl request configuration docs
- next-intl root params discussion
- Next.js
next/root-paramsdocs in the Vercel repo


