Use Next.js with Prismic
Last updated
Overview
Prismic has a first-party Next.js integration that supports all of Prismic’s features:
- Model content with slices and page types using Slice Machine.
- Fetch and display content using SDKs with generated TypeScript types.
- Preview draft content with live previews and full-website previews.
Here’s what a Prismic page looks like in Next.js with the Data Cache:
import { SliceZone } from "@prismicio/react";
import { createClient } from "@/prismicio";
import { components } from "@/slices";
export default async function Page({ params }: PageProps<"/[uid]">) {
// 1. Fetch a page from Prismic
const { uid } = await params;
const client = createClient();
const page = await client.getByUID("page", uid);
// 2. Display the page's slices
return <SliceZone slices={page.data.slices} components={components} />;
}Next.js websites use @prismicio/client, @prismicio/react, and @prismicio/next.
Set up a Next.js website
Prismic can be added to new or existing Next.js websites. Follow these steps to set up a Next.js project.
Create a Prismic repository
From the Prismic dashboard, create a Prismic repository. Select Next.js.

When asked to select a starter, select Connect your own web app.
If you prefer starting with a template, select Minimal starter or Full website demo instead.
Set up a Next.js project
Follow the setup instructions shown in your Prismic repository.

The instructions guide you through creating a new Next.js project (if needed) and adding Prismic using
@slicemachine/init.The
@slicemachine/initcommand installs Prismic’s packages and configures your project with a Prismic client, content previews, and revalidation.Set up content previews
Content writers can preview content on your website before publishing.
@slicemachine/initperforms most of the setup for you. However, there are a few steps to complete manually.See the preview setup instructions
Start building with Prismic
Your Next.js website is now ready for Prismic. Next, learn how to create pages and slices.
Create pages
Website pages are managed in Prismic using page types. Page types are created in Slice Machine.
Learn how to create page types
Write page components
Each page type needs a Next.js page component. With the App Router’s file-system based routing, you create a page file at each page’s path.
The example below shows a page component for a Page page type.
import type { Metadata } from "next";
import { SliceZone } from "@prismicio/react";
import { createClient } from "@/prismicio";
import { components } from "@/slices";
export default async function Page({ params }: PageProps<"/[uid]">) {
const { uid } = await params;
const client = createClient();
const page = await client.getByUID("page", uid);
return <SliceZone slices={page.data.slices} components={components} />;
}
export async function generateMetadata({
params,
}: PageProps<"/[uid]">): Promise<Metadata> {
const { uid } = await params;
const client = createClient();
const page = await client.getByUID("page", uid);
return {
title: page.data.meta_title,
description: page.data.meta_description,
openGraph: {
images: [{ url: page.data.meta_image.url ?? "" }],
},
};
}
export async function generateStaticParams() {
const client = createClient();
const pages = await client.getAllByType("page");
return pages.map((page) => ({ uid: page.uid }));
}Define routes
Prismic needs to know your website’s routes to fill in link URLs. Configure the routes constant in your project’s prismicio.ts with a set of route resolvers.
This example includes routes for a homepage, general pages, and a blog.
// `type` is the API ID of a page type.
// `path` determines the URL for a page of that type.
const routes: Route[] = [
{ type: "homepage", path: "/" },
{ type: "page", path: "/:uid" },
{ type: "blog_post", path: "/blog/:uid" },
];Your route resolvers should match your Next.js file-system-based routes. Here are some commonly used routes:
| Route resolver path | Next.js file-system route |
|---|---|
/ | app/page.tsx |
/:uid | app/[uid]/page.tsx |
/blog/:uid | app/blog/[uid]/page.tsx |
/:grandparent/:parent/:uid | app/[...path]/page.tsx |
Learn more about route resolvers
Create slices
Page content is written using reusable page sections called slices. Slices are created in Slice Machine.
Learn how to create slices
Write React components
Slice Machine generates a bootstrapped React component when a slice is created. You can find the generated files in src/slices or whichever slice library was selected.
Once your slice is configured with fields, edit the slice’s index.tsx file to display the slice’s content.
Here is an example of a Call to Action slice. It displays a rich text field and a link field.
import type { Content } from "@prismicio/client";
import { PrismicRichText, type SliceComponentProps } from "@prismicio/react";
import { PrismicNextLink } from "@prismicio/next";
type CallToActionProps = SliceComponentProps<Content.CallToActionSlice>;
export default function CallToAction({ slice }: CallToActionProps) {
return (
<section className="flex flex-col gap-4 p-8">
<PrismicRichText field={slice.primary.text} />
<PrismicNextLink field={slice.primary.link} className="button" />
</section>
);
}Learn how to display content
Fetch content
Use @prismicio/client and its methods to fetch page content.
Set up a Prismic client
Create a prismicio.ts file to centralize your Prismic client setup. This file contains route resolvers and default Prismic client settings.
The fetch cache is configured using the client’s fetchOptions.
import {
createClient as baseCreateClient,
type ClientConfig,
type Route,
} from "@prismicio/client";
import { enableAutoPreviews } from "@prismicio/next";
import sm from "../slicemachine.config.json";
export const repositoryName = sm.repositoryName;
// TODO: Update with your route resolvers.
const routes: Route[] = [
{ type: "homepage", path: "/" },
{ type: "page", path: "/:uid" },
{ type: "blog_post", path: "/blog/:uid" },
];
export function createClient(config: ClientConfig = {}) {
const client = baseCreateClient(repositoryName, {
routes,
fetchOptions: {
next: { tags: ["prismic"] },
cache: "force-cache",
},
...config,
});
enableAutoPreviews({ client });
return client;
}The above fetchOptions includes the following Next.js cache settings:
next.tags: Tags all Prismic API calls withprismic.cache: API calls are cached forever until theprismictag is revalidated.
Fetch content in pages and slices
Import createClient() from prismicio.ts and create a client. Use the client to fetch content.
This example page fetches content for a /[uid] dynamic route.
import type { Metadata } from "next";
import { SliceZone } from "@prismicio/react";
import { createClient } from "@/prismicio";
import { components } from "@/slices";
export default async function Page({ params }: PageProps<"/[uid]">) {
const { uid } = await params;
// Caching is configured via fetchOptions in prismicio.ts.
const client = createClient();
const page = await client.getByUID("page", uid);
return <SliceZone slices={page.data.slices} components={components} />;
}You can fetch content in slices the same way. This example fetches a Settings page.
import { Content } from "@prismicio/client";
import { SliceComponentProps } from "@prismicio/react";
import { createClient } from "@/prismicio";
type ContactFormProps = SliceComponentProps<Content.ContactFormSlice>;
export default async function ContactForm({ slice }: ContactFormProps) {
const client = createClient();
const settings = await client.getSingle("settings");
// ...
}Learn more about fetching content
Fetch new content
When pages are cached with fetchOptions, you may see stale content as you work on your website.
You can fetch new content when working locally by hard-refreshing the page in your browser. In Chrome, you can hard-refresh using Shift+⌘R.
In production, you will need to set up content revalidation to fetch new content.
Learn how to set up content revalidation
Secure with an access token
Published content is public by default. You can require a private access token to secure the API.
Learn more about content visibility
Open your API & Security settings
Navigate to your Prismic repository and go to Settings > API & Security.

The API & Security settings in a Prismic repository.
Change the API access
Under the Repository security section, change the API access dropdown to “Private API.”
Click Change the API visibility.
Generate an access token
Under the Generate an Access Token section, fill out the form.
Field Value Name A name to identify your website. Callback URL Leave blank. Click Add this application.
Pass the access token to your client
Save the access token as an environment variable in a
.envfile..envPRISMIC_ACCESS_TOKEN=my-access-tokenThen, pass the access token to the client in
prismicio.ts.prismicio.tsexport function createClient(config: ClientConfig = {}) { const client = baseCreateClient(repositoryName, { accessToken: process.env.PRISMIC_ACCESS_TOKEN, routes, ...config, }); enableAutoPreviews({ client }); return client; }The Client ID and Secret values on the API & Security page can be ignored.
Display content
Prismic content can be displayed using @prismicio/react and @prismicio/next.
Here are the most commonly used components in Next.js websites:
<PrismicNextLink>- Display links usingnext/link.<PrismicNextImage>- Display images usingnext/image.<PrismicRichText>- Display rich text.<PrismicText>- Display plain text.<SliceZone>- Display slices.
Learn how to display content from a field
Live previews in the Page Builder
Content writers can preview content live while editing in the Page Builder. Each slice in a page is shown as a live-updating thumbnail.

A page with live previews in the Page Builder.
Set up live previewing
Live previews require a special /slice-simulator route in your Next.js website.
Add
/slice-simulatorCreate a file at
app/slice-simulator/page.tsxwith the following contents.app/slice-simulator/page.tsximport { SliceSimulator, SliceSimulatorParams, getSlices, } from "@prismicio/next"; import { SliceZone } from "@prismicio/react"; import { components } from "@/slices"; export default async function SliceSimulatorPage({ searchParams, }: SliceSimulatorParams) { const { state } = await searchParams; const slices = getSlices(state); return ( <SliceSimulator> <SliceZone slices={slices} components={components} /> </SliceSimulator> ); }Set the simulator URL in the Page Builder
Navigate to your Prismic repository and open a page.
Click the ”…” button next to the Publish/Unpublish button in the top-right corner. Select Live preview settings.

The live preview settings menu option.
In the modal, enter
http://localhost:3000/slice-simulatorand click Save.
Preview draft content
Content writers can preview content on your website before publishing.
@slicemachine/init performs most of the setup for you during project setup. However, there are a few steps to complete manually.
Set up previews in Next.js
@prismicio/next provides helpers and a component to support content previews.
Add
<PrismicPreview>toapp/layout.tsx<PrismicPreview>adds the Prismic toolbar and event listeners. The website automatically refreshes when a content draft is saved.app/layout.tsximport { type ReactNode } from "react"; import { PrismicPreview } from "@prismicio/next"; import { repositoryName } from "@/prismicio"; export default function RootLayout({ children }: { children: ReactNode }) { return ( <html lang="en"> <body>{children}</body> <PrismicPreview repositoryName={repositoryName} /> </html> ); }Call
enableAutoPreviews()with your Prismic clientenableAutoPreviews()configures a Prismic client to automatically fetch draft content during a preview. Include it in yourprismicio.tsfile.prismicio.tsimport { createClient as baseCreateClient, ClientConfig, } from "@prismicio/client"; import { enableAutoPreviews } from "@prismicio/next"; import sm from "../slicemachine.config.json"; export const repositoryName = sm.repositoryName; export function createClient(config: ClientConfig = {}) { const client = baseCreateClient(repositoryName, config); enableAutoPreviews({ client }); return client; }Create an
/api/previewendpointThis endpoint enables Draft Mode and redirects a content writer to the previewed page. It uses
redirectToPreviewURL().app/api/preview/route.tsimport { NextRequest } from "next/server"; import { redirectToPreviewURL } from "@prismicio/next"; import { createClient } from "@/prismicio"; export async function GET(request: NextRequest) { const client = createClient(); return await redirectToPreviewURL({ client, request }); }Create an
/api/exit-previewendpointThis endpoint ends a preview session. It is called by the Prismic toolbar when closing a preview. It uses
exitPreview().app/api/exit-preview/route.tsimport { exitPreview } from "@prismicio/next"; export function GET() { return exitPreview(); }
Set up previews in Prismic
After setting up previews in your Next.js project, set up previews in Prismic.
Open your preview settings
Navigate to your Prismic repository and go to Settings > Previews.

The previews settings page.
Create a preview
In the Manage your previews section, create a preview using the following values:
Field Value Site name Development Domain for your application http://localhost:3000Preview route /api/previewClick Create my preview.
Deploy
To deploy your website, follow the instructions for deploying Next.js with your chosen hosting provider:
Handle content changes
Your app needs to be notified when content changes in Prismic.
Create a
/api/revalidateRoute HandlerAdd the following
/api/revalidateRoute Handler to your Next.js app. It clears the Next.js cache when content changes in Prismic.app/api/revalidate/route.tsimport { NextResponse } from "next/server"; import { revalidateTag } from "next/cache"; export async function POST() { revalidateTag("prismic", "max"); return NextResponse.json({ revalidated: true, now: Date.now() }); }The
"prismic"tag matches the tag used in page components and the Prismic client. WhenrevalidateTag()is called, Next.js clears all cache entries with this tag.Create a webhook
Add a Prismic webhook that is called when content changes in Prismic.
Follow the Create a webhook instructions in our webhooks documentation using these values:
Field Value Name ”Display published content” URL Your app’s deployed URL + /api/revalidate(e.ghttps://example.com/api/revalidate).Triggers Only check “A page is published” and “A page is unpublished”. You do not need to set up a webhook with your hosting provider.
SEO
Prismic websites can be optimized for search engines using meta_title and meta_descriptions fields. These fields provide metadata and may improve your website’s ranking.
Add SEO fields to your page types
SEO fields are added to page types by default in an SEO tab.
If your page type does not have this tab, create a tab named SEO and add the following fields:
Label API ID Type Description Meta Title meta_titleRich Text The title shown in search results. Meta Description meta_descriptionText The description shown in search results. Meta Image meta_imageImage The image shown in link previews. The
meta_imagefield is not typically used by search engines, but it can be used as a preview when linking to your website on some platforms.Add metadata to pages
Use the metadata fields in a
page.tsx’sgenerateMetadatafunction.app/[uid]/page.tsximport type { Metadata } from "next"; import { asImageSrc } from "@prismicio/client"; import { createClient } from "@/prismicio"; export async function generateMetadata({ params, }: PageProps<"/[uid]">): Promise<Metadata> { const { uid } = await params; const client = createClient(); const page = await client.getByUID("page", uid); return { title: page.data.meta_title, description: page.data.meta_description, openGraph: { images: [{ url: asImageSrc(page.data.meta_image) ?? "" }], }, }; }
Internationalization
Prismic supports websites with multiple languages.
To learn more about Prismic’s locale management, see Locales.
Install the necessary packages
The
negotiatorand@formatjs/intl-localematcherpackages are needed to support internationalization.npm install negotiator @formatjs/intl-localematcherCreate an
i18n.tsfileThe
i18n.tsfile provides a set of internationalization helpers. The helpers will be used in your website’s Proxy.Create an
i18n.tsfile next to to yourappdirectory with the following contents.i18n.tsimport type { NextRequest } from "next/server"; import { match } from "@formatjs/intl-localematcher"; import Negotiator from "negotiator"; /** * A record of locales mapped to a version displayed in URLs. The first entry is * used as the default locale. */ // TODO: Update this object with your website's supported locales. Keys // should be the locale IDs registered in your Prismic repository, and values // should be the string that appears in the URL. const LOCALES = { "en-us": "en-us", "fr-fr": "fr-fr", }; /** Creates a redirect with an auto-detected locale prepended to the URL. */ export function createLocaleRedirect(request: NextRequest): Response { const headers = { "accept-language": request.headers.get("accept-language"), }; const languages = new Negotiator({ headers }).languages(); const locales = Object.keys(LOCALES); const locale = match(languages, locales, locales[0]); request.nextUrl.pathname = `/${LOCALES[locale]}${request.nextUrl.pathname}`; return Response.redirect(request.nextUrl); } /** Determines if a pathname has a locale as its first segment. */ export function pathnameHasLocale(request: NextRequest): boolean { const regexp = new RegExp(`^/(${Object.values(LOCALES).join("|")})(\/|$)`); return regexp.test(request.nextUrl.pathname); } /** * Returns the full locale of a given locale. It returns `undefined` if the * locale is not in the master list. */ export function reverseLocaleLookup(locale: string): string | undefined { for (const key in LOCALES) { if (LOCALES[key] === locale) { return key; } } }Define locales in
i18n.tsi18n.tscontains a list of locales supported by your website.Update the
LOCALESconstant to match your Prismic repository’s locales. The first locale is used when a visitor’s locale is not supported.i18n.tsconst LOCALES = { "en-us": "en-us", "fr-fr": "fr-fr", };You can define how the locale appears in the URL by changing the object’s values. In the following example, the
en-uslocale will appear asenin the URL (e.g./en/about).i18n.tsconst LOCALES = { "en-us": "en", "fr-fr": "fr", };Create or modify
proxy.tsWebsite visitors will be directed to the correct locale using Proxy.
Create a
proxy.tsfile with the following contents, or modify your existingproxy.tsto include the highlighted lines.proxy.tsimport type { NextRequest } from "next/server"; import { createLocaleRedirect, pathnameHasLocale } from "@/i18n"; export function proxy(request: NextRequest) { if (!pathnameHasLocale(request)) { return createLocaleRedirect(request); } } export const config = { matcher: ["/((?!_next|api|slice-simulator|icon.svg).*)"], };Learn more about Proxy
Nest routes under a
[lang]dynamic route segmentCreate a directory at
app/[lang]and nest all other routes under this directory.The
langroute parameter will contain the visitor’s locale. For example,/en-us/aboutsetslangto"en-us".Learn about Next.js dynamic routes
Fetch content from the visitor’s locale
In your
page.tsxfiles, forward thelangparameter to your Prismic query.app/[lang]/[uid]/page.tsximport { createClient } from "@/prismicio"; import { reverseLocaleLookup } from "@/i18n"; export default async function Page({ params }: PageProps<"/[lang]/[uid]">) { const { lang, uid } = await params; const client = createClient(); const page = await client.getByUID("page", uid, { lang: reverseLocaleLookup(lang), }); // ... }The
reverseLocaleLookuphelper fromi18n.tsconverts a shortened locale (e.g.en) to its full version (e.g.en-us).Fetch all locales in
generateStaticParamsFetch pages from all locales using the Prismic client’s
lang: "*"option. Include thelangroute parameter.app/[lang]/[uid]/page.tsxexport async function generateStaticParams() { const client = createClient(); const pages = await client.getAllByType("page", { lang: "*", }); return pages.map((page) => ({ lang: page.lang, uid: page.uid, })); }
Configure Slice Machine
Slice Machine can be configured in slicemachine.config.json. Add options to the adapter.options property.
{
"repositoryName": "example-prismic-repo",
"libraries": ["./src/slices"],
"localSliceSimulatorURL": "http://localhost:3000/slice-simulator",
"adapter": {
"resolve": "@slicemachine/adapter-next",
"options": {
"typescript": true
}
}
}typescriptbooleanDetermines if generated files are written in TypeScript or JavaScript.
true if a project has a tsconfig.json file, false otherwise.
jsxExtensionbooleanDetermines if generated JavaScript files should use a .jsx file
extension. Has no effect when TypeScript is used.
falsegeneratedTypesFilePathstringThe filepath at which generated TypeScript types will be saved.
prismicio-types.d.tsenvironmentVariableFilePathstringThe filepath at which the active Prismic environment is stored as an environment variable.
.env.local