---
title: "Use Next.js with Prismic"
description: "Learn how to build websites with Next.js and Prismic."
category: "frameworks"
audience: developers
lastUpdated: "2026-04-28T19:34:14.000Z"
---

> This guide uses the [Type Builder](https://prismic.io/docs/type-builder.md), which is rolling out to new users. If you do not have access, use the [Slice Machine guide](https://prismic.io/docs/nextjs/with-slice-machine.md).

# Overview

Prismic has a first-party Next.js integration that supports all of Prismic's features:

* Model content with [page types](https://prismic.io/docs/content-modeling.md#page-types) and [slices](https://prismic.io/docs/slices.md) using the [Type Builder](https://prismic.io/docs/type-builder.md) or the [Prismic CLI](https://prismic.io/docs/cli.md).
* Fetch and display content using [SDKs](https://prismic.io/docs/apis.md) with generated [TypeScript](https://www.typescriptlang.org/) types.
* Preview draft content with [live previews](https://prismic.io/docs/previews.md#live-previews) and [full-website previews](https://prismic.io/docs/previews.md#full-website-previews).

> <CalloutHeading>Using an AI agent?</CalloutHeading>
>
> Configure your agent to use this page as a reference.
>
> <CalloutButton href="https://prismic.io/docs/ai.md" endIcon={<ArrowRightIcon />}>
>   Set up your AI agent
> </CalloutButton>

Here's what a Prismic page looks like in Next.js:

```tsx filename=app/[uid]/page.tsx
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](https://prismic.io/docs/technical-reference/prismicio-client/v7.md), [@prismicio/react](https://prismic.io/docs/technical-reference/prismicio-react/v3.md), and [@prismicio/next](https://prismic.io/docs/technical-reference/prismicio-next/v2.md).

# Set up a Next.js website

Prismic can be added to new or existing Next.js websites. Set up your project using the [Type Builder](https://prismic.io/docs/type-builder.md), a tool for building by hand, or the [Prismic CLI](https://prismic.io/docs/cli.md), a tool for AI agents.

* **Type Builder:**

  1. **Create a Prismic repository**

     From the [Prismic dashboard](https://prismic.io/dashboard), create a Prismic repository. Select **Next.js**.

  2. **Set up your project**

     Follow the setup instructions shown in your Prismic repository. The instructions walk you through creating a Next.js project, connecting it to Prismic, and modeling content with the Type Builder.

  3. **Add `<PrismicPreview>` to your root layout**

     [`<PrismicPreview>`](https://prismic.io/docs/technical-reference/prismicio-next/v2.md#prismicpreview) adds support for content draft previews. The `@/prismicio` import comes from a file that `prismic init` creates for you during setup.

     ```tsx filename=app/layout.tsx {2,3,9}
     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}</body>
           <PrismicPreview repositoryName={repositoryName} />
         </html>
       );
     }
     ```

     Your Next.js website is now ready for Prismic. Continue to [create pages](#create-pages) and [slices](#create-slices).

* **Prismic CLI:**

  > Your AI agent can perform these steps for you. See [Prismic with AI](https://prismic.io/docs/ai.md) for details.

  1. **Create a Next.js project**

     ```sh
     npx create-next-app@latest my-website
     cd my-website
     ```

     You can also use an existing Next.js project.

  2. **Add Prismic to your project**

     The `init` command creates a Prismic repository, installs packages, and configures your project.

     ```sh
     npx prismic init
     ```

     If you already have a Prismic repository, provide its domain with `--repo`:

     ```sh
     npx prismic init --repo your-domain
     ```

  3. **Add `<PrismicPreview>` to your root layout**

     [`<PrismicPreview>`](https://prismic.io/docs/technical-reference/prismicio-next/v2.md#prismicpreview) adds support for content draft previews. The `@/prismicio` import comes from a file that `prismic init` creates for you.

     ```tsx filename=app/layout.tsx {2,3,9}
     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}</body>
           <PrismicPreview repositoryName={repositoryName} />
         </html>
       );
     }
     ```

     Your Next.js website is now ready for Prismic. Continue to [create pages](#create-pages) and [slices](#create-slices).

# Create pages

Content writers create pages from [page types](https://prismic.io/docs/content-modeling.md#page-types), like a homepage, blog post, or landing page. Model page types by hand in the [Type Builder](https://prismic.io/docs/type-builder.md), or with the [Prismic CLI](https://prismic.io/docs/cli.md) for AI agents. TypeScript types are generated automatically.

[Learn how to create page types](https://prismic.io/docs/content-modeling.md#how-to-create-a-page-type)

## Write page components

Each page type needs a Next.js page component. With file-system-based routing, you create a [page file](https://nextjs.org/docs/app/api-reference/file-conventions/page) at each page's path.

The example below shows a page component for a **Page** page type.

```tsx filename=app/[uid]/page.tsx collapsed
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

The Prismic CLI keeps [routes](https://prismic.io/docs/routes.md) in `prismic.config.json` in sync with your page types, whether you model in the Type Builder or with CLI commands.

Page routes are inferred from each page type's API ID:

* A page type named **Homepage** maps to `/`.
* A page type named **Page** maps to `/:uid`.
* Any other page type, like **Blog Post**, maps to `/<api-id>/:uid` (e.g. `/blog-post/:uid`).

```json filename=prismic.config.json
{
  "repositoryName": "example-prismic-repo",
  "routes": [
    { "type": "homepage", "path": "/" },
    { "type": "page", "path": "/:uid" },
    { "type": "blog_post", "path": "/blog/:uid" }
  ]
}
```

Edit `prismic.config.json` directly to customize routes. Make sure your routes match your Next.js file-system routes. Here are common examples:

| 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 routes](https://prismic.io/docs/routes.md)

# Create slices

Content writers build pages from reusable sections called [slices](https://prismic.io/docs/slices.md), like a block of text, a hero, or a call to action. Model slices by hand in the [Type Builder](https://prismic.io/docs/type-builder.md), or with the [Prismic CLI](https://prismic.io/docs/cli.md) for AI agents. TypeScript types are generated automatically.

[Learn how to create slices](https://prismic.io/docs/slices.md#how-to-create-a-slice)

## Write React components

The Prismic CLI generates a starter component in `src/slices/<SliceName>/index.tsx`. Edit the component to add your implementation.

The following example **Call to Action** component displays a rich text field and a link.

```tsx filename=src/slices/CallToAction/index.tsx
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](#display-content)

# Fetch content

Use [`@prismicio/client`](https://prismic.io/docs/technical-reference/prismicio-client.md) and its methods to fetch page content.

## The Prismic client

The Prismic CLI creates a `prismicio.ts` file when you run `prismic init`. It centralizes your client configuration, including [routes](https://prismic.io/docs/routes.md) and [Next.js fetch options](https://nextjs.org/docs/app/api-reference/functions/fetch#fetchurl-options).

```ts filename=prismicio.ts collapsed
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;
};
```

The generated `fetchOptions` includes the following [Next.js cache settings](https://nextjs.org/docs/app/api-reference/functions/fetch#fetchurl-options):

* `next.tags`: Tags all Prismic API calls with `prismic`.
* `cache`: API calls are cached forever until the `prismic` tag is revalidated.

> **Tip**: `npx prismic gen setup` creates the client file if it doesn't exist.

## Fetch content in pages and slices

Import `createClient()` from `prismicio.ts` and create a client. Use the client to fetch content. The following example fetches content for a `/[uid]` dynamic route.

```ts filename=src/app/[uid]/page.tsx {3,8-9}
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} />;
}
```

You can fetch content in slices the same way. The following example fetches a **Settings** page.

```ts filename=src/slices/ContactForm/index.tsx {3,8-9}
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](https://prismic.io/docs/fetch-content.md)

## Fetch new content

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, usually with <Kbd>Shift+<KbdMod />R</Kbd>.

In production, set up content revalidation to fetch new content automatically.

[Learn how to set up content revalidation](#handle-content-changes)

## 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](https://prismic.io/docs/fetch-content.md#content-visibility)

1. **Generate an access token**

   ```sh
   npx prismic token create
   ```

   Save the new access token as an environment variable in a [`.env`](https://nextjs.org/docs/app/guides/environment-variables) file.

   ```sh filename=.env
   PRISMIC_ACCESS_TOKEN=my-access-token
   ```

   Access tokens can also be managed in your repository at **Settings** → **API & Security**.

2. **Pass the access token to your client**

   Provide the token via the `accessToken` option in `prismicio.ts`:

   ```ts filename=prismicio.ts
   export const createClient = (config: ClientConfig = {}) => {
     const client = baseCreateClient(repositoryName, {
       accessToken: process.env.PRISMIC_ACCESS_TOKEN, // [!code ++]
       routes: prismicConfig.routes,
       fetchOptions: {
         next: { tags: ["prismic"] },
         cache: "force-cache",
       },
       ...config,
     });

     enableAutoPreviews({ client });

     return client;
   };
   ```

3. **Change your repository's API access**

   Set the API access to **private**. Content API requests will immediately require an access token.

   ```sh
   npx prismic repo set-api-access private
   ```

   API visibility can also be managed in your repository at **Settings** → **API & Security**.

> **Important**
>
> The access token is a secret. Do not use it in client-side requests.

# Display content

Display content using [`@prismicio/react`](https://prismic.io/docs/technical-reference/prismicio-react/v3.md) and [`@prismicio/next`](https://prismic.io/docs/technical-reference/prismicio-next/v2.md). Here are the most commonly used components:

* [`<PrismicNextLink>`](https://prismic.io/docs/technical-reference/prismicio-next/v2.md#prismicnextlink) - Display links using [`next/link`](https://nextjs.org/docs/app/api-reference/components/link).
* [`<PrismicNextImage>`](https://prismic.io/docs/technical-reference/prismicio-next/v2.md#prismicnextimage) - Display images using [`next/image`](https://nextjs.org/docs/app/api-reference/components/image).
* [`<PrismicRichText>`](https://prismic.io/docs/technical-reference/prismicio-react/v3.md#prismicrichtext) - Display rich text.
* [`<PrismicText>`](https://prismic.io/docs/technical-reference/prismicio-react/v3.md#prismictext) - Display plain text.
* [`<SliceZone>`](https://prismic.io/docs/technical-reference/prismicio-react/v3.md#slicezone) - Display slices.

[Learn how to display content from a field](https://prismic.io/docs/fields.md)

# Live previews in the Page Builder

The [Page Builder](https://prismic.io/docs/previews.md#live-previews) shows live-updating thumbnails for each [slice](https://prismic.io/docs/slices.md) as content writers edit.

Live previews are powered by the **slice simulator**, a special route that renders individual slices. The Prismic CLI creates the simulator page at `app/slice-simulator/page.tsx` when you run `prismic init`.

Tell Prismic where your simulator is running so the Page Builder can load it:

```sh
npx prismic preview set-simulator http://localhost:3000
```

The simulator URL can also be set from any document. Click the "**...**" button next to the Publish/Unpublish button in the top-right corner and select **Live preview settings**.

Once your website is deployed, update the simulator URL to your production domain.

> **Tip**: `npx prismic gen setup` creates the simulator route if it doesn't exist.

# Preview draft content

[Full-website previews](https://prismic.io/docs/previews.md#full-website-previews) let content writers see draft content on your website before publishing. The `prismic init` command creates the needed routes during setup: `/api/preview` starts a draft preview session, and `/api/exit-preview` ends it.

Add a development preview URL so you can preview drafts locally:

```sh
npx prismic preview add http://localhost:3000/api/preview --name Development
```

Once deployed, add your production URL as a second preview. Previews can also be managed at **Settings** → **Previews**.

> **Tip**: `npx prismic gen setup` creates the preview Route Handlers if they don't exist.

# Deploy

Deploy your Next.js website with your hosting provider:

* [Vercel](https://vercel.com/guides/deploying-nextjs-with-vercel)
* [Netlify](https://docs.netlify.com/welcome/add-new-site/)
* [AWS Amplify](https://docs.amplify.aws/nextjs/build-a-backend/)
* [Cloudflare](https://opennext.js.org/cloudflare)

## Handle content changes

The `prismic init` command creates a `/api/revalidate` Route Handler that clears the Next.js cache when content changes in Prismic.

Register the endpoint as a webhook so Prismic calls it when content is published or unpublished. You do not need to set up a webhook with your hosting provider.

```sh
npx prismic webhook create https://example.com/api/revalidate \
  --trigger documentsPublished \
  --trigger documentsUnpublished
```

Webhooks can also be managed at **Settings** → **Webhooks**.

> **Tip**: `npx prismic gen setup` creates the revalidate Route Handler if it doesn't exist.

# SEO

[Page types](#create-pages) automatically include fields in an **SEO & Metadata** tab:

* `meta_title`: The page's title.
* `meta_description`: The page's description.
* `meta_image`: A preview image when your page is shared on social platforms.

Use the metadata fields in [`generateMetadata`](https://nextjs.org/docs/app/api-reference/functions/generate-metadata) inside `page.tsx`.

```ts filename=app/[uid]/page.tsx {12-18}
import 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 multi-lingual websites. See [Locales](https://prismic.io/docs/languages-locales.md) for details on locale management.

> This guide is based on the official [Next.js internationalization guide](https://nextjs.org/docs/app/building-your-application/routing/internationalization).

1. **Add locales to your repository**

   Use the [Prismic CLI](https://prismic.io/docs/cli.md) to add locales.

   ```sh
   npx prismic locale add fr-fr
   ```

   Locales can also be managed in your repository at **Settings** → **Translations & Locales**.

2. **Install the necessary packages**

   Install the packages needed for locale detection:

   ```sh
   npm install negotiator @formatjs/intl-localematcher
   ```

3. **Create an `i18n.ts` file**

   Create an `i18n.ts` file next to your `app` directory. It provides locale helpers you'll use in your website's [Proxy](https://nextjs.org/docs/app/getting-started/proxy).

   ```ts filename=i18n.ts
   import 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 ID for a given URL 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;
       }
     }
   }
   ```

4. **Define locales in `i18n.ts`**

   `i18n.ts` contains a list of locales supported by your website.

   Update the `LOCALES` constant to match your Prismic repository's locales. The first locale is used when a visitor's locale is not supported.

   ```ts filename=i18n.ts
   const LOCALES = {
     "en-us": "en-us",
     "fr-fr": "fr-fr",
   };
   ```

   Change the values to control how locales appear in URLs. In this example, the `en-us` locale appears as `en` in the URL (e.g. `/en/about`).

   ```ts filename=i18n.ts {2-3}
   const LOCALES = {
     "en-us": "en",
     "fr-fr": "fr",
   };
   ```

   > Statically defining your locales avoids a Prismic API call, optimizing your website's performance.

5. **Create or modify `proxy.ts`**

   > This file is called `middleware.ts` in Next.js v15 and earlier.

   The [Proxy](https://nextjs.org/docs/app/getting-started/proxy) redirects visitors to the correct locale.

   Create a `proxy.ts` file with the following contents, or modify your existing `proxy.ts` to include the highlighted lines.

   ```ts filename=proxy.ts {2,5-7}
   import 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](https://nextjs.org/docs/app/getting-started/proxy)

6. **Nest routes under a `[lang]` dynamic route segment**

   Create a directory at `app/[lang]` and nest all other routes under this directory.

   The `lang` route parameter will contain the visitor's locale. For example, `/en/about` sets `lang` to `"en"`.

   [Learn about Next.js dynamic routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes)

7. **Fetch content from the visitor's locale**

   In your `page.tsx` files, forward the `lang` parameter to your Prismic query.

   ```ts filename=app/[lang]/[uid]/page.tsx {2,9}
   import { 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 `reverseLocaleLookup` helper from `i18n.ts` converts a shortened locale (e.g. `en`) to its full version (e.g. `en-us`).

8. **Fetch all locales in `generateStaticParams`**

   > This step is needed if you statically build pages using [`generateStaticParams`](https://nextjs.org/docs/app/api-reference/functions/generate-static-params), which we recommend.

   Fetch pages from all locales using the Prismic client's `lang: "*"` option. Include the `lang` route parameter.

   ```ts filename=app/[lang]/[uid]/page.tsx {4,8}
   export async function generateStaticParams() {
     const client = createClient();
     const pages = await client.getAllByType("page", {
       lang: "*",
     });

     return pages.map((page) => ({
       lang: page.lang,
       uid: page.uid,
     }));
   }
   ```
