Sanity Schemas
This document outlines the structure and best practices for using Sanity schemas in the project, specifically focusing on link management and URL generation.
Architectural Patterns
Section titled “Architectural Patterns”Pages vs Blocks
Section titled “Pages vs Blocks”The codebase strictly distinguishes between Pages and Blocks to maintain a clean separation of concerns.
- Pages: Top-level documents that correspond to a specific route in the application (e.g.,
homePage,aboutUsPage,blogArticle). They define the page’s metadata and act as a container for blocks. - Blocks: Reusable components or sections (e.g.,
seoBlock,mainHeaderBlock,linkGroup). They do not have their own routes but are used within pages.
Schema name structure
Section titled “Schema name structure”To avoid magic strings and ensure type safety, all schema names are defined in the TypeName enum located at apps/web/src/lib/sanity/common/enums/type-name.ts.
Structure: The enum is grouped into:
Pages: Definitions for route-based documents.Blocks: Definitions for reusable components.
Rule: Always use TypeName.YOUR_CONSTANT instead of string literals when defining schemas or writing GROQ queries.
Reusable UI Sections
Section titled “Reusable UI Sections”A key architectural principle in this project is Reusability by Default.
The Pattern:
- Atomicity: Never define a complex UI section directly inside a Page schema.
- Encapsulation: Create a separate Block schema for that section (e.g.,
heroSectionBlock,faqSectionBlock). - Reference: In the Page schema, reference the Block using the
arrayofreferences pattern (or a specific field if it’s a singleton section).
Why?
- Multi-Page Reuse: A section designed for the Home page might later be needed on the About Us page. If it’s a Block, you simply add it to the other page’s supported blocks list.
- Independent Updates: You can update the schema or logic of a block in one place, and it propagates everywhere.
- Visual Clarity: The Studio UI remains clean, showing a list of sections rather than a massive form with hundreds of fields.
Core Concepts
Section titled “Core Concepts”References
Section titled “References”References are crucial in Sanity for connecting different documents. In our schema, references are primarily used in the link block to connect a link item to a specific page or external link document.
Why use references?
- Consistency: When a referenced document is updated (e.g., its title or slug changes), all links pointing to it automatically reflect the new data.
- Integrity: References ensure that links always point to existing content. If a referenced document is deleted, Sanity warns about potential broken links.
Intermediate Pages
Section titled “Intermediate Pages”The intermediatePages field is a powerful feature found in link and linkGroup schemas. It allows for the construction of hierarchical URLs without enforcing a strict tree structure in the content documents themselves.
Purpose:
Define the parent pages required to construct the full URL path. For example, if you want to link to a condition article at /conditions/chronic-pain, you would:
- Select the
chronic-painarticle as thepage. - Add the
conditionspage to theintermediatePagesarray.
Mechanism: The frontend or GROQ query concatenates the slugs of the intermediate pages with the slug of the target page to form the complete URL.
External Links
Section titled “External Links”External links are handled via a specific document type: externalLink.
Structure:
- Type:
document - Fields:
title: Internal title for the link.slug: Stores the actual external URL (e.g.,https://google.com).- Note: The
slugfield is of typestringand includes a uniqueness validator, ensuring you don’t duplicate external link entries.
- Note: The
Usage:
To reuse an external link, reference it in any link block, just like you would with an internal page.
URL Generation & Fetching
Section titled “URL Generation & Fetching”How URLs are Generated
Section titled “How URLs are Generated”URLs are not stored as pre-calculated strings in the database. Instead, they are dynamically generated at query time using GROQ.
*[_type == "mainFooterBlock"]{ policies[]{ "slug": coalesce(array::join(^.intermediatePages[]->slug.current, ""), "") + coalesce(array::join(intermediatePages[]->slug.current, ""), "") + page->slug.current, },}[!TIP] The
^prefix accesses the parent document’s fields from within an array projection.^.intermediatePagesresolves the parent’s intermediate pages, whileintermediatePages(without^) resolves the link’s own.
Best Practices
Section titled “Best Practices”- Strict Separation: Always differentiate between Pages (routes) and Blocks (content modules). Never inline complex schemas into a Page.
- Use
TypeNameConstants: Avoid string literals for schema types. Always import and use theTypeNameenum to ensure consistency and easier refactoring. - Modular & Reusable Blocks: Build sections as reusable Blocks. Even if a section seems unique to one page now, design it as a standalone Block referenced by that Page. This future-proofs the schema for multi-page use.
- Nested Routes via
intermediatePages: Never hardcode path prefixes (e.g.,/blog/my-post) in a slug. Use theintermediatePagesarray to dynamically construct the hierarchy. The slug should only contain the leaf segment (e.g.,my-post). - Centralised External Links: Don’t duplicate external URLs. Create a single
externalLinkdocument and reference it wherever needed.