TestaDocs
Docs/Integration/Next.js (Pages Router)

Next.js integration (Pages Router)

Two files. Grab your project ID from the dashboard (Get your script) and replace {YOUR_PROJECT_ID} below.

Requires v1.3.5 or later

On the App Router use the App Router guide instead — an app uses one or the other, never both.

bash
npm install @testa-soft/next

1. Middleware

middleware.ts at the project root (or under src/). Buckets the visitor, writes the cookie, and issues split-URL redirects before any HTML is sent.

ts
// middleware.ts
import type { NextFetchEvent, NextRequest } from "next/server";
import { createTestaProxy } from "@testa-soft/next";

const testa = createTestaProxy({
  projectId: "{YOUR_PROJECT_ID}",
  secureCookies: process.env.NODE_ENV === "production",
});

export function middleware(request: NextRequest, event: NextFetchEvent) {
  return testa(request, event);
}

Already have middleware? Put your logic in the handler option, or await the proxy and use the response it returns.

2. Provider

tsx
// pages/_app.tsx
import type { AppProps } from "next/app";
import { TestaProvider } from "@testa-soft/next/pages";

export default function App({ Component, pageProps }: AppProps) {
  return (
    <TestaProvider projectId="{YOUR_PROJECT_ID}">
      <Component {...pageProps} />
    </TestaProvider>
  );
}

The client half of every experiment type: applies HTML changes, tracks goals, reports which variation the visitor saw, re-points next/link navigations, and hides the page until the variant is on it — anti-flicker is on by default, no extra component. Mount it even if you only run split-URL tests; that's how their conversions get counted.

Done. That's a working integration.

Optional: faster first paint

The page stays hidden until the config arrives. Add <TestaGuard/> to start that download in <head> while the HTML is still parsing, instead of after hydration:

tsx
// pages/_document.tsx
import { Head, Html, Main, NextScript } from "next/document";
import { TestaGuard } from "@testa-soft/next/pages";

export default function Document() {
  return (
    <Html lang="en">
      <Head>
        <TestaGuard projectId="{YOUR_PROJECT_ID}" />
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

The provider adopts that request instead of making its own, so it's one config fetch either way.

Optional: faster publishes

By default the middleware caches the config for 60s and refreshes in the background. To pick up dashboard changes on the next page load instead:

ts
createTestaProxy({ projectId: "{YOUR_PROJECT_ID}", cache: "per-pageload" });

Goals

Create goals in the dashboard (Goals tracking). Page-view and click goals need no code. Custom-event goals fire from yours:

ts
import { pushEvent } from "@testa-soft/next";

pushEvent("signup_completed", { plan: "pro" });

Also window.testa.pushEvent(...) and window.Analytica.pushEvent(...). Each goal counts once per visitor.

Analytics events

Identical to the App Router — testa.onVariationApplied(...), window.testa for GTM Custom HTML, and the automatic Analytica dataLayer push. See Analytics events.