Movable page builder components in Sanity CMS
Tags: webdev, sanity, jamstack, tutorial
Published: 2020-10-30 16:05:21

---
layout: post
title: "Movable page builder components in Sanity CMS"
description:  "Pseudo-WYSIWYG"
published: true
date: 2020-10-27 15:00
series: Playing With Gatsby
tags: webdev,sanity,jamstack,tutorial
cover_image: https://katiekodes.com/images/header-gatsby-movable.png
canonical_url: https://katiekodes.com/sanity-page-builder/
---

[Previously](https://dev.to/katiekodes/gatsby-react-wsyiwyg-cms-friendly-markdown-1npm), I designed a data structure in the "front matter" of Markdown files that was ready to be "dragged around" a content management system – and hence a web page – by a content author, providing a Squarespace-like experience.<br/><br/>I've spent enough time dinking around in Sanity CMS that I'm ready to share how I built the same data model inside of it.

![Animated GIF of the Sanity CMS content editing experience alongside a Gatsby-based site preview](https://katiekodes.com/images/screenshots/screenshot-gatsby-sanity-2020-07/75-live-drag-animated.gif)

## End goal HTML

As before, I'm trying to build a home page that looks like this:

![Screenshot of my home page before moving contents](https://katiekodes.com/images/screenshots/screenshot-gatsby-movable-2020-06/html-before-move.png)

However, I want it to be easy for an author to edit and re-order, and even add sections so that perhaps they look like this:

![Screenshot of my home page after moving contents](https://katiekodes.com/images/screenshots/screenshot-gatsby-movable-2020-06/html-after-move.png)

---

## Page data structure

### Conceptual

[As I doodled before, it might be helpful to imagine a crafting project where you write a bulleted list with pencil and paper, and then you cut it horizontally with scissors.](https://katiekodes.com/gatsby-multi-level/#design)

This is essentially the user interface that many [content management systems](https://dev.to/katiekodes/choosing-a-headless-cms-without-losing-your-head-3ef9) _(CMSes)_ are in the business of providing to web site content authors.

Designing content for web pages as a **list of sections**, each of which might just have simple details _(a.k.a. "properties" or "attributes")_, or which might contain further lists of subsections / items, is a choice often referred to as making a **page builder** experience for content authors.

Real sections, of course, wouldn't be silly prototypes like "pink" and "blue" and "task list."  They'd be ideas like "hero," "feature," "call to action," "FAQs," "preview cards," or "testimonials."

### Technical

Many CMSes, including Sanity _(the one I'll be configuring in this post)_, allow programmers to extract data represented in the [JSON punctuation standard](https://dev.to/katiekodes/intro-to-xml-and-json-6-json-30h3), which is basically a [computer-readable way of writing a bulleted list](https://dev.to/katiekodes/intro-to-xml-and-json-2-datas-shape-3iji).

Among other uses, the extracted CMS data can be fed into a tool like a [static site generator](https://dev.to/katiekodes/what-is-a-static-site-generator-350o) to build a [web site](https://dev.to/katiekodes/what-is-static-web-hosting-5gko).

[Here's an example of the JSON representation](https://katiekodes.com/gatsby-multi-level/#json) of my [bulleted list doodle](https://katiekodes.com/gatsby-multi-level/#design) representing the "sections" of the web page I want to make.

---

## Sanity configuration files

I'll presume that this isn't your first project with Sanity.  If it is, please go do the project at [Sanity Minimum Viable Build](https://dev.to/katiekodes/sanity-cms-minimum-viable-build-4j7) to learn your way around.

This post follows all the same steps that I took for the "minimum viable build," except that my [files in the `schemas` folder](https://katiekodes.com/sanity-minimum-viable-build/#edit-schema-files-on-the-hard-drive) are different.

I also put a few extra icons into a folder called `static` because Sanity stopped offering them built as a built-in feature of its "Sanity Studio" editing interface.

[A copy of my full Sanity configuration codebase is in this GitHub repository](https://github.com/kkgthb/web-site-sanity-01-movable).

While building the configuration files, they lived in a folder on my computer called `c:\example\sanity_movable\`.

### `/schemas/schema.js`

`schema.js` in the `schemas` folder is the master file defining my Sanity schema.

I'll use it to tell Sanity about all the other "schema definition" files I create _(which in this case, there will be five of)_.

The contents of mine are:

```javascript
// First, we must import the schema creator
import createSchema from 'part:@sanity/base/schema-creator'

// Then import schema types from any plugins that might expose them
import schemaTypes from 'all:part:@sanity/base/schema-type'

// We import object and document schemas
import landing from './documents/landing'
import sectionBlue from './objects/sectionBlue'
import sectionPink from './objects/sectionPink'
import sectionTaskList from './objects/sectionTaskList'
import task from './objects/task'

// Then we give our schema to the builder and provide the result to Sanity
export default createSchema({
  // We name our schema
  name: 'default',
  // Then proceed to concatenate our document type
  // to the ones provided by any plugins that are installed
  types: schemaTypes.concat([
    // The following are document types which will appear
    // in the studio.
    landing,
    // When added to this list, object types can be used as
    // { type: 'typename' } in other document schemas
    sectionBlue,
    sectionPink,
    sectionTaskList,
    task,
  ])
})
```

### `/schemas/documents/landing.js`

I promised `schema.js` a "`landing`" schema in the `documents` subfolder of the `schemas` folder, so I added a new file at that location called `landing.js`.

Its contents are as follows:

```javascript
import icon from '../../static/icons/arrows-alt'

export default {
  name: 'landing',
  title: 'Landing Page',
  type: 'document',
  icon,
  fields: [
    {
      name: 'template',
      title: 'Template',
      type: 'string',
      required: true,
    },
    {
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      required: true,
      description: 'If not happy with what the system generated, you can hand-edit it here'
    },
    {
      name: 'sections',
      title: 'Sections',
      type: 'array',
      of: [{type: 'sectionBlue'}, {type: 'sectionPink'}, {type: 'sectionTaskList'}],
    },
  ],
  preview: {
    select: {
      slug: 'slug',
      template: 'template',
    },
    prepare(selection) {
      const {slug, template} = selection
      return {
        title: `${slug.current} [${template}]`,
      }
    }
  }
}
```

I promised `schema.js` and `landing.js` 3 more schemas in the `objects` subfolder of the `schemas` folder:  "`sectionBlue`," "`sectionPink`," and "`sectionTaskList`".  Consequently, I added 3 more `.js`-typed files at those locations:

### `/schemas/documents/sectionBlue.js`

```javascript
import icon from '../../static/icons/bluetooth'

export default {
  name: 'sectionBlue',
  title: 'Section - Blue',
  type: 'object',
  icon,
  fields: [
    {
      name: 'mention',
      title: 'Mention',
      description: 'What text would you like in this "blue" section?',
      type: 'string'
    },
  ],
  preview: {
    select: {
      mention: 'mention',
    },
    prepare(selection) {
      const {mention} = selection
      return {
        title: mention,
      }
    }
  }
}
```

### `/schemas/documents/sectionPink.js`

```javascript
import icon from '../../static/icons/cake'

export default {
  name: 'sectionPink',
  title: 'Section - Pink',
  type: 'object',
  icon,
  fields: [
    {
      name: 'say',
      title: 'Say',
      description: 'What text would you like in this "pink" section?',
      type: 'string'
    },
  ],
  preview: {
    select: {
      say: 'say'
    },
    prepare(selection) {
      const {say} = selection
      return {
        title: say,
      }
    }
  }
}
```

### `/schemas/documents/sectionTaskList.js`

```javascript
import icon from '../../static/icons/tasks'

export default {
  name: 'sectionTaskList',
  title: 'Section - Task List',
  type: 'object',
  icon,
  fields: [
    {
      name: 'accomplishments',
      title: 'Tasks/accomplishments',
      type: 'array',
      of:[{type:'task'}],
    },
  ],
}
```

I promised `schema.js` and `sectionTaskList.js` 1 last schema in the `objects` subfolder of the `schemas` folder:  "`task`".  So I added 1 last file, `task.js`:

### `/schemas/documents/task.js`

```javascript
import icon from '../../static/icons/check-box-outline-blank'

export default {
  name: 'task',
  title: 'Task For Me',
  type: 'object',
  icon,
  fields: [
    {
      name: 'task',
      title: 'Task',
      type: 'string',
      required: true,
    },
    {
      name: 'done',
      title: 'Done?',
      type: 'boolean',
      required: true,
    },
    {
      name: 'how',
      title: 'How?',
      type: 'string',
      required: false,
    },
  ],
}
```

### `/static/icons/*.js`

Finally, I promised all of my individual schema files some "icons" to make the content editing experience pretty:  a bluetooth symbol for "blue sections," a birthday cake for "pink sections," etc.

I copied a few ".js" files out of older Sanity projects that had installed a NPM moduled called `react-icons` on my behalf and pasted them into `c:\example\sanity_movable\static\icons\`.

You can grab copies of them [from my repo on Github](https://github.com/kkgthb/web-site-sanity-01-movable/tree/main/static/icons).

Alternatively, for more icons, install the `react-icons` NPM package on your computer somewhere.  Then find and copy files you like better from within its folder structure.  [Here's a nice "React icon" preview tool for deciding which ones you like.](https://react-icons.github.io/react-icons/)

---

## Deploying Sanity Studio

I followed the same [steps to "deploy" the configuration files I'd built into Sanity's cloud](https://katiekodes.com/sanity-minimum-viable-build/#build-deploy-build-again-deploy-again) as I followed in the "minimum viable build" project.

This created a "Sanity Studio" web site for editing content in my Sanity project.

---

## Data loading

Instead of making our first Sanity "document" by hand, let's cheat and data-load one in, formatted in the JSON style.

Note that instead of letting Sanity generate an ID for the document, I'm going forcing its `_id` property to the phrase `sample_landings_home`.

This will help me data-load an edit if I realize later that I made some typos.  _(You can make a lot of typos at once with large-scale data migrations.  This was a lifesaver with the project I'm in the middle of, migrating Gigpress to Sanity.)_

```json
{
 "_id": "sample_landings_home",
 "_type": "landing",
 "template": "landing",
 "slug": {
  "_type": "slug",
  "current": "/"
 },
 "sections": [{
   "_type": "sectionPink",
   "say": "I did it!"
  }, {
   "_type": "sectionTaskList",
   "accomplishments": [{
     "_type": "task",
     "done": true,
     "how": "well",
     "task": "eat"
    }, {
     "_type": "task",
     "done": false,
     "how": "soundly",
     "task": "sleep"
    }, {
     "_type": "task",
     "done": true,
     "how": "high",
     "task": "jump"
    }, {
     "_type": "task",
     "done": true,
     "task": "write"
    }, {
     "_type": "task",
     "done": true,
     "how": "regularly",
     "task": "hydrate"
    }
   ]
  }, {
   "_type": "sectionBlue",
   "mention": "Hello World"
  }
 ]
}
```

When actually loading the data, I have to use a tool _(like the **JSMin** command of Sun Junwen's **JSTool** plugin for Notepad++)_ to concatenate this one JSON record into a single line of text -- no line breaks.

Therefore, what I've actually done is create a file at `c:\example\sanity_movable\data_samples\sample_landings.ndjson` that looks like this:

```json
{"_id":"sample_landings_home","_type":"landing","template":"landing","slug":"/","sections":[{"_type":"sectionPink","say":"I did it!"},{"_type":"sectionTaskList","accomplishments":[{"_type":"task","done":true,"how":"well","task":"eat"},{"_type":"task","done":false,"how":"soundly","task":"sleep"},{"_type":"task","done":true,"how":"high","task":"jump"},{"_type":"task","done":true,"task":"write"},{"_type":"task","done":true,"how":"regularly","task":"hydrate"}]},{"_type":"sectionBlue","mention":"Hello World"}]}
```

<div class="attention-note attention-note-gentle" markdown="block">

Trivia:  the filename ends in `.ndjson`, not `.json`, to remind me that it's not a pure JSON file.

It's a variant on JSON called "newline-delimited JSON," which is the data standard Sanity chose for import operations.

In the NDJSON punctuation standard, line breaks need to be avoided with one exception:

If the outermost level of the file is a list, you leave off its `[` and `]`, and you separate members of the list using line breaks instead of commas.

</div>

You can grab a [copy of this file from my Github repository](https://github.com/kkgthb/web-site-sanity-01-movable/blob/main/data_samples/sample_landings.ndjson) if you'd like to follow along.

From a command line prompt within the folder `c:\example\sanity_movable\` on my computer, I executed the following command:

```shell
sanity dataset import data_samples/sample_landings.ndjson YOUR_SANITY_DATASET_NAME_GOES_HERE --replace
```
---

## Exploring Sanity Studio

[Logging into "Sanity studio" as before](https://katiekodes.com/sanity-minimum-viable-build/#log-into-sanity-studio), I can see my "home page" as an author would see it:

![Screenshot of Sanity Studio after loading data](https://katiekodes.com/images/screenshots/screenshot-gatsby-sanity-2020-07/76-sanity-movable-data-loaded.png)

The sections are rearrangeable by clicking the six-dot icon at their far left, holding the cursor down, and dragging them up and down.  They're deleteable by clicking the trash can icon at their far right.

I can add a section, too:

![Screenshot](https://katiekodes.com/images/screenshots/screenshot-gatsby-sanity-2020-07/80-sanity-add-section.png)

If I click on the section with a birthday cake _(a "pink" section)_, I can edit the value of `say` from `I did it!` to something else.

![Screenshot](https://katiekodes.com/images/screenshots/screenshot-gatsby-sanity-2020-07/77-sanity-pink-edit.png)

If I click on the section that says "accomplishments", a panel pops up where I can rearrange, delete, or add tasks:

![Screenshot](https://katiekodes.com/images/screenshots/screenshot-gatsby-sanity-2020-07/78-sanity-tasks-edit.png)

And, clicking on the task that says "jump", I can edit its properties.

![Screenshot](https://katiekodes.com/images/screenshots/screenshot-gatsby-sanity-2020-07/79-sanity-task-edit.png)

---

## Planning for web site generation

### Sanity does not make web sites

<div class="attention-note" markdown="block">

None of what I've done in Sanity actually makes "pink" sections appear on a web page with a pink background.

That will be the job of a tool like a [static site generator](https://dev.to/katiekodes/what-is-a-static-site-generator-350o).

All I've done with Sanity is set up a nice way to organize my thoughts and to specify that I'd _**like**_ a "pink"-typed section with the phrase "I did it!" to be the first section on my home page.

</div>

For what it's worth, I made [Eleventy](https://www.11ty.dev/) build me a web site from this tutorial's Sanity project -- check it out at [https://movable-11ty-sanity.netlify.app/](https://movable-11ty-sanity.netlify.app/) and [peek at my 11ty configuration on GitHub](https://github.com/kkgthb/web-site-11ty-02-movable-sanity).

### Test APIs

Your static site generator will have to extract data from Sanity, so it's probably a good idea to [test API access to your Sanity data](https://katiekodes.com/sanity-minimum-viable-build/#test-basic-api-access) as before and, if you're planning to build a web site with Gatsby, to [set up GraphQL API access to Sanity](https://katiekodes.com/sanity-minimum-viable-build/#set-up-graphql-api-access).

### Webhooks

<div class="attention-note" markdown="block">

Sanity is happy to change your data and never tell the server running your static site generator to rebuild your site.

Be sure to configure your static site generation server to rebuild and redeploy your web site upon receiving a "webhook."

Configure Sanity to talk to that "webhook" every time you publish new content in Sanity.

</div>

### Live previews

You should also know that for any one "document" you store in Sanity, you _(or your static site generator)_ can do more than just extract the "published" version of it.

If a content author is currently editing the document but hasn't yet "published" their changes, you can extract details as seen in the latest "draft" or few.

This might not be particularly useful in all site generation tools, but with tools that inject some special Javascript into the pages of your web site _(like Gatsby)_, there are plugins and configuration settings that can [make non-production versions of your web site change _while_ your authors edit content](https://dev.to/katiekodes/heroku-live-previews-for-gatsby-sanity-sites-dl4).

With tools like that, if an author opened two browsers at once -- one for editing content in Sanity, and the other for watching the relevant page of the non-production web site -- they could see the impact of changes they're thinking about making to the data stored in Sanity, **live**, while they edit and before they click "**Publish**."

With more "truly static" site generators like Jekyll, Hugo, or 11ty, "live previews" of draft Sanity content might be a [little trickier to implement](https://medium.com/@richyinbeta/eleventy-strapi-headless-cms-with-automatic-page-creating-and-updating-86942f10c19a).  Hopefully one day I can get something working and show it off here.

---

## Further reading

* [A visual gallery of "web page section" ideas](https://tailwindui.com/components) from Tailwind UI, in case you're confused when I use design buzzwords like "hero" or "call to action"
* [How to use structured content for page building](https://www.sanity.io/guides/how-to-use-structured-content-for-page-building) by Sanity.io
* [Create a page builder in Sanity for Gatsby](https://www.simeongriggs.dev/create-a-page-builder-in-sanity-for-gatsby) by Simeon Griggs
* [A few more screenshots of a page builder in Sanity](https://www.jaminary.com/blog/the-sanity-io-experience/) by Daniel Kapper
* [And even more screenshots of a page builder in Sanity](https://www.sanity.io/blog/community-digest-28#a-powerful-page-builder-for-a-mezcal-brand-2d1d26618e7a) by Mike Wagz