Sanity.io raises $9.3m Series A to redefine content management
Guide

How to use structured content for page building

You can use structured content to make landing page builders that will be useful beyond your next redesign. This guide shows you the basics of page building, and offers advice for dealing with presentation-related concerns.

Knut Melvær

Knut runs developer relations and support at Sanity.io.

Ronald Aveling

Ronald works with content for Sanity.io

Sanity can be used to manage things like landing page builders: they give editors enough control over page composition to get their message across using content modules, without breaking layout.

In this guide you’ll find suggestions for how to create content modules for page builders that should nicely translate to a component-based frontend framework or design system.

Protip

While page builders can be a very handy approach to content creation, it's worth asking yourself if a page builder is what you actually need. You can also arrive at compelling combinations of content and presentation by sourcing content from from various places using simple rules in your frontend.

Why you should model for meaning, not presentation

The goal with structured content is to make sure that your content stays resilient, adaptable, and easy to integrate wherever you need it. That’s why you should generally make content models that reflect what your content means rather than how it is presented. Because different presentation contexts (even within the same medium) come with different constraints: what makes sense on the web might not make sense in an app, and so on.

This guide makes no assumptions about presentation: no colors, floats, etc. While it might be tempting to add these, we think it best to leave those kinds of concerns to your code. They can add complexity not just to the implementation, but also to the things editors need to keep track of.

Think about your next redesign. Would you rather:

  • Start out with clean content that you can apply to a new channel or design?
  • Or, have to untangle your core content from a lot of presentation-related stuff that only made sense to your last design?

We find that modeling for meaning leads to better workflows and more durable content.

Protip

The rest of this guide involves a basic knowledge of schema building with Sanity.io. If you’ve never made one before, take a 3 minute detour to learn the basics of schema configuration, and/or keep our schema docs open as a reference .

Setup a Page Builder

The page builder is an array of custom types that can be reordered. It's the container for all your building blocks. With Sanity there are no pre-built blocks for you to use, but its fast and easy to make what you need.

A basic implementation of a Sanity page builder array. Items in the array can be reordered with drag and drop.

Let's add some blocks you’d expect to see on a typical landing page:

  • Hero: for your boldest statements
  • Text + illustration: when words aren’t enough
  • Call to action: big buttons and the like
  • Gallery: for eye candy 🍬
  • Form: newsletter signups, contact, etc
  • Video: for your latest promo clip or livestream recording

Now let's bring them to life in a bare bones document type called page:

// page.js

export default {
  
  // Setup a 'document' type to house the page builder field
  
  name: "page",
  type: "document",
  title: "Page",
  fields: [
    {
      name: 'pageBuilder',
      type: 'array',
      title: 'Page builder',
      of: [
        { type: 'hero' }, // hero.js (same applies for the other types)
        { type: 'textWithIllustration' },
        { type: 'callToAction' },
        { type: 'gallery' },
        { type: 'form' },
        { type: 'video' },
        // etc...
        ]
    }
  ]
}

Our pageBuilder field consists of a name, title, and the field type of array. Within the array we list all the types that we want it to build with. Now that we’ve declared our types, let's create the accompanying schema files.

Modeling the content blocks

Hero

Let's setup hero.js as an object type so that can be reused elsewhere in our schema if we need it. We’ll add fields for heading, tagline, and an image.

// hero.js

export default {
  name: "hero",
  type: "object",
  title: "Hero",
  fields: [
    {
      name: 'heading',
      type: 'string',
      title: 'Heading'
    },
    {
      name: 'tagline',
      type: 'string',
      title: 'Tagline'
    },
    {
      name: 'image',
      type: 'image',
      title: 'Image',
      options: {
        hotspot: true,
      },
      fields: [
        {
          name: 'alt',
          type: 'string',
          title: 'Alternative text'
        }
      ]
    }
  ]
}

In the image field we enabled the hotspot option for image art direction and added a simple string field for alternative text. Alt-text provides a text-based alternative to non-text content (like images) on web pages. Among other things, it helps vision impaired people understand the meaning of your images.

Protip

If providing meaningful alt-text descriptions is really important to you, make it mandatory by applying validation to this field.

Those fields will look like this in your Sanity Studio:

Call to action

The most simple form of a call-to-action is a URL and a string field which we can use for the link text. So, let's set that up in a new object:

// callToAction.js

export default {
  name: "callToAction",
  type: "object",
  title: "Call to Action",
  fields: [
    {
      name: 'linkText',
      type: 'string',
      title: 'Link Text'
    },
    {
      name: 'url',
      type: 'url',
      title: 'URL'
    }
  ]
}

Text with illustration

This object looks a lot like our hero except we’ve added a field called excerpt to store multiline text content.

// textWithIllustration.js

export default {
  name: "textWithIllustration",
  type: "object",
  title: "Text with Illustration",
  fields: [
    {
      name: 'heading',
      type: 'string',
      title: 'Heading'
    },
    {
      name: 'tagline',
      type: 'string',
      title: 'Tagline'
    },
    {
      name: 'excerpt',
      type: 'text',
      title: 'Excerpt'
    },
    {
      name: 'image',
      type: 'image',
      title: 'Image',
      options: {
        hotspot: true,
      },
      fields: [
        {
          name: 'alt',
          type: 'string',
          title: 'Alternative text'
        }
      ]
    }
  ]
}

Protip

If you need more than plain text you could use the block content type to include things like bold, italics, etc.

Image gallery

When you strip away all the presentation concerns, a gallery is just a sortable list of images. Normally the array type presents a vertically draggable list, but if you set it to grid it will do look like the example above. Here's how you do it:

// imageGallery.js

export default {
  name: "gallery",
  type: "object",
  title: "Gallery",
  fields: [
    {
      name: 'images',
      type: 'array',
      title: 'Images',
      of: [
        {
          name: 'image',
          type: 'image',
          title: 'Image',
          options: {
            hotspot: true,
          },
          fields: [
            {
              name: 'alt',
              type: 'string',
              title: 'Alternative text'
            }
          ]
        }
      ],
      options: {
        layout: 'grid'
      }
    }
  ]
}

Form

Forms come in many different shapes and sizes. In order to preserve the durability of our content structure beyond the next redesign all we really need to do is declare the kind of form we want to embed in our page builder array. Here's an example presenting 3 variations for newsletter, register, and contact form types:

// form.js

export default {
  name: "form",
  type: "object",
  title: "Form",
  fields: [
    {
      name: 'label',
      type: 'string',
      title: 'Label'
    },
    {
      name: 'heading',
      type: 'string',
      title: 'Heading'
    },
    {
      name: 'form',
      type: 'string',
      title: 'Form',
      description: 'Select form type',
      options: {
        list: ['newsletter', 'register', 'contact']
      }
    }
  ]
}

You can then use frontend code to provide varying presentations of your forms depending on the page context, and the type of form you selected.

Video

If you strip away presentation-based thinking, a video object can be modeled in the same way as our call to action object:

  • a URL field to define the resource location of your video file
  • a string field for the video's label
// video.js

export default {
  name: "video",
  type: "object",
  title: "Video",
  fields: [
    {
      name: 'videoLabel',
      type: 'string',
      title: 'Video Label'
    },
    {
      name: 'url',
      type: 'string',
      title: 'URL'
    },
  ]
}

Use your frontend for flexible presentations

Because we avoided embedding presentation concerns in our page builder we now have the flexibility to present that content in any number of ways in frontend code. Here are a few style variations applied to the same 3 fields in our hero content block. It's possible to present those fields in countless numbers of ways without compromising the meaning of our content.

What we have learned

We've learned the basics of modeling a page builder with Sanity.io. We've primed the pump with a few common builder modules that you can alter or extended to fulfil the unique needs of your project.

Along the way we made a case for keeping presentation-related concerns out of your content models. Content editing will be less complicated, and code maintenance will be easier, and your next redesign budget will thank you for it!

Page building demo

If you’d like to test out page building with Sanity give our Kitchen Sink starter project a try. It includes real-time content previews and a host of other goodies.

Kitchen Sink Studio
Our ever-evolving feature-rich demonstration studio.