CMS Features
Implement Localization
Configure languages in React Bricks, translate pages, and fetch localized content in a Next.js project.
React Bricks stores translations as localized versions of the same page.
In this how-to, you'll set up the editorial workflow in the Dashboard, translate a page, and wire your Next.js project so it fetches the right localized content for each route.
Docs reference: Localization
Configure languages in the Dashboard
Open the React Bricks Dashboard and go to Settings.
From there you can:
- add or remove languages, based on your plan limits
- choose the default language
The default language is important because React Bricks uses it as the source content when an editor creates a new translation. If a page has no translation in the default language, React Bricks copies content from the first available language instead.
Translate a page
Open a page in the React Bricks visual editor.
When your app has more than one language, the editor shows language tabs for the page. Click the tab for the language you want to translate.
If that translation does not exist yet, React Bricks asks whether you want to create it.
When a translation is created:
- content is copied from the default language when available
- each language keeps its own content
- page attributes, slug, SEO fields, meta data, and custom field values are independent for each translation
This means editors can localize not only the visible text, but also language-specific metadata and page-level custom fields.
Fetch one localized page
When rendering a page, pass the current locale as the language argument to fetchPage.
import { fetchPage } from 'react-bricks/rsc'
import config from '@/react-bricks/config'
export async function getPage(slug: string, locale: string) {
const page = await fetchPage({
slug,
language: locale,
config,
fetchOptions: { next: { revalidate: 3 } },
})
return page
}If you do not pass a language, React Bricks returns the page in the default language.
Fetch localized page lists
Use the same idea when fetching lists of pages.
For server-side or build-time lists, pass language in the fetchPages options object:
import { fetchPages } from 'react-bricks/frontend'
const posts = await fetchPages(process.env.API_KEY!, {
type: 'blog',
language: locale,
sort: '-publishedAt',
})Build a language switcher
A page returned by fetchPage includes a translations field with the available translations for that page.
Each translation includes the language, slug, page name, status, edit status, lock state, and scheduled publishing date.
type Page = {
translations: Translation[]
}
type Translation = {
language: string
slug: string
name: string
status: PageStatus
editStatus: EditStatus
isLocked: boolean
scheduledForPublishingOn: string
}Use translations to build links to the matching localized URL.
import Link from 'next/link'
type Translation = {
language: string
slug: string
name: string
}
export function LanguageSwitcher({
translations,
}: {
translations: Translation[]
}) {
return (
<nav aria-label="Languages">
{translations.map((translation) => (
<Link
key={translation.language}
href={`/${translation.language}/${translation.slug}`}
>
{translation.language.toUpperCase()}
</Link>
))}
</nav>
)
}Adapt the href format to your routing strategy. For example, the home page usually needs a special case so /en does not become /en/.
Configure Next.js App routing
If your project uses the Next.js App Router, place the locale in the route, for example:
app/[lang]/[[...slug]]/page.tsxThen read the route parameter and pass it to fetchPage:
export default async function Page({
params,
}: {
params: Promise<{ lang: string; slug?: string[] }>
}) {
const { lang, slug } = await params
const cleanSlug = slug?.join('/') ?? '/'
const page = await fetchPage({
slug: cleanSlug,
language: lang,
config,
fetchOptions: { next: { revalidate: 3 } },
})
return <PageViewer page={page} />
}For Next.js App projects, React Bricks provides createI18nMiddleware to centralize locale resolution in middleware.
Use it when your project has localized routes and you want middleware to resolve the correct language before the page renders.
In current Next.js App starters, middleware.ts is already set up for both i18n and optional A/B Testing:
import { NextResponse } from 'next/server'
import {
chain,
createI18nMiddleware,
createWithAbTestingMiddleware,
} from 'react-bricks/rsc'
import { i18n } from '@/i18n-config'
import { abTestingEnabled, getConfig } from './react-bricks/getConfig'
const rbConfig = getConfig()
const withAbTestingMiddleware = createWithAbTestingMiddleware({
i18n,
config: rbConfig,
})
const withI18nMiddleware = createI18nMiddleware({ i18n, NextResponse })
const middleware = abTestingEnabled
? chain([withAbTestingMiddleware, withI18nMiddleware])
: withI18nMiddleware
export default middleware
export const config = {
// Matcher ignoring `/_next/` and `/api/`
matcher: [
'/((?!api|_next/static|_next/image|favicon.ico|admin|logo.svg|bricks-preview-images|masks|preview).*)',
],
}If your project only uses localization, withI18nMiddleware handles the request.
If your project also uses A/B Testing, chain runs the A/B Testing middleware first and the i18n middleware after it, so the same request resolves both the active variant and the active locale.
If you maintain an older Next.js Pages Router project, see the Localization docs for the Pages Router setup.
Manage canonical and hreflang metadata
Localized pages should expose a canonical URL and hreflang alternates, so search engines can understand which translated URLs belong to the same page.
Create an i18n-config.ts file in the project root:
export const i18n = {
siteUrl: process.env.NEXT_PUBLIC_SITE_URL,
defaultLocale: 'en',
locales: ['en', 'it'],
} as const
export type Locale = (typeof i18n)['locales'][number]Then, in app/[lang]/[[...slug]]/page.tsx, use the page translations returned by React Bricks to create the alternate language URLs.
import type { Metadata } from 'next'
import { getMetadata } from 'react-bricks/rsc'
import { i18n, type Locale } from '@/i18n-config'
function getLocalizedUrl(language: string, slug: string) {
const normalizedSlug = slug === '/' ? '' : slug
const localePrefix = language === i18n.defaultLocale ? '' : `/${language}`
return `${i18n.siteUrl}${localePrefix}/${normalizedSlug}`
}
export async function generateMetadata(props: {
params: Promise<{ lang: Locale; slug?: string[] }>
}): Promise<Metadata> {
const params = await props.params
const cleanSlug = params.slug?.join('/') ?? '/'
const { page } = await getData(cleanSlug, params.lang)
if (!page?.meta) {
return {}
}
const metadata = getMetadata(page)
return {
...metadata,
alternates: {
canonical: getLocalizedUrl(params.lang, page.slug),
languages: page.translations.reduce<Record<string, string>>(
(acc, translation) => ({
...acc,
[translation.language]: getLocalizedUrl(
translation.language,
translation.slug
),
}),
{}
),
},
}
}With this pattern:
- the default language uses URLs without a locale prefix
- translated pages use URLs with the language prefix
- each translation gets its own canonical URL
page.translationsprovides the language and slug for each alternate URL
Check the result
After wiring localization:
- create or translate a page in at least two languages
- visit the localized routes in your app
- confirm each route fetches the correct translation
- check that language switcher links use the translated slugs
- verify localized canonical URLs, hreflang alternates, SEO fields, and custom fields if your page type uses them