Adding new blocks to the page builder

Creating new block types for the landing page builder

The landing page builder is a tool that allows content editors to create and edit landing pages in Sanity Studio from tailor-made blocks. It's built on top of Sanity's Array field (opens in a new tab).

When creating new blocks for the landing page builder, rely on tinloof-remix CLI to scaffold the necessary files for you by running npm run cli createBlock:

Running 'tinloof-remix createBlock' in the terminal (via npm run cli createBlock)
? What do you want to do? Create new block (createBlock)
? Block name (ex: "block.hero") Ideally prefixed with "block." block.newBlock
? Editor-friendly title (ex: "Initial section (Hero)") New Block (Example)
? Name of the front-end component (ex: "Hero") Can't  have spaces or special characters. NewBlock
✅ Block schema created at /app/sanity/schemas/blocks/blockNewBlock.ts
✅ Block schema included in the studio at /app/sanity/schemas/blocks/index.ts
✅ Typescript data definition added to /app/types/block.types.ts
✅ Component created at /app/components/blocks/NewBlock.tsx
 
 
💡 Go through each of the files above and follow the @TODOs specified in them

What the command does

Creates a new block schema schema

The schema defines how the blocks of this type will be edited in the Sanity Studio.

💡

If you're unfamiliar with Sanity schemas, check out the official documentation (opens in a new tab).

app/sanity/schemas/blocks/${newDocumentType}.ts
import { PackageIcon } from '@sanity/icons'
import { blockSchema } from '../blockSchema'
 
export const blockNewBlock = blockSchema({
  name: 'block.newBlock',
  title: 'New Block (Example)',
  type: 'object',
  // @TODO: replace with descriptive icon
  icon: PackageIcon,
  custom: {
    // @TODO: populate the explainer following other blocks' examples
    variants: undefined,
  },
  fields: [
    // @TODO: populate block's schema
    {
      name: 'title',
      title: 'Title',
      type: 'string',
      validation: (Rule) => Rule.required(),
    },
  ],
})

Adds the block to Sanity's schema

The new block type is added to the blocks array in app/sanity/schemas/blocks/index.ts, so it can be used in the Studio:

app/sanity/schemas/blocks/index.ts
// ...
import { blockNewBlock } from './blockNewBlock'
// %CLI/INJECT-IMPORT%
 
export default [
  // ...
  blockNewBlock,
  // %CLI/INJECT-VARIABLE%
]

Creates a React component for rendering the block in the front-end

app/components/blocks/NewBlock.tsx
import type { NewBlockBlockData } from '~/types'
import { PropsDisplayer } from '../PropsDisplayer'
 
const NewBlock = (props: NewBlockBlockData) => {
  // @TODO: validate block content before rendering
  if (!props.title) return null
 
  // @TODO: build the component, but don't forget to include `data-block={props._type}` in the root element
  return (
    <div {...props.rootHtmlAttributes}>
      <PropsDisplayer data={props} label="New Block (Example)" />
    </div>
  )
}
 
export default NewBlock

Sets-up this component as the block's renderer in blockComponents

app/components/blocks/blockComponents.ts
/**
 * Used by `BlocksRenderer` to render the apropriate component for each block in Sanity data.
 */
export const blockComponents: Record<string, React.ComponentType<any>> = {
  // %CLI/INJECT-VARIABLE%
  'block.newBlock': NewBlock,
  // ...
}

Create a GROQ query fragment to fetch the block's data in queries/block.queries.ts

app/queries/block.queries.ts
// @TODO: populate block's GROQ query fragment
/** @returns `NewBlockBlockData` in [block.types](../types/block.types.ts) */
const NEW_BLOCK = /* groq */ `
...,
`

Set-up this fragment as the way to fetch data for this block in BLOCKS_BODY_FRAGMENT

app/routing/block.queries.ts
export const BLOCKS_BODY_FRAGMENT = `
...,
_type == "block" => {
  ${RICH_PARAGRAPH_FRAGMENT}
},
// ...
_type == "block.newBlock" => {
  ${NEW_BLOCK}
},
`

Create Typescript definitions for the block's data

By writing TS definitions of your block's data, you make it easier to write and maintain the React components that tap into it. By default, the block component (above) will use the block's data type.

app/types/block.types.ts
// @TODO: populate block's data type definition
export interface NewBlockBlockData extends BlockInRenderer {
  title?: string
}

What's next after the creation

  • We leave a few @TODO comments in the files created. Follow them to complete the block's implementation.
  • Define the block's variants in its schema file with its title and assetUrl to guide editors when choosing blocks in the landing page builder