Next.js installation
This page has two tiers: the install line that measures browser pageviews, then the app-hook forwarder that observes requests that never run JavaScript. Both are draft until this exact two-tier setup completes its end-to-end run.
What you get
Every page load and every client-side navigation that changes the pathname is measured as a pageview. The install line is deferred and cannot block rendering or delay hydration; it sets no cookies. The forwarder is an observer, never a gatekeeper: it does not block, redirect, or change a request or response.
Prerequisites
| You need | Where it comes from |
|---|---|
Your site key (pk_…) |
Onboarding |
Managed-proxy hostname (<label>.<your-domain>) and its CNAME target |
We name the one record at onboarding, then confirm when the hostname is active |
Ingest URL and forwarder secret (sk_…) |
Onboarding |
| Node.js runtime with writable persistent storage | Required for the forwarder’s durable queue |
The managed proxy is decided and in build: onboarding is concierge today, with no self-serve dashboard. If your Content Security Policy permits only same-origin scripts, use the rewrite alternative in step 3 instead.
Install steps
1. Add the CNAME record, then wait for confirmation
Create the single CNAME record we name for <label>.<your-domain>. Wait until we tell
you the hostname is active before adding the line; certificate activation is part of
that confirmation.
2. Add the install line to your root layout
In the App Router, app/layout.tsx wraps every route, so one edit covers the whole
app. Put the literal tag inside the <body> the root layout already defines:
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<script defer src="https://<label>.<your-domain>/px/t.js" data-site="pk_your_site_key"></script>
</body>
</html>
);
}
Replace pk_your_site_key with your own site key.
In the <body>, not a hand-written <head>. Next.js’s own layout reference says a
root layout must define <html> and <body> and that you should not hand-author
<head> tags there — that is the Metadata API’s job. The install line needs no <head>
anyway: it is frozen as a deferred script sourcing /px/t.js with your data-site,
anywhere in the document, and a deferred script runs after parsing wherever it sits.
Use the literal tag, not a runtime script helper. Framework helpers that inject a script after hydration do load the snippet, but the install line is then absent from the HTML your server sends — and an install check parses the served document. Keeping the tag in the markup means what we check is what you shipped.
Strict Content Security Policy alternative. Keep the same install line semantics
but use this same-origin form and route /px/* to the managed proxy with rewrites()
in next.config:
<script defer src="/px/t.js" data-site="pk_your_site_key"></script>
async rewrites() {
return [{ source: "/px/:path*", destination: "https://<label>.<your-domain>/px/:path*" }];
}
This is a permanent alternative, not an old form. If you must run the relay yourself, use the Node proxy reference.
This line is frozen. It is stable for the life of your installation; no future version will ask you to edit it.
3. Add the forwarder
Install the package:
npm install @aurascope-analytics/forwarder-app-hook
In middleware.ts, run the middleware on the Node runtime:
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { createAuraScopeMiddleware } from "@aurascope-analytics/forwarder-app-hook";
export const config = { runtime: "nodejs" };
const auraScope = createAuraScopeMiddleware();
export function middleware(request: NextRequest) {
auraScope(request);
return NextResponse.next();
}
This tier does not run in Edge middleware: its durable queue needs a filesystem.
Match every path, or accept that unmatched paths are unobserved. This release records
requests before the response, so status ships as null.
Raw requests remain useful as registries improve: a newly recognised crawler can appear in your history retroactively. Crawler fetches are requests, not pageviews; AI-referral counts are always at least the displayed number. Unusual setup?
4. Rebuild and deploy
next build, then deploy as usual. Nothing about your rendering mode changes: the
line is inert markup either way.
Verify your install
-
The line reaches the browser. Against your deployed site:
curl -sS https://www.your-site.example/ | grep '/px/t.js'Expect the line itself back. Nothing printed means it is being injected at runtime rather than rendered — see step 2.
-
The script is served.
curl -sS -D - -o /dev/null https://<label>.<your-domain>/px/t.jsExpect
200andcontent-type: text/javascript; charset=utf-8. -
Navigation is measured. Open your app with DevTools → Network. Expect
t.jsat200and one/px/erequest at204on load, one more/px/eon each client-side navigation to a different path, and nothing when only the query string or the hash changes — a pageview is a pathname change. -
Confirm the forwarder. Log
auraScope.forwarder?.metrics()after some traffic:observedclimbs,acceptedcatches up, anddroppedstays0. -
End-to-end confirmation. There is no self-serve install checker yet, so the last step — your test pageviews visible as durably stored rows — comes from us during onboarding. Ask once steps 1–3 pass. This is the only step that catches a misconfigured relay behind your
/px/*routes: your browser still sees204either way, because the measurement path is deliberately fail-open.
Failure modes — and what they do to your site
Nothing on your site waits for AuraScope. The install line is deferred and the forwarder copies request metadata synchronously, then queues and ships it away from the response path. If the service is unreachable, the forwarder pauses, retains, and resumes; only capacity exhaustion drops the oldest events, and it counts that loss.
Troubleshooting
/px/t.js returns 404
The line is installed but the managed-proxy hostname is not active, or your same-origin
rewrite is absent. Check the onboarding confirmation and your rewrites() rule.
curl shows no line, but the browser loads the snippet
The tag is being added at runtime instead of rendered into the document. Move it into the root layout as a literal tag.
Two e requests fire on one navigation
The line is in the document twice — commonly once in the root layout and once in a nested layout or page. Keep exactly one.
Uninstall
Remove the tag from app/layout.tsx, rebuild, and deploy. That is everything:
nothing was installed on your server, and nothing was stored in your visitors’
browsers.