How to Manage Your Homepage in Sanity Studio
In this guide, we'll explore two common approaches to managing homepages in Sanity, how to implement them, and which one I think makes the most sense.
Good program design is subjective—there are trade-offs involved, and matters of taste. The best way to learn the value of well-structured design is to read or work on a lot of programs and notice what works and what doesn’t. Don’t assume that a painful mess is just the way it is. You can improve the structure of almost everything by putting more thought into it.
Marijn Haverbeke, Eloquent JavaScript, 4th Edition, Chapter 10: Modules
Introduction
For years, monolithic CMS platforms like WordPress have dominated the Web, bundling content management and frontend rendering into a single system. This model has served as a viable solution for countless websites, but as digital experiences evolve beyond just websites to apps, kiosks, and other platforms, new challenges have emerged. Headless CMS solutions like Sanity offer an alternative approach by separating content from presentation, treating it as structured data that can be delivered anywhere.
There are many use cases for Sanity—but even if it were just a content management system, it would still be a top choice. While plenty of teams use Sanity as the single source of truth of content for multichannel business operations, its most common use case is powering high-traffic websites.
If you're new to Sanity, you might be wondering how to structure content for your website, especially when it comes to fundamental elements like the homepage. Unlike traditional content management systems that provide a predefined way to manage homepages, Sanity gives you the freedom to design your own approach—which can be both empowering and challenging.
If you've been around Sanity for a little while, you've likely noticed the various approaches the community has taken to managing homepages. Some opt for a dedicated homepage schema type (often called a singleton), while others treat it as just another page in their content structure. These different approaches highlight one of Sanity's core strengths: its flexibility in content modeling.
It's important to remember, however, that Sanity schemas should model your business, not your website. In systems like Wordpress, almost everything is a page. In Sanity, almost nothing is a page. Sure, you may have a lot of documents representing webpages in your Sanity studio, but in terms of content models, the schema for your webpages is just one of many.
Because Sanity doesn’t prescribe a single way to manage homepages, you’ll find different approaches within their own ecosystem of courses and templates. For example, the Clean Next.js + Sanity starter template follows Wordpress' default approach by rendering the latest blog posts. Turbo Start, a robust starter template from Roboto Studio, utilizes a singleton structure, while in Building landing pages with Next.js, a page reference within a global settings singleton is used to configure the homepage.
Before we dive into the technical details of each approach, it's worth noting that the solutions we're going to discuss have emerged from real-world needs and experiences. I’ve used each of these approaches to managing homepages on past projects, and each has its strengths and weaknesses. The singleton pattern often appeals to developers seeking a simple, predictable page structure, while the page approach offers more flexibility and familiarity for content teams transitioning from traditional CMS platforms.
With these different approaches in mind, let's explore each one, examine their trade-offs, and consider which might be the better fit for different types of projects.
If you're ready to jump straight into code, check out the repo and give it a star.
Prerequisites
If you're completely new to Sanity, I strongly recommend heading over to Sanity Learn as your starting point. Those courses provide excellent foundational knowledge that will help you understand the concepts and techniques we'll be exploring in this guide.
To ensure you get the most value from this discussion, here's a quick checklist of what you should already be familiar with:
- Creating and configuring document schemas
- Using the Structure Builder API to customize the Studio
- Basic concepts of content modeling in Sanity
- Working with references and arrays in Sanity schemas
While not strictly required, experience with traditional CMS platforms like WordPress will help provide context for the different approaches we'll discuss.
For the sake of simplicity, the code snippets below will exclude module imports and other non-essential code.
The Singleton Approach
When I was new to Sanity, I found myself following the singleton pattern for homepages because it seemed to be the conventional approach. However, something about it never quite felt right to me. The idea of treating the homepage as a special document type, distinct from other pages, felt at odds with what it actually was—just another page. While other one-off pages like About, Contact, and Privacy Policy were managed through the regular page schema, the homepage was inexplicably different. The more I thought about it, the more the singleton approach seemed like an unnecessary abstraction that added complexity without providing meaningful benefits. Regardless, many developers use this approach, so it's worth examining it in detail to understand its respective merits and drawbacks.
What is a Singleton?
A singleton is a special type of document schema that is designed to have only one instance in your Content Lake. The singleton pattern is particularly popular in the Sanity community because it provides a straightforward way to manage one-off content types. Think of it as a unique, standalone document that represents a singular entity in your system—like global settings, company information, or in this case, a homepage. Singleton documents usually contain very important content. Therefore, once published, they are typically never unpublished or deleted.
In practical terms, a homepage singleton typically includes:
- A dedicated schema type specifically for the homepage
- Custom desk structure configuration to make it easily accessible
- Validation logic to prevent creating multiple instances or unpublishing
- Fields specific to homepage content and layout
Defining the Schema
Here's a basic example of how you might implement a homepage singleton schema. First, define the schema:
export default defineType({
name: 'home',
title: 'Home',
type: 'document',
fields: [
defineField({
name: 'title',
title: 'Title',
type: 'string'
}),
// Additional fields
]
});
Next, add the schema to the schema types in sanity.config.ts
. For simplicity, I'm doing this here in one file, but it's good practice to extract your schema types array into a separate file to keep the config file tidy.
export default defineConfig({
// ... rest of config
schema: {
types: [home],
},
})
Customizing the Structure
By default, Sanity will list all your schema types at the top-level of the structure. Many teams choose to customize how the structure is laid out. Typically, but not always, homepage singletons are listed first in the structure.
To list the homepage singleton alongside other top-level content types, you could customize the desk structure like so:
export default defineConfig({
// ... rest of config
plugins: [
structureTool({
structure: (S) => {
return S.list()
.title('Content')
.items([
S.listItem()
.title('Home')
.child(
S.defaultDocument({
schemaType: 'home',
documentId: 'home',
}),
),
])
},
}),
],
})
The end result will look something like this:

Fetching the Data
Finally, you'd need to fetch the data stored in this document from your frontend. The query for that schema would be as simple as:
export const homeQuery = defineQuery(`
*[_type == "home"][0] {
...
}
`);
You can find full examples of how to fetch and render data using a frontend framework in any of the various templates on the Sanity Exchange.
Trade-offs and Considerations
The singleton pattern for homepages emerged as an early convention in the Sanity community, largely because it seemed to make sense at first glance. After all, most sites only have one homepage, so why not treat it as a special case? Homepages also typically don’t have slugs like other pages. Some teams have extended this approach to other single-instance pages, such as about pages, contact pages, and privacy policy pages. However, while these pages are typically singular in nature, treating them as special cases can actually lead to more complexity in your content model, as well as increased maintenance costs.
The first Sanity-powered site that I ever built for a client followed this pattern. At the time, I was still finding my footing with Sanity, and for my first client project—a small site with just a handful of pages—the singleton approach felt like the safest, most straightforward option. Looking back, I was probably overcomplicating things in my attempt to keep them simple.
If you ask me, the singleton pattern for homepages has less to do with best practices and more to do with convenience and short-term thinking. A lot of us saw what others were doing on smaller projects and just assumed that was the way to handle every project. Imitation is flattery, but sometimes it can lead us down paths that don't serve our needs.
Moreover, the singleton pattern can create a false sense of simplicity. Let's explore some of the challenges and limitations with this approach:
Inflexibility for Multiple Versions — While it's not very common, a site could have multiple versions of a homepage. The singleton pattern makes managing multiple versions of the homepage difficult to maintain. With the new Content Releases API, it's much easier to have multiple versions of the homepage—or multiple versions of any document for that matter. Editors can work on different homepage versions simultaneously—iterating, publishing, and rolling back changes as needed. However, as I understand it, these versions only exist in a single point in time, meaning they can't be served simultaneously, which can be problematic for A/B testing or region-specific variations.
Schema Redundancy — Even without the page builder pattern, homepage content often shares many fields with regular pages, leading to duplicate schema definitions unless carefully architected with shared field types.
Limited Content Reusability — By isolating homepage content in a singleton, you may inadvertently create content that can't be easily reused across other parts of your site, leading to a time-consuming refactor down the road.
Migration Challenges — If you later decide to switch from a singleton to a different approach, migrating the content structure can be more complex than if you had treated it as a regular page from the start.
While these limitations might not affect every project, they're important considerations when deciding how to structure your homepage content.
Final Thoughts
The singleton approach is sufficient for simple use cases with straightforward requirements, but it may become constraining as your project grows and evolves.
Despite the fact that they've opted for the singleton pattern for the homepage, the team at Roboto Studio has done an exceptional job at minimizing the impact of schema redundancies in their new battle-tested Sanity starter template, Turbo Start. I've been admiring Roboto's work for years, so if none of the other trade-offs apply to your next project, it may be an excellent starting point.
As we'll explore in the next section, treating the homepage as a regular page can provide more flexibility while maintaining the benefits of a structured content approach.

The Page Approach
Despite its faults, WordPress approached managing homepages with surprising simplicity. In a typical WordPress setup, the homepage—technically called the Front Page—is just another page. By default, many basic WordPress themes serve the latest posts as the homepage, but if you want a static homepage, you simply create a new page and set it as the homepage in the general settings. No special treatment, no singleton-like structure—just a page like any other.
With this setup, content editors can create multiple pages and easily designate any one of them as the homepage through the settings panel. This approach provides several advantages:
- Reuse existing page schemas and components
- Maintain content flexibility
- Simplify content modeling
- Enable easy homepage swapping
- Support multiple homepage versions for testing
In Sanity, we can implement this approach by:
- Using the existing page schema type for the homepage content
- Creating a simple settings document that references which page should serve as the homepage
- Configuring the Studio to make the homepage easily accessible
Defining the Page Schema
First, we'll need a basic page schema that includes fields for our page content. This could be as simple as a title and body field, or as complex as a full page builder implementation with multiple section types.
Here's a very basic example:
export default defineType({
name: 'page',
title: 'Page',
type: 'document',
fields: [
defineField({
name: 'title',
title: 'Title',
type: 'string'
}),
defineField({
name: 'slug',
title: 'Slug',
type: 'slug',
options: {
source: 'title',
},
}),
]
});
Defining the Settings Schema
Before defining our settings schema, let’s talk about why one might be useful. While the homepage is structurally just another page, we still need a reliable way to identify it. One approach is to query for a specific slug like /
, but this can become problematic if by some chance, albeit a small one, the homepage's URL structure needs to change down the road—for example, if an authenticated app serves its dashboard at the root while the marketing homepage moves to /home
. Vercel does this. It's just one more thing to worry about.
Another approach is to add a field like isHomepage
to the page schema, but this approach can lead to data integrity issues if multiple pages accidentally get flagged as the homepage. This approach requires overriding the built-in publish action for the settings schema so that maintaining data integrity becomes quite complex. Additional validation logic would need to implemented to ensure only one page can be marked as the homepage across both draft and published states. This adds unnecessary complexity to what should be a straightforward content model.
Yet another approach is utilizing a template field on the page schema that controls which additional fields appear or which layout is used on the frontend. Querying the homepage in this scenario would look like *[_type == "page" && template == "home"][0]{...}
. This method allows for different layouts and configurations on a per-page basis, similar to how traditional content management systems operate, but it introduces quite a bit of complexity—especially when optimizing queries and validating data on the frontend.
While all of these approaches to identifying the homepage are technically possible, a better approach, in my opinion, is to store a reference to the homepage in a settings document. This offers a much simpler way to manage homepage configuration without overcomplicating the page schema itself and allows us to query for the homepage explicitly without relying on hardcoded slugs or document-specific values.
Here's an example of a basic settings schema that includes a homepage reference:
export default defineType({
name: 'homeSettings',
title: 'Home Settings',
type: 'document',
fields: [
defineField({
name: 'homepage',
title: 'Homepage',
type: 'reference',
description: 'Choose a page to display as the homepage',
to: { type: 'page' },
options: {
disableNew: true,
},
}),
],
});
For reasons that will be better appreciated in the next step, you'll notice that I've decided to create a dedicated home settings singleton document. You could choose to include the reference field in a global settings document, which works well if homepage settings are minimal. But if the homepage requires additional configuration, defining a separate homepage settings document keeps things cleaner and more maintainable. Plus, I think it makes the next step result in a slightly better UX.
Customizing the Structure
Like the singleton approach, we'll customize the desk structure to display a link to the homepage at the top-level. While step isn’t completely necessary, and there are different ways to do it, I'd like to demonstrate one way it can be achieved. In reality, a project's desk structure should be customized to the actual needs of its users.
The trick here is that because we're not using a singleton for the homepage, we may not have a document available yet to display in the document pane when we click on "Home", which results in a nasty-looking error. We need a fallback document to show in case we haven't set a homepage in our settings document, which is why I opted for a dedicated home settings document in the previous step. In my opinion, it makes more sense to show homepage-specific settings as the fallback document rather than the global settings document.
To achieve the desired result, we'll listen for changes to the homepage field on the settings document, ensuring that the correct document dynamically appears when we click on 'Home'. While changes to this field may not occur frequently, this approach prioritizes flexibility for potential future needs rather than focusing on unlikely scenarios.
export const structure: StructureResolver = async (S, context) => {
// set up the settings singleton
const homeSettings = S.defaultDocument({
schemaType: 'homeSettings',
documentId: 'homeSettings',
}).title('Home Settings')
// set up the settings list item
const homeSettingsListItem = S.listItem()
.title('Home')
.id('home')
.icon(HomeIcon)
.child(homeSettings)
// listen for changes to the homepage setting
const getHomepage = () =>
getHomepageObservable(context.documentStore).pipe(
map((id) => {
// if a homepage has not been set
// show the settings singleton
if (!id) return homeSettings;
// otherwise, show the homepage
return S.document()
.schemaType('page')
.documentId(id)
}),
)
// set up the homepage list item
const home = S.listItem()
.title('Home')
.icon(HomeIcon)
.child(getHomepage)
// filter out the homepage from the main list of pages
const getFilteredPages = () =>
getHomepageObservable(context.documentStore).pipe(
map((id) => {
return S.documentTypeList('page')
.filter(
`_type == "page" && ($id == null || _id != $id && !(_id in path("drafts." + $id)))`,
)
.params({ id }).apiVersion('v2025-03-03').title('Pages')
}),
)
const pages = S.listItem()
.title('Pages')
.icon(PageIcon)
.child(getFilteredPages)
// set up a main settings list item
// in which to list other settings singletons
// (e.g. global, header, footer, navigation, homepage, etc.)
const settings = S.listItem()
.title('Settings')
.icon(SettingsIcon)
.child(S.list().title('Settings').items([homeSettingsListItem]))
// build the structure
return S.list()
.id('root')
.title('Content')
.items([home, S.divider(), pages, S.divider(), settings])
}
import {Observable} from 'rxjs'
import {type DocumentStore} from 'sanity'
import homeSettings from '../schemaTypes/homeSettings'
/**
* Fetches the homepage ID by resolving the observable.
*/
export function getHomepageObservable(documentStore: DocumentStore): Observable<string | null> {
return listenToQuery<string | null>(
documentStore,
`*[_id == "${homeSettings.name}"][0].homepage._ref`,
)
}
/**
* Returns an observable for a given Sanity query.
*/
export function listenToQuery<T>(
documentStore: DocumentStore,
query: string,
params: Record<string, any> = {},
): Observable<T> {
return documentStore.listenQuery(query, params, {}) as Observable<T>
}
If you prefer not to have a top-level 'Home' item in your structure when no static homepage is selected, you can take this observable pattern further. The structure can dynamically update based on whether a homepage is selected or if a settings field like 'Show in structure' is enabled. Instead of returning a static list, we can return an observable. Here's how to do that:
export const structure: StructureResolver = async (S, context) => {
// ... existing code
return getHomepageObservable(context.documentStore).pipe(
map((homepageId) => {
const items = [pages, S.divider(), settings]
if (homepageId) {
items.unshift(home, S.divider())
}
return S.list().id('root').title('Content').items(items)
}),
)
}
Fetching the Data
Fetching the homepage is just as simple as before:
export const homeQuery = defineQuery(`
*[_type == "homeSettings"][0].homepage-> {
...
}
`);
Trade-offs and Considerations
While the page approach offers many advantages, it's important to consider some potential trade-offs:
Initial Setup Complexity — The biggest departure from the singleton approach is the use of a query listener to dynamically update the desk structure. For teams unfamiliar with this technique, there’s a slight learning curve, but it’s not overly complex once understood, and, once it's implemented, you don't really need to touch it again.
Extra Settings Document — Introducing a separate settings document means there's now one more piece of content to manage. It’s not a dealbreaker, but it does introduce an extra layer of technical debt that wouldn’t exist if everything lived within a single homepage document.

Document Actions — With the singleton approach, preventing deletion and the ability to un-publish documents is straightforward—simply overriding the built-in document actions in your Sanity config allows you to remove those options. However, with the page-based approach, this becomes more complicated. If you choose not to override the built-in actions, then the homepage remains deletable and unpublishable like any other page. While this isn’t ideal, it’s worth considering that the ability to delete any page—including key ones like a contact or product listing page—is already a reality. The risk of losing the homepage isn’t necessarily worse than losing other important pages. That said, there are manual and automated ways to back up datasets, reducing the risk of irreversible loss. Additionally, Sanity could potentially introduce a trash bin system in the future, making this concern less relevant.
If preventing deletion is a priority, a hypothetical solution could be to implement a custom delete action that updates a hidden field (e.g., deleted or inTrash) instead of permanently removing the document. Then, the desk structure could be set up with a “Trash” category under “Pages,” alongside “All,” “Published,” and “Draft.” By customizing the list previews, these categories could be made more visually distinct, providing a practical alternative to outright deletion. That may be an interesting challenge to tackle in a future guide.
Query Performance — I haven’t noticed any performance impact in practice, but it’s worth mentioning. Because this approach relies on a query listener, the Studio is doing more work to keep the home document and pages list in sync whenever they are accessed. That said, in practical use, the performance difference appears negligible.
Beyond the Homepage
This setup isn’t just about making the homepage more flexible—it’s a foundation that can be adapted to fit a variety of needs.
- Blog Listing Page — This approach can easily be extended to a blog listing page—the "homepage" for your blog posts. In fact, you might just choose to set your blog listing page as your homepage.
- A/B Testing & Personalization – Since the homepage is just a page, you can swap it dynamically based on user segments, running A/B tests or serving different homepages to different audiences.
- Region-Specific Homepages – Need different homepages for different locations? With this structure, you can create region-based variations and set the correct one dynamically based on geolocation or user preferences.
- Multiple Homepage Layouts – Want to experiment with different homepage designs without affecting the live site? Create multiple pages and switch between them in the Home Settings panel without publishing your changes.
- Scheduled Homepage Changes – Pair this setup with Sanity’s Content Releases API to schedule homepage updates in advance, ensuring a seamless transition between versions.
Final Thoughts
At its core, this approach keeps your content model flexible and scalable—allowing you to tailor the homepage experience without locking yourself into a rigid singleton structure. From my perspective, the page approach offers the best balance of simplicity and flexibility. A homepage should be flexible. So should the way we think about them.

Conclusion
For me, the issue of whether homepages should be a singleton or just another page is simple: The homepage is a page. As such, it shares most, if not all, of the same schema fields as any other page, especially now that page builders have become a popular feature in modern Sanity projects.
To be fair, the singleton pattern we discussed earlier emerged during a time when Sanity's position on page builders was, more or less, "We don't really do that here." In the early days of Sanity, page builders were seen as an anti-pattern to structured content—and for good reason. Those of us with Wordpress backgrounds understand just how messy and unstructured content can become with page builder plug-ins that foster co-mingling of content with design.
Homepages may have unique presentation requirements, but their underlying content structure often mirrors that of other pages—they typically contain sections like heroes, feature blocks, and calls-to-action that appear throughout the site. These sections can be colocated within a common page builder field on the page schema. As we've previously discussed, the only real challenge with the page approach is figuring out which page is our homepage. But, as I've demonstrated, it's really not all that complicated.
Most programming is subjective—there’s rarely a single right solution to a problem. What works well today might not be the best approach tomorrow, and if your team already has a system that works, there’s no need change your methodology of managing the homepage. That’s the power of a headless CMS like Sanity—you can build content models in whichever way works best for your project.
If you have any questions about structuring homepages in Sanity or if you think I missed something or made a mistake, please don't hesitate to reach out.
Last updated