# Next.js Pages Router

Add the widget script to pages/_document, then confirm Widget install is complete.

## Prerequisites

- A Modifee project for this repository.
- **Site URL** saved under **Settings → Connection**.
- **Authentication** chosen under **Settings → Client access**. The default is **Site without login** (wizard: **Public site, no login**).
- A Pages Router app: files under `pages/` or `src/pages/`, and a `next.config.*` file.

## Steps

Use the wizard step **Install the widget**, or **Settings → Widget install**.

1. If `pages/_document.tsx`, `pages/_document.jsx`, `pages/_document.js`, `src/pages/_document.tsx`, `src/pages/_document.jsx`, or `src/pages/_document.js` already exists, add this just before `</body>`. **Install automatically** inserts that line. In a `.tsx` or `.jsx` document the inserted tag is self-closing.

```
<script src="https://www.modifee.com/widget.js" data-project="YOUR_PROJECT_KEY" async />
```

`YOUR_PROJECT_KEY` is the `data-project` value in **Settings → Widget install → Install manually**. The same value is listed under **Settings → Danger zone**. Use the `src` from that snippet if it is not `https://www.modifee.com/widget.js`.

2. If no `_document` file exists, **Install automatically** creates `pages/_document.tsx`, or `src/pages/_document.tsx` when the pages live under `src/pages/`. You can paste this file yourself:

```
import { Html, Head, Main, NextScript } from "next/document";

export default function Document() {
  return (
    <Html>
      <Head />
      <body>
        <Main />
        <NextScript />
        <script src="https://www.modifee.com/widget.js" data-project="YOUR_PROJECT_KEY" async></script>
      </body>
    </Html>
  );
}
```

`YOUR_PROJECT_KEY` is the same key as in step 1.

3. Or press **Install automatically**. The pull request is titled "Install Modifee widget" on branch `modifee/install-widget`. Merge it and wait for the deploy. The screen says "Merged — waiting for deploy" until the check succeeds.

**Site without login** does not use `MODIFEE_SECRET`.

**App with login** is supported as a manual snippet. **Install automatically** still refuses, with "Pages Router has no server layout we can attach a signed token to without guessing how users log in. Paste the snippet above. No pull request was opened."

Open **Install manually** and select **Next.js (Pages Router)**. The widget reads `data-token` on the script tag. It does not call an API for the token. Sign in `getServerSideProps` on each page that should show the widget. `pages/_app.tsx` only receives the token through `pageProps`. Do not import the signing file from `_app`. Do not put the secret in a `NEXT_PUBLIC_` variable. `getSessionUser` is not a Modifee function. Replace it with the call that reads the signed-in user from the request. Store the secret as `MODIFEE_SECRET`. See [Signing secret](https://www.modifee.com/docs/vercel/signing-secret).

```
// server-only — never import this file from client code
import crypto from "crypto";

export function modifeeToken(email, name) {
  const payload = Buffer.from(
    JSON.stringify({
      email,
      name,
      exp: Math.floor(Date.now() / 1000) + 86400,
    }),
  ).toString("base64url");
  const sig = crypto
    .createHmac("sha256", process.env.MODIFEE_SECRET)
    .update(payload)
    .digest("base64url");
  return `${payload}.${sig}`;
}

// pages/index.tsx — add this to each page that should show the widget. Keep the page component.
export async function getServerSideProps({ req }) {
  const { modifeeToken } = await import("@/lib/modifee");
  const user = await getSessionUser(req);
  return {
    props: {
      modifeeToken: user?.email ? modifeeToken(user.email, user.name) : null,
    },
  };
}

// pages/_app.tsx — do not import the signing file here
import Script from "next/script";

export default function App({ Component, pageProps }) {
  return (
    <>
      <Component {...pageProps} />
      {pageProps.modifeeToken ? (
        <Script
          src="https://www.modifee.com/widget.js"
          data-project="YOUR_PROJECT_KEY"
          data-token={pageProps.modifeeToken}
          strategy="afterInteractive"
        />
      ) : null}
    </>
  );
}
```

The token is created in `getServerSideProps` on the page, passed in `props`, and read in `pages/_app.tsx` as `pageProps.modifeeToken`. `_app` has no `getServerSideProps`. That page must be rendered for the request. Leave the response uncached: do not send a public `Cache-Control` header (`public` or `s-maxage`). A cached HTML response can send one user's token to someone else.

If the repo has both an App Router layout and `pages/_document`, **Site without login** edits both. **App with login** edits the App Router layout only, and the pull request notes "Pages Router routes are not included. Those pages still need the snippet, or switch this project to Site without login."

## Verify

1. Deploy `_document`.
2. Open **Settings → Widget install**.
3. Press **Check installation**.
4. You should see "Found on the site." and "Widget found on the site." The mark beside **Widget install** turns green.

The mark stays green when the widget has loaded and the latest check did not fail. The yellow tooltip is "Widget not found on the site".

## Troubleshooting

- "Add a site URL before checking."
- `We couldn't find the widget on` the saved site URL. `Paste the snippet just before </body> and check again.`
- `The widget on this page belongs to another project.`
- `We couldn't reach` the saved site URL. `Connection failed. The widget will be marked installed as soon as it loads on the site.` `Timed out` and `HTTP` plus the status code replace `Connection failed` when that is the cause.
- `This page is rendered in the browser, so the widget script is not in the HTML we fetched from` the saved site URL. `Open the site after you paste the snippet. We'll mark this done as soon as the widget loads.`
- "This Next.js app has more than one pages/_document. Paste the snippet above. No pull request was opened."
- "This Next.js app has no app/layout or pages/_document to edit. Paste the snippet above. No pull request was opened."
- "Pages Router has no server layout we can attach a signed token to without guessing how users log in. Paste the snippet above. No pull request was opened."
- "Missing GitHub permissions. Re-authorize the GitHub App with contents:write and pull_requests:write."
- "This repository is archived. No pull request was opened."
- "GitHub rate limit reached. Wait a minute and try again."
- "Could not open the pull request."
- "This project has no API key."
- Browser console: "Modifee: data-project is required".
- [Widget not showing](https://www.modifee.com/docs/troubleshooting/widget-not-showing).
- [MDF-AUTH-002](https://www.modifee.com/docs/errors/MDF-AUTH-002) and [Email not allowed](https://www.modifee.com/docs/troubleshooting/email-not-allowed) if the widget loads but the email is rejected.
