This page explains how to send data from your Next.js app to Axiom.
Next.js is a popular open-source JavaScript framework built on top of React, developed by Vercel. It’s used by a wide range of companies and organizations, from startups to large enterprises, due to its performance benefits and developer-friendly features.
To send data from your Next.js app to Axiom, choose one of the following options:
The @axiomhq/nextjs library is currently in public preview. For more information, see Feature states.
The choice between these options depends on your individual requirements:
The two options can collect different event types.
Event type
Axiom Vercel app
next-axiom library
@axiomhq/nextjs library
Application logs
Yes
Yes
Yes
Web Vitals
No
Yes
Yes
HTTP logs
Yes
Soon
Yes
Build logs
Yes
No
No
Tracing
Yes
No
Yes
If you already use Vercel for deployments, the Axiom Vercel app can be easier to integrate into your existing experience.
The cost of these options can differ widely depending on the volume of data you transfer. The Axiom Vercel app depends on Vercel Log Drains, a feature that’s only available on paid plans. For more information, see the blog post on the changes to Vercel Log Drains.
For information on the Axiom Vercel app and migrating from the Vercel app to the next-axiom library, see Axiom Vercel app.
The rest of this page explains how to send data from your Next.js app to Axiom using the next-axiom or the @axiomhq/nextjs library.
The @axiomhq/nextjs library is part of the Axiom JavaScript SDK, an open-source project and welcomes your contributions. For more information, see the GitHub repository.
Create the folder lib/axiom to store configurations for Axiom.
Create a axiom.ts file in the lib/axiom folder with the following content:
lib/axiom/axiom.ts
Copy
Ask AI
import { Axiom } from '@axiomhq/js';const axiomClient = new Axiom({ token: process.env.NEXT_PUBLIC_AXIOM_TOKEN!,});export default axiomClient;
In the lib/axiom folder, create a server.ts file with the following content:
lib/axiom/server.ts
Copy
Ask AI
import axiomClient from '@/lib/axiom/axiom';import { Logger, AxiomJSTransport } from '@axiomhq/logging';import { createAxiomRouteHandler, nextJsFormatters } from '@axiomhq/nextjs';export const logger = new Logger({ transports: [ new AxiomJSTransport({ axiom: axiomClient, dataset: process.env.NEXT_PUBLIC_AXIOM_DATASET! }), ], formatters: nextJsFormatters,});export const withAxiom = createAxiomRouteHandler(logger);
The createAxiomRouteHandler is a builder function that returns a wrapper for your route handlers. The wrapper handles successful responses and errors thrown within the route handler. For more information on the logger, see the @axiomhq/logging library.
In the lib/axiom folder, create a client.ts file with the following content:
Ensure the API token you use on the client side has the appropriate permissions. Axiom recommends you create a client-side token with the only permission to ingest data into a specific dataset.
If you don’t want to expose the token to the client, use the proxy transport to send logs to Axiom.
lib/axiom/client.ts
Copy
Ask AI
'use client';import axiomClient from '@/lib/axiom/axiom';import { Logger, AxiomJSTransport } from '@axiomhq/logging';import { createUseLogger, createWebVitalsComponent } from '@axiomhq/react';import { nextJsFormatters } from '@axiomhq/nextjs/client';export const logger = new Logger({ transports: [ new AxiomJSTransport({ axiom: axiomClient, dataset: process.env.NEXT_PUBLIC_AXIOM_DATASET! }), ], formatters: nextJsFormatters,});const useLogger = createUseLogger(logger);const WebVitals = createWebVitalsComponent(logger);export { useLogger, WebVitals };
For more information on React client side helpers, see React.
To capture errors on Next 15 or later, use the onRequestError option. Create an instrumentation.ts file in the src or root folder of your Next.js app (depending on your configuration) with the following content:
instrumentation.ts
Copy
Ask AI
import { logger } from "@/lib/axiom/server";import { createOnRequestError } from "@axiomhq/nextjs";export const onRequestError = createOnRequestError(logger);
Alternatively, customize the error logging by creating a custom onRequestError function:
Instead of sending logs directly to Axiom, you can send them to a proxy endpoint in your Next.js app. This is useful if you don’t want to expose the Axiom API token to the client or if you want to send the logs from the client to transports on your server.
Create a client.ts file in the lib/axiom folder with the following content:
lib/axiom/client.ts
Copy
Ask AI
'use client';import { Logger, ProxyTransport } from '@axiomhq/logging';import { createUseLogger, createWebVitalsComponent } from '@axiomhq/react';export const logger = new Logger({ transports: [ new ProxyTransport({ url: '/api/axiom', autoFlush: true }), ],});const useLogger = createUseLogger(logger);const WebVitals = createWebVitalsComponent(logger);export { useLogger, WebVitals };
In the /app/api/axiom folder, create a route.ts file with the following content. This example uses /api/axiom as the Axiom proxy path.
/app/api/axiom/route.ts
Copy
Ask AI
import { logger } from "@/lib/axiom/server";import { createProxyRouteHandler } from "@axiomhq/nextjs";export const POST = createProxyRouteHandler(logger);
For more information on React client side helpers, see React.
To customize the reports sent to Axiom, use the onError and onSuccess functions that the createAxiomRouteHandler function accepts in the configuration object.
In the lib/axiom/server.ts file, use the transformRouteHandlerErrorResult and transformRouteHandlerSuccessResult functions to customize the data sent to Axiom by adding fields to the report object:
Changing the transformSuccessResult() or transformErrorResult() functions can change the shape of your data. This can affect dashboards (especially auto-generated dashboards) and other integrations.
Axiom recommends you add fields on top of the ones returned by the default transformSuccessResult() or transformErrorResult() functions, without replacing the default fields.
Alternatively, create your own transformSuccessResult() or transformErrorResult() functions:
Internally, the status code gets captured in the transformErrorResult() function using a getNextErrorStatusCode() function. To compose these functions yourself, create your own getNextErrorStatusCode() function and inject the result into the transformErrorResult() report.
The serverContextFieldsFormatter function included in the nextJsFormatters adds the server execution context to the logs, this is useful to have information about the scope where the logs were generated.
By default, the createAxiomRouteHandler function adds a request_id field to the logs using this server context and the server context fields formatter.
The createAxiomRouteHandler accepts a store field in the configuration object. The store can be a map, an object, or a function that accepts a request and context. It returns a map or an object.
The fields in the store are added to the fields object of the log report. For example, you can use this to add a trace_id field to every log report within the same function execution in the route handler.
Copy
Ask AI
import { Logger, AxiomJSTransport } from '@axiomhq/logging';import { createAxiomRouteHandler, nextJsFormatters } from '@axiomhq/nextjs';import { NextRequest } from 'next/server';import axiomClient from '@/lib/axiom/axiom';export const logger = new Logger({ transports: [ new AxiomJSTransport({ axiom: axiomClient, dataset: process.env.NEXT_PUBLIC_AXIOM_DATASET! }), ], formatters: nextJsFormatters,});export const withAxiom = createAxiomRouteHandler(logger, { store: (req: NextRequest) => { return { request_id: crypto.randomUUID(), trace_id: req.headers.get('x-trace-id'), }; },});
import { runWithServerContext } from '@axiomhq/nextjs';export const middleware = (req: NextRequest) => runWithServerContext({ trace_id: req.headers.get('x-trace-id') }, () => { // trace_id will be added to the log fields logger.info(...transformMiddlewareRequest(request)); // trace_id will also be added to the log fields log.info("Hello from middleware"); event.waitUntil(logger.flush()); return NextResponse.next(); });