Add analytics to a v0 app with its AI agent
undefined
v0 builds Next.js apps and deploys them on Vercel, so analytics goes in two places: a small proxy file that serves the tracker from your own domain, and a script tag in app/layout.tsx. To have v0 do it, connect AgentFirstStack as an MCP server in v0, paste the prompt below, and publish. v0's agent creates the analytics site, writes both files, and after you publish runs verify and the setup check. Keys go in the project's environment variables, never in the code. Other builders and tools: add analytics with an AI agent.
1. Connect AgentFirstStack to v0
In the chat, open the + menu > MCPs and add a server with the URL https://agentfirststack.com/mcp. v0 supports four ways to authenticate an MCP server; two fit:
- OAuth. You sign in with Google, create a project named after your app's domain and choose full access. The project is yours in the AgentFirstStack dashboard straight away.
- Bearer Token. Paste an AgentFirstStack key. Get one from any terminal with
curl -X POST https://agentfirststack.com/v1/signupand keep a copy in your password manager.
v0's docs say "MCP servers only provide tools for v0 to use during generation": the agent uses them while it works, and your deployed app never calls them.
2. Paste the prompt
Add analytics to this app with AgentFirstStack, using the AgentFirstStack MCP server I connected. Read https://agentfirststack.com/llms.txt and https://analytics.agentfirststack.com/llms.txt first. 1. Name the project after the production domain (afs_update_project) if it has no name. 2. Create an analytics site (analytics_create_site) for the production domain: the custom domain, or the app's .vercel.app address if there is none. Get the install instructions (analytics_get_install_instructions, framework nextjs). 3. Serve the tracker from the app's own domain: add proxy.ts (Next.js 16; middleware.ts with a middleware function on older versions) that rewrites /_a/* to https://analytics.agentfirststack.com with the x-afs-proxy-key header read from process.env.AFS_PROXY_KEY, the visitor's IP as X-Forwarded-For and Vercel's x-vercel-ip-* geo headers mapped to x-afs-geo-*. Tell me the value of first_party_proxy.proxy_key so I can add it as the environment variable AFS_PROXY_KEY (Production and Development, never NEXT_PUBLIC_). 4. In app/layout.tsx, add <script async src="/_a/s.js" data-site="SITE_ID" /> inside <head>, so it's in the server-rendered HTML. 5. Create goals for sign-ups, purchases and the main buttons. Send conversions only the server knows about from a Route Handler or Server Action as server events, with the key in the environment variable AGENTFIRSTSTACK_API_KEY. 6. Add an email destination, a weekly report, and alerts for a traffic drop and for no data. 7. Tell me to publish. After I say it's live, run analytics_verify_install, fix what it reports, then run afs_check_setup with services ["analytics"] and work through its next_steps until it says ready. Write the site id and the env var names (never values) in AGENTFIRSTSTACK.md, and tell me what you set up and how you verified it.
Connected with a bearer token instead of OAuth? Add a line asking for a claim link (afs_create_claim_link) so you can see the project in the dashboard.
The proxy file
Ad blockers block requests to analytics.agentfirststack.com. Served from your own domain through proxy.ts, the tracker and its collect endpoint are first-party, so those visitors count. On Next.js 16 the file is proxy.ts with a proxy function; older versions call it middleware.ts with a middleware function. The core of it:
// proxy.ts
import { NextResponse, type NextRequest } from "next/server";
const GEO: Record<string, string> = { country: "x-vercel-ip-country", "region-code": "x-vercel-ip-country-region",
city: "x-vercel-ip-city", timezone: "x-vercel-ip-timezone", lat: "x-vercel-ip-latitude", lon: "x-vercel-ip-longitude" };
export function proxy(request: NextRequest) {
const headers = new Headers(request.headers);
for (const k of [...headers.keys()]) if (k.startsWith("x-afs-")) headers.delete(k); // never pass on visitor-sent values
headers.set("x-afs-proxy-key", process.env.AFS_PROXY_KEY!);
headers.set("x-forwarded-for", (request.headers.get("x-forwarded-for") || "").split(",")[0].trim());
for (const [k, h] of Object.entries(GEO)) {
const v = request.headers.get(h);
if (v) headers.set("x-afs-geo-" + k, v);
}
headers.delete("cookie");
const dest = new URL(request.nextUrl.pathname + request.nextUrl.search, "https://analytics.agentfirststack.com");
return NextResponse.rewrite(dest, { request: { headers } });
}
export const config = { matcher: "/_a/:path*" };
The x-afs-proxy-key header is the reason for AFS_PROXY_KEY: the collector trusts the forwarded visitor IP and Vercel's location headers only from requests that carry the site's key. Without it, every visitor arrives with Vercel's address and merges into one, and verify reports proxy_key. If the app already has a proxy.ts or middleware.ts, the /_a/ branch goes into that file; a project has one. The Next.js guide has a version that also reports AI crawlers.
Environment variables, not code
v0 keeps environment variables in the project settings (Project menu > Settings > Environment Variables), "encrypted and stored securely on Vercel". Two go there: AFS_PROXY_KEY and, if the app sends server events, AGENTFIRSTSTACK_API_KEY. v0's security docs put the rule plainly: "Never expose sensitive tokens with NEXT_PUBLIC_ prefix." The tracker tag needs neither; its site id is public.
Set them for Development as well as Production. v0's docs note that "the v0 preview will only be able to access environment variables that are available to the Development environment", so a key set only for Production makes the proxy fail in the preview and work after publishing, which is confusing to debug.
Publish, then verify
v0 deploys to Vercel when you click Publish, and later edits need Publish Changes; with GitHub connected, v0 opens or reuses a pull request and merges it rather than pushing to main. Then ask the agent to verify, or run the checks yourself:
K=$AGENTFIRSTSTACK_API_KEY
curl -s -X POST https://analytics.agentfirststack.com/v1/sites/$SITE/verify -H "Authorization: Bearer $K"
curl -s "https://agentfirststack.com/v1/setup?services=analytics" -H "Authorization: Bearer $K"
Verify fetches your production homepage, finds the tag, fetches /_a/s.js through your proxy to make sure it serves the tracker, checks whether events arrived with the proxy key, and lists fixes. Open the production URL once in a normal browser first: headless browsers are filtered out unless the visit is marked with ?afsa_debug=1.
Things that trip people up
- Preview URLs. Each branch and the v0 preview get their own URLs. A site accepts events only from its domain and subdomains, so preview traffic is dropped as
hostname_not_allowedand stays out of your numbers. Test on the production URL. next/scriptinstead of a plain tag. If v0 writes<Script strategy="afterInteractive">, tracking works, but the tag is injected after hydration and isn't in the server HTML, so verify can't find it and the domain can't be proven from the homepage. Ask for the plain<script async>in<head>.- No custom domain yet. Create the site for
your-app.vercel.app.vercel.appon its own is refused as a site domain, because it's a shared suffix. - Instructions for later chats. v0's Instructions (+ > Instructions) are saved to your account and apply while checked. A line such as "This app uses AgentFirstStack analytics through proxy.ts; ids are in AGENTFIRSTSTACK.md; run afs_check_setup before changing tracking" keeps later chats from removing the proxy.
FAQ
Can v0 do this without the MCP server?
v0's agent can run shell commands in its sandbox and visit URLs, so it may manage with curl. The MCP server is the documented route for giving it tools, and it keeps the key out of the chat.
Why a proxy and not just the script from analytics.agentfirststack.com?
Ad blockers block requests to that host. Through /_a/ on your own domain, the script and its events are first-party, so visitors with blockers are counted.
Where does the proxy key go?
In the project's environment variables as AFS_PROXY_KEY, for Production and Development. Never in a file v0 commits, and never with a NEXT_PUBLIC_ prefix.
Do I need a router hook for page changes?
No. The tracker counts every History API navigation, which is how the App Router moves between pages. A usePathname effect would double-count.
What does it cost?
The hosted service is free, with fair-use limits such as 10 analytics sites per account. The code is open source (AGPL-3.0).
Related
- AnalyticsAdd analytics with an AI agentThe prompt, the checks, and every tool and platform.
- AnalyticsNext.js analytics in one promptApp Router, Pages Router, proxy.ts and Vercel.
- AnalyticsAdd analytics to a Lovable appindex.html, Cloud secrets and the published URL.
- AnalyticsAdd analytics to a Bolt appindex.html, .env and publishing.
- AnalyticsAdd analytics with Claude CodeOne prompt in the terminal, then questions over MCP.
- All servicesOne MCP server for every serviceAnalytics, tags, mail and replay tools on one key.
- AnalyticsGoogle Analytics MCP comparedRead GA4, or let the agent create the analytics.
- Server-side conversion trackingMeta, Google Ads, TikTok and GA4, counted once.