Using Prismic with Next.js


This article discusses how to use Prismic with Next.js, an exciting React framework that allows for Server Side Rendering (SSR). This is the biggest difference it presents from React, since most web apps built with React work with Client Side Rendering (CSR). We will go through considerations to keep in mind when working with Prismic and Next.js, as well as using the powerful (and trendy!) serverless mode to produce efficient and lighting-fast web applications.

Note that these instructions require that you're using Next.js version 9 or above.

Open a Next.js project

If you don't yet have an existing Next.js project, check out Next's docs to get started with a brand new project.
Or, if you prefer, run one of our example projects to see everything working right away.

Get your project up and running and then come back!

Create a prismic-configuration file

The first step you need to do is create a prismic-configuration.js file at the root of your project. Note that you will need to update this file with your Prismic endpoint and optional access token:

Copy
import Prismic from '@prismicio/client'

export const apiEndpoint = 'https://your-repo-name.cdn.prismic.io/api/v2'
export const accessToken = ''

// Client method to query documents from the Prismic repo
export const Client = (req = null) => (
  Prismic.client(apiEndpoint, createClientOptions(req, accessToken))
)

const createClientOptions = (req = null, prismicAccessToken = null) => {
  const reqOption = req ? { req } : {}
  const accessTokenOption = prismicAccessToken ? { accessToken: prismicAccessToken } : {}
  return {
    ...reqOption,
    ...accessTokenOption,
  }
}

Query data and build pages

In order to query the data from your Prismic repository you'll be using two key async functions:

  • getStaticProps: It pre-renders your pages page at build time using the props returned by it.
  • getStaticPaths: If a page has dynamic routes and uses getStaticProps it needs to define a list of paths that have to be rendered to HTML at build time.

Refer to Next.js official Data fetching documentation to learn more

Here's an example of a simple page component where we dynamically generate pages by querying for all the documents of the type 'post', in the /pages/[uid].js file. One route and one page will be generated per document in the repository!

Copy
//pages/[uid].js
import React from 'react'
import { Client } from '/path-to-your-prismic-configuration'

const Post = () => ({ posts }) {
  return (
     // Render your page component...
  )
}


export async function getStaticProps({params}) {
  const client = Client();
  const doc = (await client.getByUID('page', params.uid)

  return {
    props: {
      doc,
    },
  }
}

export async function getStaticPaths() {
     return {
      // You can run a separate query here to get dynamic parameters from your documents.
       paths: [
          { params: { uid: '1' } },
          { params: { uid: '2' } }
       ],
      fallback: //true or false
    }
}

export default Post

Rendering the data you have fetched is straightforward and unchanged from React. You just have to access the props defined by getStaticProps.

Copy
render (doc) {
 const post = doc.data
  return (
    <div>
      <h1>{RichText.asText(post.title)}</h1>
      <h3>{RichText.asText(post.description)}</h3>
    </div>
  );
}

Dynamic routing

You can refer to the Next.js documentation for how to setup your dynamic routes.

Links for navigation

Client-side transitions between routes can be enabled via the Link component exported by next/link. This is where you set the path or URL that will determine how the link's url is shown in the browser.

The <Link> component just needs one required parameter which is href. This usually takes the form of page/[uid], but given that this will change depending on the document type you're trying to link to, it is a good idea to simplify things with an linkResolver.

Here's an example that will be defined as a function in prismic-configuration.js

Copy
linkResolver: function(doc) {
  if (doc.type === 'post') {
    return `/post/${doc.uid}`;
  }
  return '/'
}

With /pages/[uid].js being the file that will take care of rendering pages for documents of type 'post'.

So building a Link to an internal document in your application would look like the following component example:

NOTE: Take care of not confusing this Link component with Prismic's own Link component which is part of the prismic-reactjs package. For this reason, always use an alias when importing it

Copy
import { default as NextLink } from 'next/link'
import { linkResolver } from 'path-to-your-linkResolver'

// ...

render () {
  const link = this.props.doc.data.link
  return (
    <NextLink as={linkResolver(link)}>
      <a>Click here</a>
    </NextLink>
  )
}

HTML Serializer

Content writers may add links to internal documents as part of Rich Text fields. By default, this will be simple <a href> elements which does not make for an optimal navigation experience.

The best approach to provide an automated method for these user-created links to be handled with client-side routing as well, without the need of reloading the web application is by setting up an HTML serializer function that will modify the <a href> elements when rendering Rich Text. However, instead of outright replacing them for <Link> elements, it's better to modify their onClick behaviour so that they perform an imperative router push, effectively replicating the behaviour of Next's Link component. The reason for this roundabout approach is that replacing <a> directly for <Link> elements won't trigger the same associated behaviour.

You can use this function in Next.js just as with any other framework, pass it as part of the render function for a given rich text field. Below you can find an example HTML Serializer function that will handle both regular links and links added to images for internal documents.

Copy
import React from 'react'
import { RichText } from 'prismic-reactjs'
import { linkResolver } from 'path/to/linkResolver'
import { hrefResolver } from 'path/to/hrefResolver'
import Router from 'next/router'

const Elements = RichText.Elements
const onClickHandler = function (href, as) {
  // Handler that will do routing imperatively on internal links
  return e => {
    e.preventDefault()
    Router.push(href, as)
  }
}

const propsWithUniqueKey = function (props, key) {
  return Object.assign(props || {}, { key })
}

export const htmlSerializer = function (type, element, content, children, key) {
  var props = {}
  switch (type) {
    case Elements.hyperlink: // Link
      if (element.data.link_type === 'Document') {
        // Only for internal links add the new onClick that will imperatively route to the appropiate page
        props = Object.assign({
          onClick: onClickHandler(hrefResolver(element.data), linkResolver(element.data)),
          href: linkResolver(element.data)
        })
        return React.createElement('a', propsWithUniqueKey(props, key), children)
      } else {
        // Default link handling
        const targetAttr = element.data.target ? { target: element.data.target } : {}
        const relAttr = element.data.target ? { rel: 'noopener' } : {}
        props = Object.assign({
          href: element.data.url || linkResolver(element.data)
        }, targetAttr, relAttr)
        return React.createElement('a', propsWithUniqueKey(props, key), children)
      }

    case Elements.image: // Image
      var props = {}
      var internal = false

      if (element.linkTo && element.linkTo.link_type === 'Document') {
        // Exclusively for internal links, build the object that can be used for router push
        internal = true
        props = Object.assign({
          onClick: onClickHandler(hrefResolver(element.linkTo), linkResolver(element.linkTo)),
          href: linkResolver(element.linkTo)
        })
      }
      // Handle images just like regular HTML Serializer
      const linkUrl = element.linkTo ? element.linkTo.url || linkResolver(element.linkTo) : null
      const linkTarget = (element.linkTo && element.linkTo.target) ? { target: element.linkTo.target } : {}
      const linkRel = linkTarget.target ? { rel: 'noopener' } : {}
      const img = React.createElement('img', { src: element.url, alt: element.alt || '' })
      return React.createElement(
        'p',
        propsWithUniqueKey({ className: [element.label || '', 'block-img'].join(' ') }, key),
        linkUrl ? React.createElement('a',
          // if it's an internal link, replace the onClick
          internal ? propsWithUniqueKey(props, key) : Object.assign({ href: linkUrl },
          linkTarget, linkRel), img) : img
      )

    default:
      return null
  }
}

export default htmlSerializer

Deploy with Vercel

Learn how to deploy a statically-generated Next site for free on Vercel in three easy steps:

  1. Sign up to Github and push your project to a new repository
  2. Sign up to Vercel and Import your project
  3. Then just click Deploy, wait a bit for the project to build and then your site will be live!
  • The revalidate option: Incremental Static Regeneration allows you to update existing pages by re-rendering them in the background as the changes load. You'll just need to use the revalidate option in the getStaticProps function and pass the seconds after which a page re-generation can occur.

Also read: Next.js deployment documentation.

Handling previews

There are a few steps you need to perform when configuring previews requests of both published documents and documents that have not yet been published.

Configure the preview in Prismic

Start with setting up the previews in your Prismic repository. Once you have filled this fields:

  • Site name
  • Domain for your application

Add the following route for the:

  • Link Resolver:
Copy
/api/preview

Your configuration will look something like this:

Configure the preview in your app

Then set the Next Preview mode configuration. Create an API directory: pages/api. Inside of this, add two new files:

  • preview.js
  • exit-preview.js (Required file to exit a preview session correctly)

The preview.js file

The preview configuration uses the Next.js setPreviewData method which sets some cookies on the browser, turns on the preview mode, and redirects the request to the appropriate URL.

Copy
import Prismic from "@prismicio/client";
import { linkResolver } from 'path/to/linkResolver'

const apiEndpoint = "https://your-repo-name.cdn.prismic.io/api/v2";
const accessToken = "";

// Client method to query from the Prismic repo
const Client = (req = null) =>
  Prismic.client(apiEndpoint, createClientOptions(req, accessToken));

const createClientOptions = (req = null, prismicAccessToken = null) => {
  const reqOption = req ? { req } : {};
  const accessTokenOption = prismicAccessToken ? { accessToken: prismicAccessToken } : {};
  return {
    ...reqOption,
    ...accessTokenOption,
  };
};

const Preview = async (req, res) => {
  const { token: ref, documentId } = req.query;
  const redirectUrl = await Client(req)
    .getPreviewResolver(ref, documentId)
    .resolve(linkResolver, "/");

  if (!redirectUrl) {
    return res.status(401).json({ message: "Invalid token" });
  }

  res.setPreviewData({ ref });
  res.writeHead(302, { Location: `${redirectUrl}`  })
  res.end();
};

export default Preview;

getPreviewResolver

Note that in the code above we are processing the Preview Token with the getPreviewResolver method instead of the deprecated previewSession method.

Make sure that you are using the latest @prismicio/client v.4.0.0 kit or above.

The Link Resolver function

This requires the use of a Link Resolver function so that the preview endpoint knows where to redirect to. You can learn more about link resolving by checking out the Link Resolving page.

You either need to define the Link Resolver in the Preview component or import it.

The exit-preview.js file

Whenever you have an active preview session and you want to exit you'll need to manually add /api/exit-preview to the URL in the browser. See the examples below:

  • In development mode: http://localhost:xxxx/api/exit-preview
  • In production: http://your-site-name.com/api/exit-preview
Copy
export default async (_, res) => {
  res.clearPreviewData();

  res.writeHead(307, { Location: "/" });
  res.end();
};

Pass the preview ref

You'll need to Pass the preview ref to all of your pages now that the preview cookie provided by the setPreviewData method is available in your project, you just need to read it inside your pages by retrieving the previewData cookie and getting the ref with this line of code: const { ref } = previewData.

Take this example of a simple index.js file:

Copy
import React from 'react'
import { Client } from '../route-to-client-config'

const HomePage = ({ doc }) => {
  if (doc) {
    return (
      <div>
        {RichText.asText(doc.data.title)}
      </div>
    )
  }
  return null
}

export async function getStaticProps({ preview = null, previewData = {} }) {

  const { ref } = previewData

  const client = Client()
  const doc = await client.getSingle('homepage', ref ? { ref } : null) || {}

  return {
    props: {
      doc,
      preview
    }
  }
}

export default HomePage

Exit the preview correctly

At this point, your previews will work. As mentioned earlier, the last thing to note is that whenever you have an active preview session and you want to exit you'll need to manually add /api/exit-preview to the URL in the browser.

The toolbar script must be present in all of your rendered pages. You can find the code to include it in your repository settings page, in the Previews section.

Working examples

We provide example projects using Next.js to develop a straightforward personal Blog web app or a Multi-Page Site with Navigation. You can review the code for working examples of all the topics touched upon in these articles:

Instructions on how to install and get this simple projects off the ground can be found in detail, as well as the finished demos.