next-slicezone

Learn how to work with Slice Machine's next-slicezone to render slices, query documents, and build routes.

Introduction

The next-slicezone package exports two lifecycle hooks (useGetStaticProps, useGetStaticPaths) that allow you to fetch associated data of documents on Prismic and get the static paths of a given route. It also exports a component (SliceZone) that matches your Next.js components with Prismic slices.

Together they render front-end components for each of your Prismic documents. You’ll see how to use these later, but first, you should learn how to install and configure the package.

Dependencies installation

Install the next-slicezone package via a package manager:

npm install next-slicezone

You should also install @prismicio/client which will be used to query the Prismic API:

npm install @prismicio/client@^5

Project files configuration

You will need to configure a few project files to use this package.

The sm.json file

Create a file at the root of your project called sm.json. This file is a configuration file for the slice-machine-ui and your <SliceZone> component which will use this file to find the location of your slice libraries.

Accepted AttributesUsage
apiEndpoint

You can specify your Prismic API endpoint here and import it throughout your project.

libraries

Used to specify a slices directory (@/my-slices), nested slice directories (@/slices/new-library) or library packages (like react essential-slices).

_latest

Used to specify the version of slice-machine-ui.

storybook

Used to specify the port in which to open a storybook integration.

Example file

{
  "apiEndpoint": "https://your-repo-name.cdn.prismic.io/api/v2",
  "libraries": [
    "@/slices",
    "@/my-slices/new-library",
    "essential-slices"
  ],
  "_latest": "0.1.0",
  "storybook": "http://localhost:8888"
}

createResolver and SliceResolver functions

The createResolver function will generate an sm-resolver.js file in the root of your project every time you change your slices structure (rename, add, delete a slice, add a library, etc.). This file contains the SliceResolver function, which automatically matches slices and their content coming from Prismic to the local components in your project. You will need to import it when building a page and pass it to the <SliceZone> component.

To do this, create a 〜/pages/_document.js file and add the createResolver method to its getInitialProps method:

import Document, { Html, Head, Main, NextScript } from 'next/document'
import { createResolver } from 'next-slicezone/resolver' // import the function here

export default class extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx)
    /* In development, generate an sm-resolver.js file
    that will map slices to components */
    if (process.env.NODE_ENV === 'development') {
      await createResolver()
    }
    return { ...initialProps }
  }

  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </div>
      </Html>
    )
  }
}

Example sm-resolver.js file

This file contains the JavaScript SliceResolver function which you need to import when building a page and pass it to the <SliceZone> component. You can see how to do this further down the article where we discuss the <SliceZone>.

import { Fragment } from "react";
import * as Slices from "./slices";

const __allSlices = { ...Slices };

const NotFound = ({ sliceName, slice, i }) => {
  console.error(
    `[sm-resolver] component "${sliceName}" not found at index ${i}.`,
  );
  console.warn(`slice data: ${slice}`);
  return process.env.NODE_ENV !== "production" ? (
    <div
      style={{
        height: "30vh",
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        flexDirection: "column",
        textAlign: "center",
        background: "#FAFAFA",
      }}
    >
      <h2>Slice "{sliceName}" not found.</h2>
      <p style={{ maxWidth: "320px", fontSize: "16px" }}>
        Check that you registered this component in your
        slices library!
      </p>
    </div>
  ) : (
    <Fragment />
  );
};

export default function SliceResolver({
  sliceName,
  ...rest
}) {
  return __allSlices[sliceName]
    ? __allSlices[sliceName]
    : () => <NotFound sliceName={sliceName} {...rest} />;
}

Data Fetching

Next.js offers two data fetching functions:

getStaticProps()

which gets the data for statically-generated pages

getStaticPaths()

which determines all of the routes for statically-generated dynamic pages

next-slicezone extends the functionality of those hooks with useGetStaticPaths() and useGetStaticProps(),

useGetStaticProps

useGetStaticProps can be used on every page using the SliceZone. It’s responsible for:

  • fetching content from Prismic
  • returning a pre-written Next getStaticProps

apiParams

useGetStaticProps takes an apiParams object or function as an argument.

The object argument allows you to specify static props to send to your query such as a language code.

The function argument gives you access to the params, previewData, and preview objects to help build your queries and send dynamic data such as document UIDs from the URL bar to the query. The returned data can also be used when building static paths which you can see in the useGetStaticPaths example below.

client

function (required)

Receives a Prismic client. Example:

Prismic.client(apiEndpoint)

apiParams

object

Object or function passed to client apiOptions. The function gives you access to the params, previewData, and preview objects. Static Object Example:

apiParams: {
  lang: 'fr-fr',
},

queryType

string

Defines whether the custom type is a singleton or repeatable. Defaults to ‘repeat’. Example:

'single'

type

string

Custom type to query. Defaults to ‘page’. Example:

'blog_post'

slicesKey

string

Key of slices array in API response (doc.data.[slicesKey]) Defaults to slices. Example:

'MySliceZone'

getStaticPropsParams

extra params used by getStaticProps, like notFound or redirect. Example:

{ revalidate: true }
import Prismic from "@prismicio/client";
import { useGetStaticProps } from "next-slicezone/hooks";

export const getStaticProps = useGetStaticProps({
  client: Prismic.client(
    "https://your-repo-name.cdn.prismic.io/api/v2",
  ),
  queryType: "repeat",
  type: "page",
  slicesKey: "MySliceZone",
  getStaticPropsParams: {
    notFound: false,
  },
  apiParams({ params }) {
    // params are passed by getStaticPaths
    return {
      lang: params.lang,
      uid: params.uid,
    };
  },
});

useGetStaticPaths

useGetStaticPaths can be used in dynamic pages to define the static paths to be generated. It returns a function to be passed directly to Next.js’s getStaticPaths function. It will fetch content from Prismic using dynamic properties.

useGetStaticPaths takes a params object as an argument.

client

Same as useGetStaticProps

type

Same as useGetStaticProps

apiParams

Same as useGetStaticProps

formatPath

function (required)

Function to format the path object that must be returned from getStaticPaths in Next.js. Pass null to skip. Defaults to (doc) => null. Example:

formatPath: (prismicDocument) => {
  return {
    params: {
      uid: prismicDocument.uid
    }
  }
}
import Prismic from "@prismicio/client";
import {
  useGetStaticProps,
  useGetStaticPaths,
} from "next-slicezone/hooks";

// Fetch content from prismic
export const getStaticProps = useGetStaticProps({
  client: Prismic.client(
    "https://your-repo-name.cdn.prismic.io/api/v2",
  ),
  type: "page",
  apiParams({ params }) {
    // params are passed by getStaticPaths
    return {
      lang: params.lang,
      uid: params.uid,
    };
  },
});

// fetch all docs of type `page` and pass params accordingly
export const getStaticPaths = useGetStaticPaths({
  client: Prismic.client(
    "https://your-repo-name.cdn.prismic.io/api/v2",
  ),
  type: "page",
  formatPath: (prismicDocument) => {
    return {
      params: {
        lang: prismicDocument.lang,
        uid: prismicDocument.uid,
      },
    };
  },
});

<SliceZone />

Once slices have been fetched, they must be matched with Next.js components and rendered. The SliceZone accepts data from the API (fetched by useGetStaticProps) as a prop. It also accepts a resolver, which defines how to match Prismic slices with Next.js components.

slices array (required)

The data components fetched from Prismic

resolver function (required)

Resolves calls to components from the SliceZone

sliceProps object || function

This allows you to pass props, like for Theme UI, to have more control of a globally available component on the page level.

Example:

Here’s an example of a [uid].js file with a slice zone component that is receiving slices, custom props for Theme UI and we’ve imported the sm-resolver.js to pass to the SliceZone component.

import Prismic from "@prismicio/client";
import SliceZone from "next-slicezone";
import {
  useGetStaticProps,
  useGetStaticPaths,
} from "next-slicezone/hooks";
import resolver from "../sm-resolver";

const Page = ({ slices, data }) => (
  <SliceZone
    slices={slices}
    resolver={resolver}
    sliceProps={({ slice, sliceName, i }) => ({
      theme: i % 1 ? "light" : "dark",
      alignLeft: data.keyTextTitle?.length > 35,
    })}
  />
);

export const getStaticProps = useGetStaticProps({
  client: Prismic.client(
    "https://your-repo-name.cdn.prismic.io/api/v2",
  ),
  apiParams({ params }) {
    return {
      uid: params.uid,
    };
  },
});

export const getStaticPaths = useGetStaticPaths({
  client: Prismic.client(
    "https://your-repo-name.cdn.prismic.io/api/v2",
  ),
  formatPath: (prismicDocument) => {
    return {
      params: {
        uid: prismicDocument.uid,
      },
    };
  },
});

export default Page;

Examples

Take a look at the different use cases where you can make use of the SliceZone and the Lifecycle hooks.

Query by single type

In this example, we query a Singleton page of type “homepage” in an index.js file:

import Prismic from "@prismicio/client";
import SliceZone from "next-slicezone";
import { useGetStaticProps } from "next-slicezone/hooks";
import resolver from "../sm-resolver"; // import from project root

const Page = ({ slices }) => (
  <SliceZone resolver={resolver} slices={slices} />
);

export const getStaticProps = useGetStaticProps({
  client: Prismic.client(
    "https://your-repo-name.cdn.prismic.io/api/v2",
  ),
  type: "homepage",
  queryType: "single",
});

export default Page;

Query by repeatable type

In this example, we dynamically query the Repeatable pages of type “post” using the uid of each document in a [uid].js file:

import Prismic from "@prismicio/client";
import SliceZone from "next-slicezone";
import {
  useGetStaticProps,
  useGetStaticPaths,
} from "next-slicezone/hooks";
import resolver from "../sm-resolver"; // import from project root

const Post = ({ slices }) => (
  <SliceZone resolver={resolver} slices={slices} />
);

export const getStaticProps = useGetStaticProps({
  client: Prismic.client(
    "https://your-repo-name.cdn.prismic.io/api/v2",
  ),
  type: "post",
  apiParams({ params }) {
    return {
      uid: params.uid,
    };
  },
});

export const getStaticPaths = useGetStaticPaths({
  client: Prismic.client(
    "https://your-repo-name.cdn.prismic.io/api/v2",
  ),
  formatPath: (prismicDocument) => {
    return {
      params: {
        uid: prismicDocument.uid,
      },
    };
  },
});

export default Post;

Query dynamically by language

In this example, we query a default repeatable type “page” using the uid and lang based in a 〜/pages/[lang]/[uid].js file:

import Prismic from "@prismicio/client";
import SliceZone from "next-slicezone";
import {
  useGetStaticProps,
  useGetStaticPaths,
} from "next-slicezone/hooks";
import resolver from "../sm-resolver"; // import from project root

const Page = ({ slices }) => (
  <SliceZone resolver={resolver} slices={slices} />
);

export const getStaticProps = useGetStaticProps({
  client: Prismic.client(
    "https://your-repo-name.cdn.prismic.io/api/v2",
  ),
  apiParams({ params }) {
    return {
      lang: params.lang,
      uid: params.uid,
    };
  },
});

export const getStaticPaths = useGetStaticPaths({
  client: Prismic.client(
    "https://your-repo-name.cdn.prismic.io/api/v2",
  ),
  formatPath: (prismicDocument) => {
    return {
      params: {
        uid: prismicDocument.uid,
        lang: prismicDocument.lang,
      },
    };
  },
});

export default Page;