Visual Editing with Next.js App Router and Sanity Studio
Setup "Live by Default" fetches and interactive live preview with Presentation in Sanity Studio
Go to Visual Editing with Next.js App Router and Sanity StudioRelating content in a structured way creates meaning. But what about the meaning we make when we shape content into hierarchies?
This guide explores the value of hierarchies, how they differ from other content structures, and how to best use them for navigation and beyond.
In another article, we learned about building fields and relationships in an open, flexible way.
But sometimes you need to order things into hierarchical parent-child structures. Let‘s take a closer look at the difference between the two approaches, when order is a good idea, and what influence navigation concerns should have on the way we build hierarchies.
When different types of content are linked together in a network of relationships they form a graph.
Graphs are everywhere in the world of data modeling, but they’re also super common in nature. A food web is a form of a graph defined by who eats who within an ecosystem.
We recently created a graph of relationships in our content model, where the structure of our graph is a function of the value we seek to deliver through it.
Graphs are really useful, but sometimes you need an organizing principle defined by hierarchical rules. That’s where trees come in.
Trees are just a hierarchical form of a graph. They’re usually bound by the rule that any node in a tree structure can have only one “parent”, but multiple “children”.
Sanity is naturally graph-like in the way it organizes content. You’re never forced into formal structures, but you can make them when you need to.
If you’ve ever found what you’re after within an organizational chart or site map, you’ve successfully navigated a tree. You’re also tree-making when you create nested folder structures on your computer. They’re a really handy tool for finding your way through a bunch of stuff, and their usefulness is comprised of three things:
Sometimes an item in a collection isn’t always suited to the organizing principles by which it is bound. As an example, pianos have strings but are often classified as percussion instruments due to the way they are played. In truth, pianos aren’t a great fit for either group, and their existence places pressure on the rules of the system.
Sometimes the best thing to do is make a new branch to accommodate your edge case, but at a certain point more branches = more confusion. Boundary conditions like these are hard to avoid, so when you encounter them try to balance the needs of the thing you’re categorizing against the needs of the collection. Most of the time you have to shoehorn a few things into funny slots in order to preserve the organizing principle.
To set up a child-to-parent relationship, we just make a reference to the same content type.
// schemas/documents/department.js
export default {
title: 'Department',
name: 'department',
type: 'document',
fields: [
{ title: 'Title', name: 'title', type: 'string', },
{
title: 'Parent Department',
name: 'parentDepartment',
type: 'reference',
to: [
{type: 'department'},
]
}
]
}
This approach is worthwhile if the overall structure of your tree is known in advance.
If you need to contain every published document within a tree, make the parent selector field mandatory with validation.
We can flip the previous example on its head with a parent-to-child relationship. Instead of referencing a single item, just change it to an array:
// schemas/documents/department.js
export default {
title: 'Department',
name: 'department',
type: 'document',
fields: [
{ title: 'Title', name: 'title', type: 'string', },
{
title: 'Child departments',
name: 'childDepartments',
type: 'array',
of: [
{
type: 'reference',
to: [
{type: 'department'},
]
}
]
}
]
}
It’s faster to connect things this way, but if you’re not careful you end up with documents containing multiple parents. Also, every time you want to move a “leaf” to a different “branch” you need to make the change in two places.
Sometimes referencing more than one parent is helpful. These relationships are called polyhierarchies. They’re common in e-commerce menus, where a user might expect to find a product in more than one place.
Bear in mind, you’re kinda “breaking the rules” of an organized system when you do this, and at a certain point, your tree becomes an informal graph again. When taking this approach it‘s often a good idea to list a "primary parent" to retain the formal qualities of your tree. This approach is handy when you need to provide breadcrumb navigation from a polyhierarchy.
You can also create a separate content type to handle hierarchies. From here it’s possible to reference any document from any number of types. This approach is handy if you need to apply a different layer of meaning to your content’s fundamental structure. You can think of this content type as an “organizing lens” that shows you a structure when you look through it. The advantage is that it frees you to make many “lenses” and reuse your content in many different hierarchies.
Website menus are a great use case. This method will let you quickly make a tree of links spanning different types without having to alter fields in the documents themselves. If you want to try out an alternate menu structure just make a new Table of Contents document and point your front end to the new version.
To limit this document type to a single instance, make it a singleton with Structure Builder.
Because this approach is decoupled from your main content structure, it can be easy to lose sync with new or changed documents.
// schemas/documents/toc.js
export default {
name: 'toc',
type: 'document',
title: 'Table of Contents',
fields: [
{
type: 'string',
name: 'title',
title: 'Title',
},
{
type: 'array',
name: 'sections',
title: 'Sections',
of: [{ type: 'tocSection' }]
}
]
}
// schemas/objects/tocSection.js
export default {
name: 'tocSection',
type: 'object',
title: 'Section',
fields: [
{
type: 'reference',
name: 'target',
title: 'Target',
to: [
{ type: 'department'},
// etc
]
},
{
type: 'string',
name: 'title',
title: 'Title'
// to override title from referenced items
},
{
type: 'array',
name: 'links',
title: 'Links',
of: [{ type: 'tocLink' }]
}
]
}
// schemas/objects/tocLink.js
export default {
title: 'TOC link',
name: 'tocLink',
type: 'object',
fields: [
{
type: 'reference',
name: 'target',
title: 'Target',
to: [
{ type: 'department'},
// etc
],
},
{
type: 'string',
name: 'title',
title: 'Title',
// to override title from referenced items
},
{
type: 'array',
name: 'children',
title: 'Children',
of: [{ type: 'tocLink' }]
}
],
}
The Table of Contents above lets you provide a hierarchical model for a certain audience without changing the fundamentals of your content graph. This headless way of thinking about navigation can let you:
We looked at the difference between graphs and trees, explored different ways to build trees with Sanity, and why separating navigation concerns from structure is usually a good idea.
Next up, we’ll explore different ways to support personalized content with Sanity.
Sanity Composable Content Cloud is the headless CMS that gives you (and your team) a content backend to drive websites and applications with modern tooling. It offers a real-time editing environment for content creators that’s easy to configure but designed to be customized with JavaScript and React when needed. With the hosted document store, you query content freely and easily integrate with any framework or data source to distribute and enrich content.
Sanity scales from weekend projects to enterprise needs and is used by companies like Puma, AT&T, Burger King, Tata, and Figma.
Setup "Live by Default" fetches and interactive live preview with Presentation in Sanity Studio
Go to Visual Editing with Next.js App Router and Sanity StudioA complete guide to setting up your blog using Astro and Sanity
Go to Build your blog with Astro and SanityThis guide teaches how to add a custom input component to a field for Sanity Studio v3
Go to How to build an input component for Sanity Studio v3A thorough intro to using GROQ-projections in a webhook contest
Go to GROQ-Powered Webhooks – Intro to Projections