← Back to Notes

Prismic CMS

Hamed Bahram /
19 min read--- views

Prismic is a headless CMS and a website builder. With a traditional CMS, you can manage a website's content. With Prismic, you can also manage website components. Here are a few key features that will help you understand how Prismic works:

  • Slices: building blocks for websites.
  • Slice machine: a local development tool to build slices.
  • Editor: the Prismic app where writers create content.

Slices

slices are like components but for content. They are sections of a page you can reuse as many times as you need, each time with new content. slices bring the component-based workflow to the content editor.

You render your slices with Prismic's SliceZone component. The SliceZone component requires two props:

  • slices: an array of slices returned from the API.
  • components: a collection of React components for each slice type.
const Page = ({ document }) => (
  <SliceZone
    slices={document.data.body}
    components={{
      text: TextSlice,
      image: ImageSlice
    }}
  />
)
 
export default Page

Slice machine

slice machine is a local development tool to build slices. You build slices by adding fields. A field stores a piece of data, like an image, a number, or text. For each field the slice machine will create a code snippet to template the React component.

In addition to slices, you use slice machine to create Custom Types. If a slice represents a section of a webpage, a Custom Type represents the webpage itself.

As you develop your slice, you can simulate what the slice will look like in slice machine. When you're happy with your slices and Custom Types, you can push them to the Prismic editor for your content team to use in the Editor.

Editor

The editor is the app your content team uses to write content for the website. Prismic hosts the editor for you.

While creating content, writers can see all available slices and how each slice looks like. They can create, update, and delete documents, just like files on your computer. They can also preview their content on the website before publishing it.

Set up Prismic

  1. Create a NextJS app:

    npx create-next-app next-prismic
  2. In the root of your NextJS project, run the following command:

    npx @slicemachine/init

    This command will do the following:

    • Create a new Prismic repository or let you specify an existing one.
    • Add a slicemachine script to package.json.
    • Create an sm.json configuration file containing your API endpoint and the location of your slice library.
    • Detect your framework (Next.js).
    • Install the following dependencies:
      • @prismicio/client: enables fetching data from the Prismic API.
      • @prismicio/react: renders Prismic data as React components.
      • slice-machine-ui: provides a tool for building slices.
      • @prismicio/slice-simulator-react: provides an environment to simulate slices with mock data as you build them.
  3. Next install @prismicio/next

    npm install @prismicio/next

    This package enables previewing functionality in NextJS. It also exposes some NextJS specific components like PrismicNextImage for rendering images.

  4. Configure Prismic

    Create a file called prismicio.js at the root of your project and paste in the following code. This file will contain configurations for your project

    prismicio.js
    import * as prismic from '@prismicio/client'
    import * as prismicH from '@prismicio/helpers'
    import * as prismicNext from '@prismicio/next'
    import sm from './sm.json'
     
    export const repositoryName = prismic.getRepositoryName(sm.apiEndpoint)
     
    // Update the Link Resolver to match your project's route structure
    export function linkResolver(doc) {
      switch (doc.type) {
        case 'homepage':
          return '/'
        case 'page':
          return `/${doc.uid}`
        default:
          return null
      }
    }
     
    export const createClient = (config = {}) => {
      const client = prismic.createClient(sm.apiEndpoint, config)
     
      prismicNext.enableAutoPreviews({
        client,
        previewData: config.previewData,
        req: config.req
      })
     
      return client
    }

    Customize the linkResolver function to match the routing of your project. We'll discuss this in more details here

  5. Add PrismicProvider and PrismicPreview

    PrismicProvider and PrismicPreview are components that wrap your entire app in /pages/_app.js. PrismicProvider provides Prismic utilities and settings. PrismicPreview enables previewing. Add them to your app like this:

    pages/_app.js
    import Link from 'next/link'
    import { PrismicProvider } from '@prismicio/react'
    import { PrismicPreview } from '@prismicio/next'
    import { linkResolver, repositoryName } from '../prismicio'
     
    export default function App({ Component, pageProps }) {
      return (
        <PrismicProvider
          linkResolver={linkResolver}
          internalLinkComponent={({ href, ...props }) => (
            <Link href={href}>
              <a {...props} />
            </Link>
          )}
        >
          <PrismicPreview repositoryName={repositoryName}>
            <Component {...pageProps} />
          </PrismicPreview>
        </PrismicProvider>
      )
    }

    You now have Prismic utilities available throughout your project, and your project is set up to handle previews. The internalLinkComponent prop specifies what component to use for internal links. This code snippet passes a NextJS link component.

  6. Create slice simulator page

    The slice simulator allows you to preview what your slices will look like using mock data. The slice simulator also allows slice machine to take screenshots of your slices and send it to the Editor app, making it easier for your content team when choosing slices.

    This functionality is generated by a page component located at /slice-simulator. In your pages directory, create a file called slice-simulator.jsx, and paste in this code:

    pages/slice-simulator.jsx
    import { SliceSimulator } from '@prismicio/slice-simulator-react'
    import { SliceZone } from '@prismicio/react'
     
    import { components } from '../slices'
    import state from '../.slicemachine/libraries-state.json'
     
    const SliceSimulatorPage = () => {
      return (
        <SliceSimulator
          sliceZone={({ slices }) => (
            <SliceZone slices={slices} components={components} />
          )}
          state={state}
        />
      )
    }
     
    export default SliceSimulatorPage
     
    // Only include this page in development
    export const getStaticProps = async () => {
      if (process.env.NODE_ENV === 'production') {
        return { notFound: true }
      } else {
        return { props: {} }
      }
    }

    Then, open sm.json and add a property for the slice Simulator URL:

    sm.json
    {
      "_latest": "...",
      "apiEndpoint": "...",
      "localSliceSimulatorURL": "http://localhost:3000/slice-simulator",
      "libraries": ["..."]
    }

Create your first slice

  1. Start the slice machine

    To start the development server:

    npm run dev

    To use the slice machine, open a new terminal window and run:

    npm run slicemachine

    This will start the slice machine on localhost:9999.

  2. Create a new slice

    To create your first slice, click on the "slices" tab in the menu, give your slice a name, like "TextBlock", "ImageSlider", or "Button", and save it to your "slices" library.

    A slice is a collection of fields. You build slices by adding fields. A field stores a piece of data, like an image, a number, or text. Add fields to your slice and click "save model to filesystem".

    Now you have a directory at the root of your project called slices. Inside, you'll find a directory for the slice you just created. That directory contains an index.js file, which is the React component responsible for rendering that specific slice.

    For each field the slice machine will create a code snippet to help you template the React component. Click the button "Show Code Snippets" and paste the code into your component to start templating your slice.

  3. Custom Types

    In addition to slices, you can use the slice machine to create custom types. If a slice represents a section of a webpage, a custom type represents the webpage itself. It might be something like "page", "homepage", "article" or "product".

    Custom types are composed of a static zone and a slice zone:

    • Static zone has fields that only appear once in a document, like a "title".
    • Slice zone contains your slices, which are repeatable sections of a page.
  4. Push Slices and Custom Types to Prismic

    You have now created custom types and slices in your local project. To make sure your content editors can start using these to create new documents in the Prismic editor, you must push them to Prismic. On each model, click "Push to Prismic". When slice machine is done syncing, the button will be disabled.

Create your first document

Now that you've created custom types and Slice models, you can start creating content. Go to prismic.io/dashboard and click on your repository. In the "Documents" tab of your repository, click the green icon to create your first document. When you're done editing, click "Save" and "Publish". Your first document is now live.

Fetch Data

Now that you have created your first document, let's learn how to perform queries against the Prismic API to retrieve your content in NextJS.

Here's a basic example of a homepage document fetched from the Prismic API inside getStaticProps:

pages/index.jsx
import { SliceZone } from '@prismicio/react'
import { createClient } from '../prismicio'
import { components } from '../slices'
 
const Page = ({ page, navigation, settings }) => {
  return <SliceZone slices={page.data.slices} components={components} />
}
 
export default Page
 
export async function getStaticProps({ previewData }) {
  const client = createClient({ previewData })
  const page = await client.getSingle('homepage')
  return {
    props: {
      page
    }
  }
}

Here is an example of a dynamic page which includes getStaticPaths:

pages/[uid].jsx
import * as prismicH from '@prismicio/helpers'
import { SliceZone } from '@prismicio/react'
import { createClient, linkResolver } from '../prismicio'
import { components } from '@/slices/index'
 
const Page = ({ page, navigation, settings }) => {
  return <SliceZone slices={page.data.slices} components={components} />
}
 
export default Page
 
export async function getStaticProps({ params, previewData }) {
  const client = createClient({ previewData })
  const page = await client.getByUID('page', params.uid)
  return {
    props: {
      page
    }
  }
}
 
export async function getStaticPaths() {
  const client = createClient()
  const pages = await client.getAllByType('page')
  return {
    paths: pages.map(page => prismicH.asLink(page, linkResolver)),
    fallback: false
  }
}

Perform a query

Queries are performed using a client created by the createClient function exported from prismicio.js.

Query helpers

Here are the most commonly-used query helper methods:

  • getByUID

    getByUID(type, uid)
    getByUID(type, uid, params)

    Queries a document from the Prismic repository with a UID and Custom Type. type refers to the API ID of the Custom Type.

    const document = await client.getByUID('page', 'about')
  • getSingle

    getSingle(type)
    getSingle(type, params)

    Queries a singleton document from the Prismic repository for a specific Custom Type. type refers to the API ID of the Custom Type. For example, here we are querying for the only document of the Custom Type homepage.

    const document = await client.getSingle('homepage')
  • getAllByType

    getAllByType(type)
    getAllByType(type, params)

    Queries all documents from the Prismic repository for a specific Custom Type. type refers to the API ID of the Custom Type. This method may perform multiple network requests. It returns an array containing all matching documents from the repository.

    const documents = await client.getAllByType('article')

Template your content

To get started, let's look at the structure of the API response. The document object contains the data for an individual document. Here is an example of a document object return from the API:

{
  "uid": "about",
  // Metadata for this result
  ...
  "data": {
    "example_date": "2020-12-10",
    "example_color": "#c7ab5d",
    "example_key_text": "Example Key Text Value",
    "slices": [
      {
        "slice_type": "image_gallery",
        "slice_label": null,
        "items": [
          {...}
        ],
        "primary": {
          "example_key_text": "Some text..."
        }
      },
      {
        "slice_type": "content_block",
        "slice_label": null,
        "items": [
          {...}
        ],
        "primary": {
          "example_key_text": "Some more text..."
        }
      }
    ]
  }
}

Each document object consists of some metadata, and the fields and slices defined in the corresponding custom type. Let's look at the fields first.

Document Fields

Fields can be either a simple primitive value or an object. The simple fields can be injected directly into your app since their value is either a string, a number, or a boolean. Here are the simple fields:

  • Color
  • Key Text
  • Number
  • Select
  • Boolean
  • Date
  • Timestamp

Simple field types can be used directly in your application:

<span>{document.data.number}</span>

Here are the fields with object or array as content:

  • GeoPoint
  • Embed
  • Images
  • Rich Text and Titles
  • Link
  • Content Relationship
  • Group

Let's review the most commonly used fields here:

Images

The Image field returns an object with data about the image, including a URL for your image (hosted on Prismic's servers) and alt text.

"example_image": {
  "dimensions": {
    "width": 1920,
    "height": 1302
  },
  "alt": "Pink flowers on a tree",
  "copyright": null,
  "url": "https://images.prismic.io/..."
}

You can template an image using PrismicNextImage component from @prismicio/next. It renders an optimized image using next/image and Prismic's built-in imgix integration.

import { PrismicNextImage } from '@prismicio/next'
 
function ImageSlice({ slice }) {
  return (
    <section>
      <PrismicNextImage
        field={slice.primary.image}
        imgixParams={{ sat: -100 }}
      />
    </section>
  )
}

Images can be transformed using imgix integration and the imgixParams prop. This allows you to resize, crop, recolor, and more. See the imgix API for more details.

Rich Text and Titles

Rich Text and Titles are delivered in an array that contains information about the text structure. Here's an example of the API response of the Rich Text field (Title fields follow the same format).

"example_rich_text": [
  {
    "type": "paragraph",
    "text": "Example Rich Text Value",
    "spans": [
      {
        "start": 8,
        "end": 17,
        "type ": "strong"
      }
    ]
  }
]

To render Rich Text and Title fields as React components, use the PrismicRichText component from @prismicio/react.

<PrismicRichText field={document.data.title} />

This component returns a React fragment with no wrapping element around the content. If you need a wrapper, add a component around PrismicRichText.

<article>
  <PrismicRichText field={document.data.myRichTextField} />
</article>

By default, HTML elements are rendered for each block of content. For example, an h1 HTML element will be rendered for a heading1 block.

To modify the default output, provide a list of component to the components prop. The list of components maps an element type to its React component. Here is an example:

<PrismicRichText
  field={document.data.rich_text}
  components={{
    heading1: ({ children }) => <Heading>{children}</Heading>,
    paragraph: ({ children }) => <p className="text-base">{children}</p>
  }}
/>

Components can also be provided in a centralized location using the PrismicProvider React context provider in _app.js. All PrismicRichText components will use the shared component mapping automatically.

pages/_app.js
export default function App({ Component, pageProps }) {
  return (
    <PrismicProvider
      richTextComponents={{
        heading1: ({ children }) => <Heading>{children}</Heading>,
        paragraph: ({ children }) => (
          <p className="paragraph">{children}</p>
        )
      }}
    >
      <Component {...pageProps} />
    </PrismicProvider>
  )
}

If a different component needs to be rendered for a specific instance of PrismicRichText, a components prop can be provided to override the shared component mapping.

Plain Text

The PrismicText component from @prismicio/react will convert and output the text in the Rich Text or Title field as a string.

import { PrismicText } from '@prismicio/react'
...
<PrismicText field={document.data.title} />

The Link field allows you to link to an external webpage, an internal Prismic document, or an item in your media library (like a PDF). The Link field is used to create a link (i.e. an a element).

The Link field response will be an object and the content depends on the type of link you add (external webpage, internal document, or media). The link_type property of the response object will reflect this by one of the three possible values: Document, Web, Media.

Here is an example of a link to another document with the UID of another-document and the type of page:

"example_link": {
  "id": "X9C65hEAAEFIAuLo",
  "type": "page",
  "tags": [],
  "slug": "another-document",
  "lang": "en-us",
  "uid": "another-document",
  "link_type": "Document",
  "isBroken": false
}

Use the PrismicLink component from @prismicio/react to render links:

import { PrismicLink } from '@prismicio/react'
...
<PrismicLink field={document.data.example_link}>Example Link</PrismicLink>

PrismicLink automatically resolves your external links, but you need to use the Link component from NextJS to create links between internal pages.

To configure PrismicLink to use next/link for internal links, you'll need to wrap your app with PrismicProvider and configure the internalLinkComponent prop (as shown in prior setup steps):

pages/_app.js
import Link from 'next/link'
import { PrismicProvider } from '@prismicio/react'
import { PrismicPreview } from '@prismicio/next'
import { linkResolver, repositoryName } from '../prismicio'
 
export default function App({ Component, pageProps }) {
  return (
    <PrismicProvider
      linkResolver={linkResolver}
      internalLinkComponent={({ href, children, ...props }) => (
        <Link href={href}>
          <a {...props}>{children}</a>
        </Link>
      )}
    >
      <PrismicPreview repositoryName={repositoryName}>
        <Component {...pageProps} />
      </PrismicPreview>
    </PrismicProvider>
  )
}

Now PrismicLink will use the next/link component for internal links. But there is still one more thing: Prismic does not know the routing structure of your app to correctly resolve internal links.

You can use a link resolver function to tell PrismicLink how to resolve internal links. A Link Resolver function takes a document from Prismic as an argument and returns the URL path for that document.

In prismicio.js, customize the linkResolver function to match the routing of your project. For each Custom Type that corresponds to a page in your app, add a case that returns the route for that page.

export function linkResolver(doc) {
  switch (doc.type) {
    case 'homepage':
      return '/'
    case 'page':
      return `/${doc.uid}`
    case 'blog':
      return `/blog/${doc.uid}`
    default:
      return null
  }
}

After that, PrismicLink will automatically render the correct route.

Content Relationship

The Content Relationship field allows you to link specifically to an internal Prismic document constrained by Custom Type. Content Relationship fields are used to pull data from another document.

To pull in content from another document, you must fetch that content in your API query using the graphQuery or fetchLinks option.

  • First, reference the ID of the Custom Type in your Content Relationship field.
  • Then, the ID of the field that you want to retrieve.

If you have a Custom Type called blog that includes a Content Relationship field linked to a document of type author and you want to retrieve the author_name field, your query will be like so:

export const getStaticProps = async ({ previewData }) => {
  const client = createClient({ previewData })
 
  const document = await client.getByUID('blog', 'my-blog-post', {
    fetchLinks: 'author.author_name'
  })
 
  return {
    props: { document }
  }
}

The linked content will appear in a data object nested in the Content Relationship field in the response object.

Now that we've covered most of our document's fields, let's look at the slices section.

Document Slices

Slices are just collections of fields you define in the slice machine and template as a React component to reuse in your documents.

To render slices, use the SliceZone component by passing an array of slices from the API and a list of components for each type of slice.

import { SliceZone } from '@prismicio/react'
import { components } from '../slices'
 
const Page = ({ page }) => {
  return <SliceZone slices={page.data.slices} components={components} />
}

Each slice component will receive the following props:

  • slice: the slice object being rendered.
  • index: the index of the slice within the SliceZone.
  • slices: the list of all slice objects in the SliceZone.
  • context: arbitrary data passed to the slice zone's context prop.

A simple slice component could look like this:

import { PrismicRichText } from '@prismicio/react'
 
function TextSlice({ slice }) {
  return (
    <section>
      <PrismicRichText field={slice.primary.text} />
    </section>
  )
}

Preview Drafts

Prismic Previews allow you to view draft content on your live website without publishing them publicly. You can set up many preview environments to preview your content in different contexts, such as production and development.

Setup previews in Prismic

  1. Head to your Prismic repo and click on Settings > Previews
  2. Ignore the step about including the Prismic Toolbar, we've already set this up with the PrismicPreview component from @prismicio/next
  3. Choose a name, a domain, and a preview route for your application example:
    • name: localhost
    • domain: http://localhost:3000
    • preview route: /api/preview

Pass preview data to queries

Wherever you query Prismic data in your pages directory using getStaticProps or getServerSideProps, make sure you pass previewData to the createClient function.

import { createClient } from '../prismiscio'
 
export async function getStaticProps({ previewData }) {
  const client = createClient({ previewData })
  const home = await client.getByUID('page', 'home')
 
  return {
    props: {
      home
    }
  }
}

Add the preview route

Create a preview.js file inside the pages/api folder, and paste the following code:

pages/api/preview.js
import { setPreviewData, redirectToPreviewURL } from '@prismicio/next'
import { linkResolver, createClient } from '../../prismicio'
 
const handler = async (req, res) => {
  const client = createClient({ req })
  setPreviewData({ req, res })
  await redirectToPreviewURL({ req, res, client, linkResolver })
}
 
export default handler

Exit preview route

Create a exit-preview.js file inside the pages/api folder, and paste the following code:

import { exitPreview } from '@prismicio/next'
 
export default async function exit(req, res) {
  exitPreview({ res, req })
}

That's it, now you can preview your changes from Prismic editor before publishing it.

Deploy

You can easily deploy your app to Vercel, and create a continuous deployments using GitHub.

  1. Push your project to GitHub.
  2. Create a New Project in Vercel.
  3. Using the 'Import Git Repository' select the correct repo.
  4. Click Deploy and you're done.

Add a webhook

If you're using Static Site Generation (SSG) to generate pages in your project, you need to rebuild your site anytime there are changes in Prismic.

You can setup a webhook to automate this process when there are changes in your repo.

In your Vercel project:

  • Go to Settings > Git > Deploy Hooks
  • Create a hook for your main git branch
  • Copy the URL

In your Prismic repository:

  • Visit Settings > Webhooks
  • Create a new webhook by choosing a name and pasting in the webhook URL you just copied from Vercel. You can leave the "Secret" empty.

Now, whenever you change your content in Prismic, the changes will be reflected on your site.

Summary

That's it folks. You now have a fully functional NextJS app that's powered by Prismic CMS. This enables the content creators to create pages and documents independent of the developers.

Resources

Here are some of the resources that inspired this note:

Documentation