Next.js (App Router)
Two files. That's it — request logging and the AEO query endpoint are live.
Install
npm install apptvtywithApptvty()
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.
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:
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.ico | Browser favicon |
| .svg .png .jpg .gif .webp .ico | Image assets |
| .woff .woff2 .ttf .css .js.map | Fonts, 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).
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
// 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>;