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.

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 Prismic from 'prismic-javascript'
import { RichText } from 'prismic-reactjs'
import { apiEndpoint } from '../prismic-configuration'

export default class extends React.Component {
  static async getInitialProps(context) {
    const req = context.req
    const home = await this.getHomePage(req)
    return {
      doc: home
    }
  }

  static async getHomePage (req) {
    const API = await Prismic.getApi(apiEndpoint, { req })
    return await API.getSingle('homepage')
  }

  // 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 { RichText, Date, Link } from 'prismic-reactjs'
import { default as NextLink } from 'next/link'
import { linkResolver, hrefResolver } from '../prismic-configuration'

// ...

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, hrefResolver } from 'prismic-configuration'
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

In order to prepare your web application to handle previews of both edited and not-yet-published documents, you'll have to prepare for handling preview requests. 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 { Client } from 'prismic-configuration'
import { linkResolver } from 'path/to/linkResolver'

export default async (req, res) => {
  const token = req.query.token;
  if (token) {
    try {
      const url = await Client(req).previewSession(token, linkResolver, '/')
      res.writeHead(302, { Location: url })
      res.end()
    } catch {
      res.status(400).send('Something went wrong with the previewSession request');
    }
  } else {
    res.status(400).send('Missing token from preview request');
  }
}

Additionally, you should add a confirmation check before rendering any document. This way the page will not fail when trying to render drafts of documents.

Copy
render () {
  if (!this.props.post) {
    return <div>404 Error!</div> 
  } else {
    return (
      <div>
        <h1>{RichText.asText(this.props.post.data.title)}</h1>
        <h3>{RichText.asText(this.props.post.data.description)}</h3>
      </div>
    )
  }
}

The toolbar script must be present in all of your rendered pages. You can find the code to include in Prismic settings page, in the Previews section. Creating a layout component and using it to render all of your pages would be the best idea to make sure that the script is included.

Working example

We provide an example project using Next.js to develop a straightforward personal blog web app. You can review the code for working examples of all the topics touched upon in this article:

https://user-guides.prismic.io/examples/next-js-samples/sample-blog-with-api-based-cms-in-nextjs

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