Updated 2026-09-25
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/orsrc/pages/, and anext.config.*file.
Steps
Use the wizard step Install the widget, or Settings → Widget install.
- If
pages/_document.tsx,pages/_document.jsx,pages/_document.js,src/pages/_document.tsx,src/pages/_document.jsx, orsrc/pages/_document.jsalready exists, add this just before</body>. Install automatically inserts that line. In a.tsxor.jsxdocument 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.
- If no
_documentfile exists, Install automatically createspages/_document.tsx, orsrc/pages/_document.tsxwhen the pages live undersrc/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.
- 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.
// 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
- Deploy
_document. - Open Settings → Widget install.
- Press Check installation.
- 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 onthe saved site URL.Paste the snippet just before </body> and check again.The widget on this page belongs to another project.We couldn't reachthe saved site URL.Connection failed. The widget will be marked installed as soon as it loads on the site.Timed outandHTTPplus the status code replaceConnection failedwhen that is the cause.This page is rendered in the browser, so the widget script is not in the HTML we fetched fromthe 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.
- MDF-AUTH-002 and Email not allowed if the widget loads but the email is rejected.