Docs/SDK/Next.js

Next.js (App Router)

Two files. That's it — request logging and the AEO query endpoint are live.

Install

bash
npm install apptvty

withApptvty()

Wraps your middleware.ts file with Apptvty request logging. Every request that passes through Next.js middleware is captured, its crawler type detected, and a log entry queued — without blocking the response.

middleware.ts
import { withApptvty } from 'apptvty/nextjs';

export default withApptvty({
  apiKey: process.env.APPTVTY_API_KEY!,
  siteId: process.env.APPTVTY_SITE_ID!,
});

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};

If you already have a middleware function, pass it as the second argument — Apptvty wraps it:

middleware.ts
import { withApptvty } from 'apptvty/nextjs';
import { type NextRequest, NextResponse } from 'next/server';

async function myMiddleware(req: NextRequest) {
  // your existing auth / redirect logic
  return NextResponse.next();
}

export default withApptvty(
  { apiKey: process.env.APPTVTY_API_KEY!, siteId: process.env.APPTVTY_SITE_ID! },
  myMiddleware,
);

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};

Application errors propagate normally

If your wrapped middleware throws, the error is re-thrown as-is. Apptvty never swallows application exceptions — only logging failures are suppressed.

Paths never logged

These paths are automatically excluded from logging:

/_next/Next.js build artifacts and HMR
/api/_*Internal Next.js API routes
/favicon.icoBrowser favicon
.svg .png .jpg .gif .webp .icoImage assets
.woff .woff2 .ttf .css .js.mapFonts, stylesheets, source maps

createNextjsQueryHandler()

Creates a Next.js App Router GET handler for the AEO endpoint. Place it at app/query/route.ts (or any path you prefer — update queryPath in your config to match).

app/query/route.ts
import { createNextjsQueryHandler } from 'apptvty/nextjs';

export const GET = createNextjsQueryHandler({
  apiKey: process.env.APPTVTY_API_KEY!,
  siteId: process.env.APPTVTY_SITE_ID!,
});

When an agent calls GET /query (no ?q=), it receives a self-describing discovery JSON so it knows how to use the endpoint. When called with ?q=your+question, it returns a RAG-powered answer with sources. See full response shapes →

Singleton behavior

Both withApptvty and createNextjsQueryHandler share a single ApptvtyClient and RequestLogger per apiKey. Calling both functions with the same config in the same process shares one log buffer — no duplicated flushes, no extra connections.

Function signatures

typescript
// apptvty/nextjs
import type { NextRequest, NextResponse } from 'next/server';

type NextMiddleware = (
  request: NextRequest,
) => Response | NextResponse | Promise<Response | NextResponse>;

function withApptvty(
  config: ApptvtyConfig,
  next?: NextMiddleware,
): NextMiddleware;

function createNextjsQueryHandler(
  config: ApptvtyConfig,
): (request: NextRequest) => Promise<NextResponse>;