'Post',
How I Wire Sanity CMS Multilingual Content to Next.js with next-intl
Introduction
Sanity CMS, along with the Next.js App Router and next-intl, offer a robust multilingual content management solution. However, connecting these components seamlessly can be quite challenging. This post documents the process of integrating Sanity CMS multilingual support into a production project using next-intl.
Responsibilities
First, let's clarify who is responsible for what:
- Sanity: Handles content storage through the document internationalization plugin.
- next-intl: Manages routing and message delivery via the
[locale]dynamic segment in URLs. - You (the developer): Acts as the bridge layer to ensure proper integration.
Schema Design
To achieve seamless multilingual functionality, it's crucial to design your schema carefully. Start by installing the Sanity document internationalization plugin:
npm i @sanity/document-internationalization
Next, configure it in sanity.config.ts:
// sanity.config.ts
import { defineConfig } from 'sanity';
import { documentInternationalization } from '@sanity/document-internationalization';
export default defineConfig({
// ... existing configurations
plugins: [
documentInternationalization({
supportedLanguages: [
{ id: 'en', title: 'English' },
{ id: 'fr', title: 'French' },
{ id: 'de', title: 'German' },
],
schemaTypes: ['page', 'post'],
}),
],
});
Design your schema to keep language-neutral fields on a shared document type and place all translatable fields on the locale document:
// schemas/post.ts
import { defineType, defineField } from 'sanity';
export const post = defineType({
name: 'post',
type: 'document',
fields: [
defineField({ name: 'language', type: 'string', readOnly: true, hidden: true }),
defineField({ name: 'slug', type: 'slug' }),
// ... other fields
],
});
GROQ Query Patterns
To fetch a single post by slug and locale, use the following GROQ query pattern:
// Fetch a single post by slug and locale
*[ _type == "post" && language == $locale && slug.current == $slug && !(_id in path("drafts.**")) ][0]
{
_id,
title,
slug,
language,
}
For listing pages that require alternate-locale URLs (canonical alternates for SEO hreflang), fetch all language variants via the _translations metadata reference:
// Get all locale versions of a document to build hreflang
*[ _type == "translation.metadata" && references($id) ][0]
{
translati...
Conclusion
By following these steps and maintaining clear responsibilities, you can effectively integrate Sanity CMS multilingual content with Next.js and next-intl. This setup ensures that your project is well-structured, scalable, and ready for internationalization.
[Title]: "How I wire Sanity CMS multilingual content to Next.js with next-intl" [Description]: "Sanity CMS multilingual support with next-intl in the Next.js App Router is one of those setups where each piece works fine in isolation but the wiring between them is fiddly. This post documents exactly how I connect the two on production projects." [Tags]: "Next.js, Sanity CMS, Multilingual Content, Internationalization"
Source: DEV Community. AI-assisted editorial synthesis — TechnoExpress.

