Best practices for storing all site text and navigation in Sanity
Great question! Managing all site data including navigation, labels, and text snippets in Sanity is definitely achievable and a common pattern. Here's how to approach this for a multi-language project:
Singleton Documents for Global Content
For navigation, footer, and site-wide text, you'll want to use singleton documents. These are document types that only have one instance and are perfect for global settings. You'd typically create schemas like:
siteSettings- for global configurationnavigation- for main menu structurefooter- for footer contentlabels- for UI strings like button text, form labels, etc.
Setting up singletons requires configuring three things in your sanity.config.js: the Structure Builder to create direct links to these documents, template filtering to prevent creating duplicates, and document actions to disable deletion. The singleton document guide walks through this, or you can use the singleton-tools plugin to simplify setup.
Choosing Your Localization Strategy
Sanity offers two main approaches for multi-language content, and based on the localization documentation, here's what works best:
Document-level localization (recommended for most cases): Each language gets its own complete document. The @sanity/document-internationalization plugin handles this by creating separate documents linked through a shared reference. This means you'd have navigation-en, navigation-es, etc. This approach:
- Avoids attribute limit issues
- Provides cleaner content structures
- Works great for all content types including Portable Text
Field-level localization: Store all translations within the same document. While convenient for simple use cases, this isn't recommended for complex content or Portable Text fields as it can quickly hit attribute limits and create unwieldy data structures.
Structuring Navigation and Labels
For navigation specifically, you might create a schema like:
{
name: 'navigation',
type: 'document',
fields: [
{
name: 'items',
type: 'array',
of: [{
type: 'object',
fields: [
{ name: 'label', type: 'string' },
{ name: 'url', type: 'string' },
// or reference to pages
{ name: 'page', type: 'reference', to: [{ type: 'page' }] }
]
}]
}
]
}For UI labels and strings, consider grouping them logically:
{
name: 'labels',
type: 'document',
fields: [
{ name: 'submitButton', type: 'string' },
{ name: 'learnMore', type: 'string' },
{ name: 'contactUs', type: 'string' }
// etc.
]
}Additional Considerations
- Language Filter: Use the Language Filter to help editors work with specific languages in the Studio interface
- Studio UI localization: You can also localize the Studio interface itself so editors work in their preferred language
- Fallback strategy: Plan how your frontend handles missing translations - typically falling back to a default language
- Translation workflows: If using professional translation services, Sanity integrates with Smartling and Transifex
The document-level approach with the @sanity/document-internationalization plugin combined with singleton documents for global content is generally the most scalable and maintainable solution for what you're describing. It keeps your data clean and makes it easy for editors to manage translations independently.
Show original thread16 replies
Sanity – Build the way you think, not the way your CMS thinks
Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.