Integrating Advanced Localization in Next.js: From Built-In CSK Localization to next-intl
Last updated: June 22, 2026
Category: SDK
Problem Statement
The Component Starter Kit (CSK) ships with basic, dependency-free localization that works well for early development. Once you need URL-based locale routing, centralized locale configuration, and locale-aware middleware, migrate to next-intl — the localization library recommended by Next.js — while keeping the Uniform setup intact.
Solution
File-structure:
src
├── i18n
│ ├── locales.json
│ ├── routing.ts
│ └── request.ts
├── middleware.ts
└── app
└── [locale]
├── layout.tsx
└── [[...path]]
└── page.tsx1. Install next-intl
npm install next-intl2. Configure next-intl in next.config.js
Wrap your config with both the next-intl plugin and withUniformConfig, and add an additional remotePatterns instance:
const createNextIntlPlugin = require('next-intl/plugin');
const { withUniformConfig } = require('@uniformdev/canvas-next-rsc/config');
const withNextIntl = createNextIntlPlugin();
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{ protocol: 'https', hostname: '*' },
{
protocol: 'https',
hostname: 'uniform.global',
},
],
deviceSizes: [320, 420, 640, 768, 1024, 1280, 1536],
},
};
module.exports = withUniformConfig(withNextIntl(nextConfig));
3. Wrap the app in NextIntlClientProvider
In app/layout.tsx, add NextIntlClientProvider to enable next-intl across all components:
import { ReactNode } from 'react';
import { NextIntlClientProvider, hasLocale } from 'next-intl';
import { setRequestLocale, getMessages } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { ThemeProvider as NextThemeProvider } from 'next-themes';
import { UniformContext } from '@uniformdev/canvas-next-rsc';
import { routing } from '@/i18n/routing';
import '@/styles/globals.css';
type Props = {
children: ReactNode;
params: Promise<{ locale: string }>;
};
export function generateStaticParams() {
return routing.locales.map((locale) => ({ locale }));
}
export default async function Layout({ children, params }: Props) {
const { locale } = await params;
// Validate the incoming locale before trusting it
if (!hasLocale(routing.locales, locale)) {
notFound();
}
// Enable static rendering for Server Components in this tree
setRequestLocale(locale);
// Omit if you don't use next-intl translation hooks
const messages = await getMessages();
return (
<html lang={locale} suppressHydrationWarning>
<body>
<NextThemeProvider attribute="class">
<NextIntlClientProvider messages={messages}>
<UniformContext>{children}</UniformContext>
</NextIntlClientProvider>
</NextThemeProvider>
</body>
</html>
);
}4. Create the request configuration
Add src/i18n/request.ts to select the locale from the URL and fall back to the default for unsupported locales:
import { getRequestConfig } from 'next-intl/server';
import { hasLocale } from 'next-intl';
import { routing } from './routing';
export default getRequestConfig(async ({ requestLocale }) => {
// Typically corresponds to the `[locale]` segment matched by middleware
const requested = await requestLocale;
const locale = hasLocale(routing.locales, requested)
? requested
: routing.defaultLocale;
return {
locale,
// Only needed if you use next-intl's own translation hooks
// (useTranslations / getMessages). If you rely solely on Uniform
// for content localization, you can omit `messages`.
messages: (await import(`../../messages/${locale}.json`)).default,
};
});
5. Define routing
Create src/i18n/routing.ts to enable URL-based locale routing driven by locales.json:
import { defineRouting } from 'next-intl/routing';
import config from './locales.json';
export const routing = defineRouting(config);This file enables URL-based locale routing based on locales.json. It dynamically updates the language setting based on the URL structure, improving SEO and user experience for multilingual setups.
6. Add middleware for locale routing
Create src/middleware.ts. The matcher excludes internal, static, and API routes:
import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';
export default createMiddleware(routing);
export const config = {
// Exclude API, Next internals, and any path containing a dot (static files)
matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)',
};
7. Remove the custom retrieveRoute function
With next-intl managing localization, import retrieveRoute directly from @uniformdev/canvas-next-rsc in page.tsx instead of using a custom implementation:
import { cookies } from 'next/headers';
import { notFound } from 'next/navigation';
import {
createServerUniformContext,
ContextUpdateTransfer,
retrieveRoute,
PageParameters,
UniformComposition,
} from '@uniformdev/canvas-next-rsc';
import { setRequestLocale } from 'next-intl/server';
import { ThemePackProvider } from '@uniformdev/theme-pack/components';
import componentResolver from '@/components';
import { isRouteWithoutErrors } from '@/utils';
type LocalePageParameters = PageParameters & {
params: Promise<{ locale: string; path?: string[] }>;
};
export default async function Home(props: LocalePageParameters) {
const { locale } = await props.params;
// Enable static rendering and align next-intl with the URL locale
setRequestLocale(locale);
const route = await retrieveRoute(props);
const serverContext = await createServerUniformContext({
searchParams: props.searchParams,
});
// cookies() is async in Next.js 15+
const theme = (await cookies()).get('theme')?.value || 'light';
if (!isRouteWithoutErrors(route)) {
return notFound();
}
return (
<ThemePackProvider>
<ContextUpdateTransfer
serverContext={serverContext}
update={{ quirks: { theme } }}
/>
<UniformComposition
{...props}
route={route}
resolveComponent={componentResolver}
mode="server"
/>
</ThemePackProvider>
);
}
export { generateMetadata } from '@/utils/generateMetadata';
Troubleshooting
Verify it works: start the app and open a locale-prefixed URL (e.g. /<locale>/...) — the page renders in that language, and an unsupported locale falls back to the default from locales.json.
Locale not switching from the URL: confirm the middleware matcher covers the route and that src/middleware.ts exists at the project source root.
Unsupported locale errors: make sure every locale in your URLs is listed in locales.json; request.ts falls back to routing.defaultLocale for anything else.