How to translate page metadata in Next.js (Pages Router) v16
Translate metadata for search and social
Problem
Page metadata—titles and descriptions—appears outside the page content itself. It shows in browser tabs, bookmarks, search engine results, and social media previews. When metadata doesn't match the page's language, users encounter jarring inconsistency before they even see the content. A Spanish page with an English title confuses visitors and signals poor localization quality.
Search engines interpret language mismatches as indicators of low-quality localization, potentially lowering rankings in language-specific results. Social media platforms display the wrong language in link previews, reducing engagement from international audiences. Consistent translation across all touchpoints—including metadata—is essential for a professional multilingual experience.
Solution
Translate page metadata using the same translation resources as page content. Use react-intl's useIntl hook to access translated strings and place them in the page's <Head> component. This ensures titles and descriptions match the current locale in browser tabs, search results, and social previews.
By sourcing metadata from the same message catalogs as visible content, you maintain consistency and avoid duplicating translation effort. The approach works for both static pages and dynamic routes where metadata depends on page-specific data.
Steps
1. Add metadata messages to your translation files
Define message descriptors for page titles and descriptions in your translation catalogs, using the same structure as other translated content.
{
"home.title": "Welcome to Our Store",
"home.description": "Discover amazing products at great prices",
"products.title": "Our Products",
"products.description": "Browse our full catalog of items"
}
Each page that needs translated metadata should have corresponding message IDs for its title and description.
2. Create a translated metadata component
Use the useIntl hook to access the formatMessage function, then render translated strings inside the Head component.
import Head from "next/head";
import { useIntl } from "react-intl";
export default function HomePage() {
const intl = useIntl();
return (
<>
<Head>
<title>{intl.formatMessage({ id: "home.title" })}</title>
<meta
name="description"
content={intl.formatMessage({ id: "home.description" })}
/>
</Head>
<main>
<h1>{intl.formatMessage({ id: "home.title" })}</h1>
</main>
</>
);
}
The Head component is a React component built into Next.js that allows you to modify the <head> of a page. The formatMessage function returns the translated string for the current locale.
3. Add Open Graph and social metadata
Extend the pattern to include Open Graph and Twitter Card metadata for social media previews.
import Head from "next/head";
import { useIntl } from "react-intl";
export default function ProductsPage() {
const intl = useIntl();
const title = intl.formatMessage({ id: "products.title" });
const description = intl.formatMessage({ id: "products.description" });
return (
<>
<Head>
<title>{title}</title>
<meta name="description" content={description} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
</Head>
<main>
<h1>{title}</h1>
</main>
</>
);
}
Storing the formatted messages in variables avoids calling formatMessage multiple times for the same translation and keeps the JSX cleaner.
4. Handle dynamic metadata for parameterized pages
For pages with dynamic routes, combine route parameters with translated strings to create contextual metadata.
import Head from "next/head";
import { useIntl } from "react-intl";
import { useRouter } from "next/router";
export default function ProductDetailPage() {
const intl = useIntl();
const router = useRouter();
const { id } = router.query;
const title = intl.formatMessage(
{ id: "product.detail.title" },
{ productId: id },
);
return (
<>
<Head>
<title>{title}</title>
</Head>
<main>
<h1>{title}</h1>
</main>
</>
);
}
Message descriptors support variable interpolation, allowing you to inject dynamic values like product IDs or names into translated metadata strings. The corresponding message might be "Product {productId} - Our Store".