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). This approach allows to render pages faster for end users, as well as fetch data from varied sources with little difficulty. Prismic is of course one of these possible data sources. 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.

Create a Prismic config 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 'prismic-javascript'

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,
  }
}

Fetching data to build pages

In order to query the data you require from your Prismic repository, Next provides the getInitialProps method. This asynchronous static method allows the application to perform a query, return the data object and store it as props for the component to use. This method can only be called from files located in the /pages folder, which will be used to generate all of the different pages that make up the website. This is of course quite different from how React's lifecycle methods work, but this approach ends up providing a more streamlined development experience.

Here's an example of a simple query for a single document of type 'homepage'

Copy
import React from 'react'
import { Client } from 'path/to/prismic-configuration'

export default class extends React.Component {
  static async getInitialProps(context) {
    const req = context.req
    const home = await Client(req).getSingle('homepage')
    return {
      doc: home
    }
  }

  // Render method ...
}

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

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

It is highly recommended to use Next's Link component for all links inside your web application, which will allow for client-side routing, giving a far faster and consistent navigation experience for users without requiring a full reload of all of the site's assets when switching pages.

In order to build <Link> components correctly, it's required to pass two attributes: as which will determine how the link's url is shown in the browser. href 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 hrefResolver. This helper function works in a similar way to linkResolver in that depending on the document's type it will generate a different string.

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

Copy
hrefResolver: function(doc) {
  if (doc.type === 'post') {
    return '/post/[uid]'
  }
  return '/'
}

With /pages/post.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

Copy
<Link as={linkResolver(post)} href={hrefResolver(post)}>
  <a>Click here to go read more!</a>
</Link>

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/linkResolver'
import { hrefResolver } from 'path/to/hrefResolver'

// ...

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

You may have noticed that we use the familiar linkResolver to render the path string for the as attribute. Since this is the aesthetic front-facing path that will be shown, it's important that it's presented in a proper way, so we use the link resolving function to render the expected url.

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, consistent navigation experience. This is why it's important 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.

The best approach to do this is setting up an HTML serializer helper 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 behavior so that they perform an imperative router push, effectively replicating the behavior 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 behavior.

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

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.

Handling dynamic routing

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

Deploying to Now as a serverless web application

Now makes it super easy to deploy your application! All you need to do is install Now and run the command:

Copy
npm install -g now-cli
now

Handling document previews

There are two steps you need to take into account when configuring previews in a Next project:

  1. Start with setting up the previews in your Prismic repository. Once you have filled the Site name and Domain for your application, add the following route for the Link Resolver field:

Copy
/api/preview

Your configuration will look something like this:

2. Then you'll need to prepare your web application to handle preview requests of both edited and not-yet-published documents. This requires a Next.js API route. So you can setup a route such as /pages/api/preview.js file that will take care of redirecting a preview request to the appropriate URL.

You can find below an example Preview route which will receive the query URL from the context object, get the proper URL with the help of the link resolver function, and redirect to it.

Copy
import { useEffect } from 'react';
import qs from 'qs';
import Prismic from 'prismic-javascript';
import { linkResolver } from './path/to/the/link-resolver.js';
 
const apiEndpoint = 'https://your-repo-name.cdn.prismic.io/api/v2';
const client = Prismic.client(apiEndpoint);
const Preview = ({ history, location }) => {
  useEffect(() => {
    const {token, documentId} = qs.parse(location.search.slice(1));
    if (!token) {
      return console.warn(`No token available, check your configuration`);
    }
    client.getPreviewResolver(token, documentId).resolve(linkResolver, '/').then(url => history.push(url));
    });
  return null;
};
 
export default Preview;

Deprecated previewSession method

Note that we are Processing the Preview Token with 'getPreviewResolver' method instead of the Deprecated 'previewSession' method. If you're using this last one, follow the links to learn how to change it.

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 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. Read about the In-Website Edit Button.

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:

Sample Multi-Page Site with Navigation in Next.js

Sample Blog with API-based CMS in Next.js

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