Next.js
Next.js has two different proxy mechanisms depending on which mode you need.
- Simple mode: a one-liner in
next.config.js. Done. - Full mode: requires a Route Handler (or Edge Function). A plain
rewrites()config proxies server-side and drops the visitor IP, which breaks geolocation. The Route Handler reads the IP and re-sends it.
Snow’s Settings → Proxy tab generates the right code for each mode.
Simple mode (recommended)
Add one rewrite to your Next.js config. That’s it.
// next.config.js
module.exports = {
async rewrites() {
return [
{
source: "/sa/sa.js",
destination: "https://api.snowanalytics.app/sa.js",
},
];
},
};Then update your embed to add data-api-url:
<script defer
src="https://yourdomain.com/sa/sa.js"
data-site="YOUR_SITE_ID"
data-api-url="https://api.snowanalytics.app/api/collect"
data-domain="yourdomain.com"></script>The data-api-url attribute tells sa.js to post events directly to the Snow API. The browser sends them; your Next.js server is not in the path for events, so the real visitor IP reaches Snow untouched.
If you’re using the Next.js install guide, this replaces the plain src URL. The rewrite and data-api-url together are what make it first-party.
Full mode (script and events)
Full mode routes both the script and events through your Next.js app. Because events go through your server, you must forward the real visitor IP. Otherwise every visitor geolocates to your server’s city.
Why a Route Handler, not rewrites()? Next.js rewrites() proxy requests server-side and drop the original client IP. A Route Handler (or Edge middleware) runs in the request context where you can read X-Forwarded-For or request.ip and re-emit it in the headers you send to Snow.
Add the script rewrite
Keep this in next.config.js. rewrites() is fine for the script because no IP forwarding is needed there.
// next.config.js
module.exports = {
async rewrites() {
return [
{
source: "/sa/sa.js",
destination: "https://api.snowanalytics.app/sa.js",
},
];
},
};Enable Secure proxy
In Settings → Proxy, switch to Full mode and enable Secure proxy. Copy the secret. It’s shown once.
Create the Route Handler
Create this file at app/sa/api/collect/route.ts (App Router). It reads the visitor’s IP from the incoming request and forwards it with the secret.
// app/sa/api/collect/route.ts
import { NextRequest } from "next/server";
const SNOW_API = "https://api.snowanalytics.app/api/collect";
const PROXY_SECRET = process.env.SNOW_PROXY_SECRET;
if (!PROXY_SECRET) {
throw new Error("SNOW_PROXY_SECRET env var is not set");
}
export async function POST(request: NextRequest) {
// Read the real visitor IP. Your reverse proxy (Nginx, Caddy, …) sets it in
// x-forwarded-for or x-real-ip. Locally, fall back to a placeholder.
const ip =
request.headers.get("x-forwarded-for")?.split(",")[0].trim() ||
request.headers.get("x-real-ip") ||
"127.0.0.1";
const headers = new Headers();
headers.set("content-type", request.headers.get("content-type") || "text/plain");
headers.set("user-agent", request.headers.get("user-agent") || "");
headers.set("X-Snow-Client-IP", ip);
headers.set("X-Forwarded-For", ip);
headers.set("X-Snow-Proxy-Secret", PROXY_SECRET);
const response = await fetch(SNOW_API, {
method: "POST",
headers,
body: await request.text(),
});
return new Response(response.body, {
status: response.status,
headers: { "content-type": response.headers.get("content-type") || "text/plain" },
});
}Set the environment variable
Add to .env.local (and to your deployment environment):
SNOW_PROXY_SECRET=your_secret_from_settingsUpdate your embed
<script defer
src="https://yourdomain.com/sa/sa.js"
data-site="YOUR_SITE_ID"
data-api-url="/sa/api/collect"
data-domain="yourdomain.com"></script>data-api-url is now a relative path. The browser posts events to your Next.js Route Handler, which forwards them to Snow with the correct headers.
Verify
Generate some traffic, then check Analytics → Locations. A correct setup shows a realistic country distribution, not everything from one city. See Verify it’s working.