Prismic CMS
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 PageSlice 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
Create a NextJS app:
npx create-next-app next-prismicIn the root of your NextJS project, run the following command:
npx @slicemachine/initThis 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.jsonconfiguration 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.
Next install
@prismicio/nextnpm install @prismicio/nextThis package enables previewing functionality in NextJS. It also exposes some NextJS specific components like
PrismicNextImagefor rendering images.Configure Prismic
Create a file called
prismicio.jsat the root of your project and paste in the following code. This file will contain configurations for your projectprismicio.jsimport * 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
linkResolverfunction to match the routing of your project. We'll discuss this in more details hereAdd
PrismicProviderandPrismicPreviewPrismicProviderandPrismicPrevieware components that wrap your entire app in/pages/_app.js.PrismicProviderprovides Prismic utilities and settings.PrismicPreviewenables previewing. Add them to your app like this:pages/_app.jsimport 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
internalLinkComponentprop specifies what component to use for internal links. This code snippet passes a NextJS link component.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 calledslice-simulator.jsx, and paste in this code:pages/slice-simulator.jsximport { 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.jsonand add a property for the slice Simulator URL:sm.json{ "_latest": "...", "apiEndpoint": "...", "localSliceSimulatorURL": "http://localhost:3000/slice-simulator", "libraries": ["..."] }
Create your first slice
Start the slice machine
To start the development server:
npm run devTo use the slice machine, open a new terminal window and run:
npm run slicemachineThis will start the slice machine on
localhost:9999.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.jsfile, 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.
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.
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:
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:
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:
getByUIDgetByUID(type, uid) getByUID(type, uid, params)Queries a document from the Prismic repository with a UID and Custom Type.
typerefers to the API ID of the Custom Type.const document = await client.getByUID('page', 'about')getSinglegetSingle(type) getSingle(type, params)Queries a singleton document from the Prismic repository for a specific Custom Type.
typerefers to the API ID of the Custom Type. For example, here we are querying for the only document of the Custom Typehomepage.const document = await client.getSingle('homepage')getAllByTypegetAllByType(type) getAllByType(type, params)Queries all documents from the Prismic repository for a specific Custom Type.
typerefers 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.
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} />Link
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):
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.
Link Resolver
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 theSliceZone.slices: the list of all slice objects in theSliceZone.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
- Head to your Prismic repo and click on Settings > Previews
- Ignore the step about including the Prismic Toolbar, we've already set
this up with the
PrismicPreviewcomponent from@prismicio/next - Choose a name, a domain, and a preview route for your application
example:
- name:
localhost - domain:
http://localhost:3000 - preview route:
/api/preview
- name:
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:
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 handlerExit 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.
- Push your project to GitHub.
- Create a New Project in Vercel.
- Using the 'Import Git Repository' select the correct repo.
- 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
maingit 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: