TestaDocs
Docs/Integration/Next.js

Next.js integration

Split-URL experiments (server-side 307, zero flicker), HTML experiments, and goal tracking — one proxy file and two components. You need your project ID from the dashboard (see Onboarding); replace {YOUR_PROJECT_ID} below with it.

bash
npm install @testa-soft/next

1. Create the proxy

proxy.ts at the project root (or under src/). It buckets each visitor server-side, writes the sticky cookie, and issues split-URL redirects before any HTML is sent.

ts
// proxy.ts — Next.js 16
import { NextResponse, type NextFetchEvent, type NextRequest } from "next/server";
import { createTestaProxy } from "@testa-soft/next";

const testa = createTestaProxy({
  projectId: "{YOUR_PROJECT_ID}",
  secureCookies: process.env.NODE_ENV === "production",
  // cache: "per-pageload", // config caching — see the table below
});

export async function proxy(request: NextRequest, event: NextFetchEvent) {
  try {
    return await testa(request, event);
  } catch (error) {
    console.error("[testa] proxy failed, passing through:", error);
    return NextResponse.next();
  }
}

export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico|api).*)"],
};

Next.js 13–15: name the file middleware.ts and the export middleware.

What's the config? Everything you set up in the Testa dashboard — experiments, variations, their changes, goals, targeting — is compiled into a single JSON document (the config) when you hit publish. The proxy and <TestaProvider/> fetch it by projectId from Testa's CDN; it is the one input that drives all of their behavior, so "publishing a change" means "serving a new config".

Caching behavior determines how long it may take for Admin panel changes to propagate to your site. It's controlled by the cache option of createTestaProxy (shown commented out in the snippet above):

cacheBehavior
true (default)Cached 60s, then served stale while revalidating in the background — zero request latency, publishes live within ~1 min.
'per-pageload'Every hard page load fetches a fresh config (publishes live on the next pageview); soft navigations keep the config pinned for the session.
falseNo server-side caching — every request fetches.

2. Enable HTML tests

Add two components to your root layout: <TestaGuard/> (anti-flicker, only renders when there is something to hide) and <TestaProvider/> (applies HTML experiments client-side and arms goal tracking).

tsx
// app/layout.tsx
import { TestaGuard, TestaProvider } from "@testa-soft/next/server";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <TestaGuard selector="body" timeoutMs={4000} />
      </head>
      <body>
        {children}
        <TestaProvider projectId="{YOUR_PROJECT_ID}" />
      </body>
    </html>
  );
}

3. Goals

Create goals in the dashboard (Goals tracking). Page-view and click goals need no code. Custom-event goals fire when your code sends a matching event:

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

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

Also available as window.testa.pushEvent(...) and — legacy-compatible — window.Analytica.pushEvent(...). Each goal counts once per visitor.