Skip to content

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.

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.

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.

A key architectural principle in this project is Reusability by Default.

The Pattern:

  1. Atomicity: Never define a complex UI section directly inside a Page schema.
  2. Encapsulation: Create a separate Block schema for that section (e.g., heroSectionBlock, faqSectionBlock).
  3. Reference: In the Page schema, reference the Block using the array of references 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.

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.

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:

  1. Select the chronic-pain article as the page.
  2. Add the conditions page to the intermediatePages array.

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 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 slug field is of type string and includes a uniqueness validator, ensuring you don’t duplicate external link entries.

Usage: To reuse an external link, reference it in any link block, just like you would with an internal page.

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. ^.intermediatePages resolves the parent’s intermediate pages, while intermediatePages (without ^) resolves the link’s own.

  1. Strict Separation: Always differentiate between Pages (routes) and Blocks (content modules). Never inline complex schemas into a Page.
  2. Use TypeName Constants: Avoid string literals for schema types. Always import and use the TypeName enum to ensure consistency and easier refactoring.
  3. 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.
  4. Nested Routes via intermediatePages: Never hardcode path prefixes (e.g., /blog/my-post) in a slug. Use the intermediatePages array to dynamically construct the hierarchy. The slug should only contain the leaf segment (e.g., my-post).
  5. Centralised External Links: Don’t duplicate external URLs. Create a single externalLink document and reference it wherever needed.