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 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
Create a NextJS app:
npx create-next-app next-prismic
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.
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.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 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
linkResolver
function to match the routing of your project. We'll discuss this in more details hereAdd
PrismicProvider
andPrismicPreview
PrismicProvider
andPrismicPreview
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.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
internalLinkComponent
prop 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.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
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
.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.
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:
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 Typehomepage
.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.
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
PrismicPreview
component 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 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.
- 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
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: