---
title: Track Claude Code, Codex, and Cursor Requests in Next.js | Apostl
description: Add server-side agent traffic analytics to a Next.js App Router route, verify delivery, and classify Claude Code, Codex, and Cursor request evidence without fake precision.
canonical: https://apostl.dev/blog/track-claude-code-codex-cursor-nextjs
eyebrow: Next.js agent traffic tutorial
schema_type: BlogPosting
date_published: 2026-08-28
date_modified: 2026-08-28
primary_cta_label: Add Pulse to Next.js
primary_cta_href: /pulse
final_cta_title: See which public routes agents actually request.
final_cta_body: Add Pulse to a real server route, prove the first event, and keep weak identities in the unknown bucket.
final_cta_label: Explore Apostl Pulse
final_cta_href: /pulse
image: /assets/blog/track-claude-code-codex-cursor-nextjs.webp
image_alt: Three coding workstations sending public requests into one Next.js server
visual_kicker: Classification ladder
visual_title: Record the request first, then bound the label
visual_left_title: Evidence the server receives
visual_left_items: Full User-Agent when present | Public path and response | Trusted proxy-derived address
visual_axis: Heuristic only
visual_right_title: Defensible report
visual_right_items: Supported agent evidence | Likely machine activity | Unknown remains visible
visual_caption: A coding tool may fetch through curl, a search provider, or a browser. The public server does not see the private prompt or product UI upstream.
---
# Track Claude Code, Codex, and Cursor Requests in Next.js

You can track public requests that may come from workflows involving Claude Code, Codex, Cursor, and other coding agents. You usually cannot prove the exact product or model behind every request.

That distinction should shape the implementation. A Next.js server can record the method, public path, response, request headers, and trusted network evidence it receives. It cannot read the private prompt, tool selection, or product UI that caused the request. Some agents fetch through a search provider. Others run a generic command such as `curl`, and network access may be disabled entirely.

This tutorial uses the current public [Apostl Pulse SDK](https://github.com/apostl-dev/pulse-sdk) App Router quickstart. The code is short. The useful part is building an evidence model that retains `unknown` instead of turning a weak User-Agent into a confident product badge.

## Define what “track a coding agent” means {#tracking-boundary}

At the server boundary, tracking means observing an eligible request and estimating whether it belongs to machine activity. It does not mean identifying a developer, employer, exact model, or task.

The limitation comes from how coding agents reach the web. [Claude Code's CLI](https://docs.anthropic.com/en/docs/claude-code/cli-usage) can run permitted shell tools. [Codex cloud internet access](https://developers.openai.com/codex/cloud/agent-internet) is disabled by default and can be enabled with domain and HTTP-method restrictions. The same OpenAI documentation includes a security example where a Codex shell command reaches a destination with `User-Agent: curl/8.5.0`.

[Cursor Agent](https://cursor.com/docs/agent/overview) has a web-search tool, but its public documentation does not define a stable destination-facing User-Agent. A request associated with a Cursor workflow therefore need not contain the word “Cursor.” This is an architectural limitation, not a claim about every Cursor request.

Use three reporting buckets: supported agent evidence, likely machine traffic, and `unknown`. Exact product labels should appear only when the request carries stable, documented evidence. Product names in a dashboard are classifications, not identity documents.

## Put collection on a real Next.js server route {#server-boundary}

Browser analytics starts after client JavaScript runs. A coding agent may fetch Markdown, `llms.txt`, an OpenAPI document, or an API endpoint without executing any page script. Collection therefore belongs in the server runtime that handles the request.

Next.js App Router [Route Handlers are public HTTP endpoints](https://nextjs.org/docs/app/guides/backend-for-frontend) built on the Web `Request` and `Response` APIs. That makes a real Route Handler a clean place to observe a public agent-facing surface. A static export has no request-time server, so this pattern requires a deployment mode where Next.js receives the request.

Next.js 16 renamed Middleware to [Proxy](https://nextjs.org/docs/app/getting-started/proxy). The current Pulse README does not ask you to invent a global Proxy integration; it provides a confirmed `withPulse()` wrapper for a Route Handler. Use that published contract first. It is easier to verify and keeps the measured surface explicit.

## Install Pulse and keep its key server-only {#install}

Install the package:

```sh
npm install @apostl-dev/pulse-sdk
```

Set both variables in the deployed server runtime:

```sh
APOSTL_PULSE_ENDPOINT=https://ingest.apostl.dev
APOSTL_PULSE_API_KEY=your_server_key
```

Keep the key outside `NEXT_PUBLIC_`, public diagnostics, rendered HTML, logs, and Client Components. The [Pulse README](https://github.com/apostl-dev/pulse-sdk) makes the same boundary explicit because `createPulse()` reads the server environment directly.

Create `lib/pulse.ts`:

```ts
import 'server-only';
import { createPulse } from '@apostl-dev/pulse-sdk';

export const pulse = createPulse();
```

The `server-only` import gives Next.js a build-time guard against accidental client use. The API key still needs ordinary secret management in your deployment platform.

## Wrap the public `llms.txt` Route Handler {#route-handler}

Create or update `app/llms.txt/route.ts` with the current SDK quickstart:

```ts
import { withPulse } from '@apostl-dev/pulse-sdk/next';
import { pulse } from '../../lib/pulse';

export const GET = withPulse(
  pulse,
  async () => new Response('# Agent docs\n', {
    headers: { 'content-type': 'text/markdown; charset=utf-8' },
  }),
  () => ({ surface: 'llms', surfaceName: 'llms-index' }),
);
```

Wrap the route that actually serves your content. A fake `llms.txt` added solely to make an analytics chart move gives agents nothing useful. The response should point to accurate, current resources, and the deployed public URL should return the expected content type.

The wrapper observes the response after the handler runs. Pulse treats an exact `/llms.txt` visit as immediate evidence for an estimated agent journey. The same public SDK pattern can be applied to other supported Route Handlers, but each surface should have a real purpose and a stable name.

## Understand what Pulse records and excludes {#data-contract}

Pulse records the canonical origin and path for eligible public traffic. Query parameters, fragments, request bodies, cookies, and authorization headers are not sent. The SDK sends trusted client IP and full User-Agent data because those signals participate in journey grouping and classification.

Eligible public health and API `GET` and `HEAD` responses are included. Auth and account routes, assets, mutations, and 5xx responses are excluded. Most `/api/*` routes are ignored unless their public prefix is configured; `/api/mcp` and `/api/turnstile-config` are documented safe defaults.

This boundary keeps the dataset focused on public content demand. It also means Pulse is not a replacement for your access logs, error tracker, security telemetry, or authenticated product analytics. If a route returns 500, diagnose it in operational logs. If a signed-in account completes an integration, record that outcome in the product.

Proxy headers need the same care. Use `cf-connecting-ip` only when the origin accepts traffic through Cloudflare, and configure trust for the proxy or CDN you actually control. Trusting an arbitrary forwarded header lets a client manufacture the network signal used for grouping.

## Read classification as evidence, not attribution {#classification}

Pulse groups eligible requests with the same project, trusted IP, and full User-Agent until a 30-minute inactivity window expires. An exact `/llms.txt` request qualifies immediately. A generic non-browser client can qualify after it requests two distinct machine-readable surfaces within ten minutes, such as `openapi.json` and a Markdown page.

This catches a useful class of tool-driven research without requiring the client to advertise an agent name. It also explains why one request should not automatically become “Codex visited pricing.” The official Codex security example reaches the destination through `curl`. Claude Code can run shell tools, while Cursor exposes web search without documenting a stable destination-facing identifier. The server sees the delivered request, not the product UI upstream.

Keep raw request count, estimated journey count, page demand, and explicit product labels separate. A request can support a page-demand conclusion even when the source product stays `unknown`. That row is still useful: it tells you a machine-shaped client requested a public resource.

## Prove the first event before reading the dashboard {#verification}

A successful deployment is not proof that telemetry arrived. The SDK README provides a bounded verification chain:

- Start the production build with both server environment variables present.
- Request the real public `/llms.txt` route with the deployment's trusted client-IP header and a full User-Agent.
- Flush once through a temporary private test hook or graceful shutdown.
- Check `pulse.diagnostics()` for `configured=true`, `accepted>=1`, `sent>=1`, `droppedDelivery=0`, and `lastError=null`.

`diagnostics()` returns counters, not the API key, captured headers, IP addresses, or request URLs. Keep the diagnostics route protected and temporary because the counters are operational metadata. Remove or disable it after the test.

Record the deployed origin, path, request time, response status, and diagnostics result together. If the dashboard is empty later, that proof separates an ingestion problem from a filter or reporting-window problem.

## Turn page demand into one concrete change {#review}

Review the public routes with the most estimated agent activity and compare them with the resources agents need to complete common developer tasks. Check whether each route returns useful content without client-only rendering, whether links resolve, and whether the machine-readable version matches the human page.

Choose one change per review: repair a broken schema link, add a missing Markdown representation, clarify an authentication example, or expose a stable `llms.txt` index. Watch the same routes after the change. A request shift is traffic evidence; it is not proof of task completion or conversion.

[Explore Apostl Pulse](/pulse) to add server-side agent traffic analytics to Next.js. The useful outcome is a defensible map of public machine demand, including the requests that remain `unknown`, not a chart that pretends every generic client announced its name.
