Overview
@prismicio/next
is a new, yet to be written package designed to ease development when integrating Prismic with Next.js. It is both a library and a set of architecture recommendations for Next.js users.
It is targeted for users using Next.js directly, whether alongside other data sources, such as APIs and the file system, or exclusively with Prismic.
This package should make adoption of Prismic within Next.js a more pleasant experience, with most manual work automated through helpful functions.
This package and the surrounding concepts have the following goals:
- Easily fetch Prismic content
- Easily display Prismic content
- Easily integrate with Preview Mode
- Remain flexible and cognizant of users' own logic
"Easily" means easy to setup, but also easy to maintain and produce sustainable projects.
Background Information
Prismic's integration with Next.js is currently handled in two ways:
- A first-class Slice Machine integration composed of custom code generators, Slice Machine-specific functions, and React components.
- Starter projects with a suggested project organization to orchestrate fetching and displaying Prismic data.
This leaves a gap between the two approaches: developers who are familiar with Next.js with existing projects and developers who will be using Prismic outside of Slice Machine.
Developers in those groups are currently left to wire up solutions themselves. This can lead to error-prone code, additional maintenance burdens, and frustration.
Ultimately, this could drive users away from Prismic.
Proposal
Concepts from Slice Machine's integration with Next.js can be made more general. By making them less Slice Machine-specific, any user of Next.js—and, in some cases, React—can take advantage of the abstractions.
This is not a duplication of efforts. Instead, code generated and used by Slice Machine will come from @prismicio/next
.
The following concepts and abstractions can be provided by the new package. Some come from Slice Machine, while others are new and unique to this approach.
Embrace Next.js' concepts
Users should use Next.js and its features as the Next.js team intends. By doing so, all documentation, examples, and best practices for Next.js will apply to these users' projects. As new Next.js features are introduced, they can be integrated into users' projects by the users themselves.
It should follow, then, that @prismicio/next
works within the paradigms of Next.js. It should embrace the framework's core features, such as getStaticPaths
and getStaticProps
. It should provide solutions that are flexible and configurable, much like Next.js itself.
It should not try to hide Next.js features. It should not provide solutions that close off users from extending functionality.
Working to Next.js' strengths will also make the Prismic integration stronger.
Embrace Prismic core libraries
This proposal builds upon previous proposals and efforts to strengthen and simplify lower-level Prismic libraries. For example, by simplifying the core JavaScript client library, @prismicio/client
, we have made data fetching in Next.js simpler as well.
These efforts should be embraced and used directly within Next.js.
For context, the opposite of this approach would involve hiding lower-level functionality behind single-task black boxes. This leads to technical debt as users can no longer extend that single task without updates to the library with fragmentation of usage.
Consider the following example for setting up getStaticPaths
and getStaticProps
:
import { useGetStaticProps, useGetStaticPaths } from "next-slicezone/hooks";
import { Client } from "../prismic-configuration";
export const getStaticProps = useGetStaticProps({
client: Client(),
uid: ({ params }) => params.uid,
});
export const getStaticPaths = useGetStaticPaths({
client: Client(),
type: "page",
fallback: process.env.NODE_ENV === "development",
formatPath: ({ uid }) => ({ params: { uid } }),
});
And the more flexible, albeit more verbose, counterpart:
import * as prismicNext from "@prismicio/next";
import * as prismicH from "@prismicio/helpers";
import { createClient, linkResolver } from "../prismic";
export const getStaticProps = async (context) => {
const client = createClient();
prismicNext.enableClientServerSupportFromContext(client, context);
const uid = context.params.uid;
const page = await client.getByUID("page", uid);
return {
props: { page },
};
};
export const getStaticPaths = async () => {
const client = createClient();
const pages = await client.getAllByType("page");
const paths = pages.map((page) => ({
params: {
uid: page.uid,
pagePath: prismicH.documentAsLink(page, linkResolver).split("/"),
},
}));
return { paths, fallback: true };
};
The first example showcases single-task black boxes to output data Next.js expects. It allows a user to configure its output through different string and function options. Someone familiar with Next.js, but unfamiliar with next-slicezone/hooks
, may not understand what is happening or what the options affect.
The second example performs the same tasks as the first, but takes a more functional approach. Rather than providing single-task black boxes, @prismicio/next
provides small functions to be used throughout a user's own logic. In this example, that logic includes using @prismicio/client
directly to fetch documents, allowing the user to perform that work as they see fit. Someone familiar with Next.js, but unfamiliar with @prismicio/next
, could read the code, have a general understanding of its effects, and know how to extend it.
Although neither example shows how TypeScript would be integrated, it should be clear how it can be added to the second example.
Provide small helper functions
The following helper functions abstract common tasks while allowing a user to use them as they see fit.
buildPreviewDataFromReq
- Builds a Next.js Preview Mode data object from an API req
object. This is used to automate some of the work needed to support previews.
getPreviewRefFromContext
- Extracts a preview ref from a Next.js context object for use with Preview Mode. enableClientServerSupportFromContext
uses this function internally.
enableClientServerSupportFromContext
- Enables server support for a @prismicio/client
from a Next.js context object. This can be used in any Next.js API that provides the request context, such as getStaticProps
, getServerProps
, and getInitialProps
.
How could it be implemented
The library would consist of the above small helper functions. As such, implementations should be straightforward.
// enableClientServerSupportFromContext.ts
import * as nextT from "next/types";
import * as prismic from "@prismicio/client";
import { getPreviewRefFromContext } from "./getPreviewRefFromContext";
export const enableClientServerSupportFromContext = (
client: prismic.Client,
context: nextT.GetStaticPropsContext
): void => {
const ref = getPreviewRefFromContext(context);
if (ref) {
client.queryContentFromRef(ref);
}
};
// getPreviewRefFromContext.ts
import * as nextT from "next/types";
import { PrismicNextPreviewData } from "../types";
const isPrismicNextPreviewData = (
previewData: nextT.PreviewData
): previewData is PrismicNextPreviewData =>
typeof previewData === "object" && "ref" in previewData;
export const getPreviewRefFromContext = (
context: nextT.GetStaticPropsContext
): string | undefined => {
if (isPrismicNextPreviewData(context.previewData)) {
return context.previewData.ref;
}
};
// buildPreviewDataFromReq.ts
import * as nextT from "next/types";
import { PrismicNextPreviewData } from "../types";
export const buildPreviewDataFromReq = (
req: nextT.NextApiRequest
): PrismicNextPreviewData => {
if (Array.isArray(req.query.token)) {
return {
ref: req.query.token[0]
};
}
return {
ref: req.query.token
};
};
From these functions, users can setup Preview Mode easily:
// pages/api/preview.ts
import * as nextT from "next/types";
import * as prismicNext from "@prismicio/next";
import { buildPreviewDataFromReq } from "../../lib/buildPreviewDataFromReq";
import { createClient, linkResolver } from "../../prismic";
export default async function handler(
req: nextT.NextApiRequest,
res: nextT.NextApiResponse
): Promise<void> {
const client = createClient();
client.enableServerSupportFromReq(req);
const previewData = prismicNext.buildPreviewDataFromReq(req);
const resolvedURL = await client.resolvePreviewURL({ linkResolver });
res.setPreviewData(previewData);
res.redirect(resolvedURL);
}
// pages/api/exit-preview.ts
import * as nextT from "next/types";
export default function handler(
_req: nextT.NextApiRequest,
res: nextT.NextApiResponse
): void {
res.clearPreviewData();
}
The Preview Mode entry handler (pages/api/preview.ts
) is recommended to be implemented within a project rather than via a helper method (e.g. createPrismicPreviewModeAPIHandler
). This simplifies @prismicio/client
instance management and allows a user to customize the handler.
If you have a good suggestion on how this could be more automated, please share!
How to provide feedback
Everything described above is open for feedback.
Do you think @prismicio/next
should do more? Be more opinionated? Or less?
If you have comments, please share them here. ✌️