Next.js
The fast way
Section titled “The fast way”npx @plaintrace/cli init --site <site id> --endpoint https://collect.plaintrace.devThe CLI edits app/layout.* or src/app/layout.* (App Router), or pages/_document.* (Pages Router; created if
missing). It shows the diff and asks before writing (--yes skips the question).
By hand: App Router
Section titled “By hand: App Router”import Script from 'next/script'
export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body> {children} <Script src="https://collect.plaintrace.dev/t.js" data-site="<site id>" strategy="afterInteractive" /> </body> </html> )}Route changes are picked up automatically: no router hook is needed.
By hand: Pages Router
Section titled “By hand: Pages Router”Add <script async src="https://collect.plaintrace.dev/t.js" data-site="<site id>" /> inside <Head> in
pages/_document.tsx.
Events
Section titled “Events”With the script tag, call the global px the script defines:
'use client'
declare global { interface Window { px?: (...args: unknown[]) => unknown }}
<button onClick={() => window.px?.('track', 'signup_clicked', { plan: 'pro' })}>Start</button>Prefer the npm package? Use it instead of the script tag: call init() once in a client component, then
track() anywhere. track() calls made before init() are queued until it runs.
'use client'import { init } from '@plaintrace/browser'import { useEffect } from 'react'
export function Analytics() { useEffect(() => void init({ site: '<site id>', endpoint: 'https://collect.plaintrace.dev' }), []) return null}See events.
Server SDK in middleware
Section titled “Server SDK in middleware”The sample uses middleware.ts (Next.js 15). In Next.js 16 the file is called proxy.ts and exports proxy;
the body is the same.
Serve the tracker from your own domain, record crawler reads and measure loss:
import { createClient } from '@plaintrace/server'import type { NextFetchEvent, NextRequest } from 'next/server'
const analytics = createClient({ key: process.env.ANALYTICS_KEY!, endpoint: 'https://collect.plaintrace.dev', ipHeader: 'x-vercel-forwarded-for', // on Vercel; see the proxy page for other hosts})
export async function middleware(request: NextRequest, event: NextFetchEvent) { const res = await analytics.handle(request, { waitUntil: (p) => event.waitUntil(p) }) if (res) return res}Then load the script from your domain: src="/_i/t.js" with data-endpoint="/_i". See
first-party proxy.