In this tutorial, you'll build a small Next.js website with Prismic from scratch: setting up the project, modeling content, fetching and displaying it, wiring up live and full-website previews, and configuring caching so your site stays fast without serving stale content.
What you should already know
Set up your Next.js project
You can add Prismic to a new or an existing Next.js project.
npx create-next-app@latest my-website
cd my-websiteFrom your project's root, run the Prismic CLI's init command. It creates your repository (if you don't have one yet), installs the required packages, and configures your project.
npx prismic init
# Already have a repository? Point the CLI at it directly:
npx prismic init --repo your-domainWhat prismic init sets up for you
Running the CLI configures your project with:
- A Prismic client at
prismicio.ts - Route resolvers and Next.js fetch options preconfigured on that client
- A
/slice-simulatorroute for live previews in the Page Builder /api/previewand/api/exit-previewroute handlers for full-website draft previews- A
/api/revalidateroute handler for on-demand cache revalidation
You'll use every one of these in this tutorial.
Get familiar with the Prismic CLI
The Prismic CLI is the tool you'll return to throughout a project's life, not just at setup. It manages your repository's locales, access tokens, previews, and webhooks directly from your terminal, which is especially handy if you're wiring up a project with an AI coding agent.
# Regenerate any setup files your project is missing
npx prismic gen setup
# Create a private-content access token
npx prismic token create
# Register a preview URL
npx prismic preview add http://localhost:3000/api/preview --name Development
# Create a revalidation webhook
npx prismic webhook create https://example.com/api/revalidate --trigger documentsPublished --trigger documentsUnpublished
# Add a locale
npx prismic locale add fr-frModel content with page types and slices
Content writers build pages from page types, like a homepage, blog post, or landing page, and reusable sections called slices, like a hero, a text block, or a call to action. You can model both by hand in the Type Builder, or hand the job to the Prismic CLI, which is built for AI agents to drive directly.
TypeScript types are generated automatically, and a starter React component is scaffolded for every slice you create at src/slices/<SliceName>/index.tsx.
Generated TypeScript types
Every page type and slice you model is reflected in a generated prismicio-types.d.ts file, kept in sync automatically as your content models change. These types cover your Content.PageDocument, Content.HomepageDocument, and every slice's props, so your editor can autocomplete field names and catch typos before you ship them.
Define your routes
Prismic needs to know your website's URL structure to fill in link fields correctly. The CLI keeps a routes array in prismic.config.json in sync with your page types automatically: a page type named Homepage maps to /, a page type named Page maps to /:uid, and any other page type maps to /<api-id>/:uid.
Edit the file directly if you need custom paths. Just make sure they match your Next.js file-system routes.
{
"repositoryName": "example-prismic-repo",
"routes": [
{ "type": "homepage", "path": "/" },
{ "type": "page", "path": "/:uid" },
{ "type": "blog", "path": "/blog/:uid" }
]
}Fetch and display content
Your prismicio.ts file centralizes the Prismic client, your routes, and your caching configuration in one place. You'll import createClient() from here anywhere you need to query content.
import {
createClient as baseCreateClient,
type ClientConfig,
} from "@prismicio/client";
import { enableAutoPreviews } from "@prismicio/next";
import prismicConfig from "../prismic.config.json";
export const repositoryName = prismicConfig.repositoryName;
export const createClient = (config: ClientConfig = {}) => {
const client = baseCreateClient(repositoryName, {
routes: prismicConfig.routes,
fetchOptions: {
next: { tags: ["prismic"] },
cache: "force-cache",
},
...config,
});
enableAutoPreviews({ client });
return client;
};With the client in place, query content directly inside an async Server Component. Note that in current Next.js versions, route params arrive as a Promise, so you'll need to await them before use.
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,
};
}
export async function generateStaticParams() {
const client = createClient();
const pages = await client.getAllByType("page");
return pages.map((page) => ({ uid: page.uid }));
}You can query content the same way inside a slice component, which is useful for things like a footer or a contact form that needs its own Settings document.
A handful of components from @prismicio/react and @prismicio/next handle the rest of the rendering work:
<SliceZone>renders a page's slices using the components you map them to<PrismicRichText>renders rich text fields as React elements<PrismicNextLink>renders link fields usingnext/link<PrismicNextImage>renders image fields usingnext/image, with automatic width and height
Add live previews in the Page Builder
The Page Builder shows a live-updating thumbnail for each slice as content writers edit, powered by a slice simulator route that prismic init already added to your project.
import {
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>
);
}Point your repository at the simulator so the Page Builder knows where to load it:
npx prismic preview set-simulator http://localhost:3000Set up full-website draft previews
Full-website previews let content writers click through your actual site to see draft content in context before it's published. <PrismicPreview> adds the toolbar and event listeners that make this work.
import { 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}
<PrismicPreview repositoryName={repositoryName} />
</body>
</html>
);
}Two Route Handlers bridge Prismic and your app: one enters Next.js Draft Mode and redirects to the previewed page, the other ends the session.
import { 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 });
}import { exitPreview } from "@prismicio/next";
export function GET() {
return exitPreview();
}Finally, tell Prismic about your preview endpoint. Add another one for your production domain once you deploy.
npx prismic preview add http://localhost:3000/api/preview --name DevelopmentConfigure caching and on-demand revalidation
Look back at the fetchOptions in your prismicio.ts file: next.tags tags every Prismic API call with "prismic", and cache: "force-cache" tells Next.js to cache those calls indefinitely, until that tag is revalidated. This gives you the speed of static generation without giving up fresh content.
To clear the cache the moment content changes, add a /api/revalidate Route Handler that calls revalidateTag() on the same "prismic" tag.
import { NextResponse } from "next/server";
import { revalidateTag } from "next/cache";
export async function POST() {
revalidateTag("prismic");
return NextResponse.json({ revalidated: true, now: Date.now() });
}Then register that endpoint as a webhook, so Prismic calls it whenever a document is published or unpublished. You don't need to configure anything with your hosting provider.
npx prismic webhook create https://example.com/api/revalidate \
--trigger documentsPublished \
--trigger documentsUnpublishedWorking locally
Since queries are cached indefinitely, you won't see new content locally until you revalidate or hard-refresh your browser (Shift+Cmd/Ctrl+R). That's expected. Once deployed, the webhook keeps production content current automatically.
Migrating an older Prismic and Next.js project
If you built with Prismic and Next.js a while back, here's what's changed and what to check when you touch that codebase again:
- Pages Router to App Router:
getStaticPropsandgetStaticPathsare replaced by fetching data directly inside async Server Components, plusgenerateStaticParamsfor static generation. - Slice Machine to the Prismic CLI:
@slicemachine/initand the standalone Slice Machine UI have given way tonpx prismic initand CLI commands likeprismic gen setup. The Type Builder remains available if you prefer a visual editor. slicemachine.config.jsontoprismic.config.json: routes, repository name, and adapter options moved to the new config file. Older projects can keep their existing file; new projects get the new one by default.- Route params are now asynchronous:
paramsandsearchParamsare Promises in current Next.js versions, so page and layout components need toawait paramsbefore readinguid,lang, or other route segments. middleware.tsis nowproxy.ts: if your project uses internationalization or other request-level logic, rename the file and update the exported function toproxy().
If you're not sure what's missing from an older project, npx prismic gen setup will scaffold any files it doesn't find, including the Prismic client, the slice simulator, and the preview and revalidate Route Handlers.
Recap
You now have a Next.js website with Prismic wired in end to end:
- Content is modeled through the Prismic CLI or the Type Builder, with TypeScript types generated for you
- Pages and slices fetch content directly inside Server Components
- Content writers get live previews in the Page Builder and full-website draft previews before anything publishes
- Published content reaches your site through tagged, on-demand cache revalidation
- More about Next.js at https://prismic.io/docs/nextj
Questions?
If something doesn't click, ask in the Prismic Community forum (we're there most days). Or add your comments below.



