name: axiom description: Implements structured logging and observability with Axiom for serverless applications. Use when adding logging, tracing, and Web Vitals monitoring to Next.js and Vercel applications.
Axiom
Observability platform for logs, metrics, and traces. Native Vercel integration with structured logging for Next.js applications.
Quick Start
npm install @axiomhq/js @axiomhq/logging @axiomhq/nextjs @axiomhq/react
Next.js Setup (2025 API)
Create Axiom Client
// lib/axiom/client.ts
import { Axiom } from '@axiomhq/js';
export const axiom = new Axiom({
token: process.env.AXIOM_TOKEN!,
orgId: process.env.AXIOM_ORG_ID,
});
Create Logger
// lib/axiom/logger.ts
import { Logger, ConsoleTransport, AxiomJSTransport } from '@axiomhq/logging';
import { nextJsFormatters } from '@axiomhq/nextjs';
import { axiom } from './client';
export const logger = new Logger({
transports: [
new AxiomJSTransport({
axiom,
dataset: process.env.AXIOM_DATASET!,
}),
new ConsoleTransport(),
],
formatters: nextJsFormatters,
});
Create Route Handler
// lib/axiom/route.ts
import { createAxiomRouteHandler } from '@axiomhq/nextjs';
import { axiom } from './client';
import { logger } from './logger';
export const axiomRouteHandler = createAxiomRouteHandler({
axiom,
logger,
dataset: process.env.AXIOM_DATASET!,
});
API Route Handler
// app/api/axiom/route.ts
import { axiomRouteHandler } from '@/lib/axiom/route';
export const POST = axiomRouteHandler;
Logging
Server Components
// app/page.tsx
import { logger } from '@/lib/axiom/logger';
export default async function Page() {
logger.info('Page rendered', { page: 'home' });
try {
const data = await fetchData();
logger.debug('Data fetched', { count: data.length });
return <div>{/* render */}</div>;
} catch (error) {
logger.error('Failed to fetch data', { error });
throw error;
} finally {
await logger.flush();
}
}
API Routes
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { logger } from '@/lib/axiom/logger';
export async function POST(request: NextRequest) {
const body = await request.json();
logger.info('Creating user', {
email: body.email,
source: request.headers.get('referer'),
});
try {
const user = await createUser(body);
logger.info('User created', { userId: user.id });
return NextResponse.json(user);
} catch (error) {
logger.error('User creation failed', {
error: error instanceof Error ? error.message : 'Unknown error',
email: body.email,
});
return NextResponse.json({ error: 'Failed' }, { status: 500 });
} finally {
await logger.flush();
}
}
Middleware
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { logger } from '@/lib/axiom/logger';
export async function middleware(request: NextRequest) {
const start = Date.now();
logger.info('Request started', {
path: request.nextUrl.pathname,
method: request.method,
});
const response = NextResponse.next();
logger.info('Request completed', {
path: request.nextUrl.pathname,
duration: Date.now() - start,
});
await logger.flush();
return response;
}
Client-Side Logging
// app/providers.tsx
'use client';
import { AxiomWebVitals } from '@axiomhq/react';
export function Providers({ children }: { children: React.ReactNode }) {
return (
<>
<AxiomWebVitals />
{children}
</>
);
}
Client Logger (Proxy Method)
Send logs through your API to avoid exposing tokens:
// lib/axiom/client-logger.ts
class ClientLogger {
private queue: Array<{ level: string; message: string; data?: object }> = [];
log(level: string, message: string, data?: object) {
this.queue.push({ level, message, data });
}
info(message: string, data?: object) {
this.log('info', message, data);
}
error(message: string, data?: object) {
this.log('error', message, data);
}
async flush() {
if (this.queue.length === 0) return;
const logs = [...this.queue];
this.queue = [];
await fetch('/api/axiom', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ logs }),
});
}
}
export const clientLogger = new ClientLogger();
Client Component Usage
'use client';
import { useEffect } from 'react';
import { clientLogger } from '@/lib/axiom/client-logger';
export function TrackableButton() {
const handleClick = () => {
clientLogger.info('Button clicked', { buttonId: 'cta' });
clientLogger.flush();
};
useEffect(() => {
clientLogger.info('Component mounted');
return () => {
clientLogger.info('Component unmounted');
clientLogger.flush();
};
}, []);
return <button onClick={handleClick}>Click me</button>;
}
Web Vitals
Automatic Core Web Vitals tracking:
// app/layout.tsx
import { AxiomWebVitals } from '@axiomhq/react';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<AxiomWebVitals />
{children}
</body>
</html>
);
}
Legacy API (next-axiom)
The older next-axiom package is still supported:
npm install next-axiom
Setup
// next.config.js
const { withAxiom } = require('next-axiom');
module.exports = withAxiom({
// your next config
});
Server Component Logging
import { Logger } from 'next-axiom';
export default async function Page() {
const log = new Logger();
log.info('Page rendered');
await log.flush();
return <div>Content</div>;
}
Client Component Logging
'use client';
import { useLogger } from 'next-axiom';
export function ClientComponent() {
const log = useLogger();
const handleClick = () => {
log.info('Button clicked');
};
return <button onClick={handleClick}>Click</button>;
}
Structured Logging Patterns
Request Context
function createRequestLogger(request: NextRequest) {
return {
info: (message: string, data?: object) => {
logger.info(message, {
...data,
requestId: request.headers.get('x-request-id'),
path: request.nextUrl.pathname,
method: request.method,
userAgent: request.headers.get('user-agent'),
});
},
error: (message: string, data?: object) => {
logger.error(message, {
...data,
requestId: request.headers.get('x-request-id'),
path: request.nextUrl.pathname,
});
},
};
}
Timing
async function withTiming<T>(
name: string,
fn: () => Promise<T>
): Promise<T> {
const start = Date.now();
try {
const result = await fn();
logger.info(`${name} completed`, {
duration: Date.now() - start,
success: true,
});
return result;
} catch (error) {
logger.error(`${name} failed`, {
duration: Date.now() - start,
error: error instanceof Error ? error.message : 'Unknown',
});
throw error;
}
}
// Usage
const users = await withTiming('fetchUsers', () => db.user.findMany());
Error Logging
function logError(error: unknown, context?: object) {
if (error instanceof Error) {
logger.error(error.message, {
...context,
name: error.name,
stack: error.stack,
cause: error.cause,
});
} else {
logger.error('Unknown error', {
...context,
error: String(error),
});
}
}
Vercel Integration
Enable the Axiom integration in Vercel for automatic log drains:
- Go to Vercel Dashboard > Integrations
- Install Axiom integration
- Connect your Axiom organization
- Logs from
console.log,console.error, etc. are automatically sent
Environment Variables
AXIOM_TOKEN=xaat-xxxxxxxx
AXIOM_ORG_ID=your-org-id
AXIOM_DATASET=your-dataset
# For next-axiom
NEXT_PUBLIC_AXIOM_INGEST_ENDPOINT=https://api.axiom.co/v1/datasets/your-dataset/ingest
Query Examples (APL)
// Recent errors
dataset
| where level == "error"
| order by _time desc
| limit 100
// Request latency by path
dataset
| where message == "Request completed"
| summarize avg(duration), p95(duration), count() by path
| order by count_ desc
// Errors by type
dataset
| where level == "error"
| summarize count() by ['error.name']
| order by count_ desc
Best Practices
- Flush before returning - Always call flush() in server components
- Use structured data - Pass objects, not strings
- Add request context - Include requestId, path, method
- Log at appropriate levels - debug, info, warn, error
- Proxy client logs - Don't expose tokens to browser
- Use Vercel integration - For automatic console.log capture
- Add timing - Track duration for performance monitoring