Authors
Islam Shehata
Software Engineer
Gabriel de Andrade
Software Engineer
Have you ever found yourself fighting against your logging library instead of having it work for you? We’ve been there too. That’s why we’re thrilled to announce Axiom’s completely reimagined JavaScript logging ecosystem: a suite of libraries designed from the ground up to be flexible, powerful, and framework-agnostic.
Our existing next-axiom
package has served developers well, but we’ve heard your feedback. You wanted more customization options. You wanted to log to multiple destinations simultaneously. You wanted a clearer, more intuitive API that doesn’t rely on environment variable magic.
Here’s the thing: logging shouldn’t be complicated, but it should be powerful. Our new libraries give you both simplicity and flexibility, with an architecture that lets you start with sensible defaults and expand as your needs grow.
All code samples in this blog post are correct at the time of publication but are subject to change in newer versions.
What’s changing?
We’ve introduced three new packages that work together to provide a comprehensive logging solution:
@axiomhq/logging
: A framework-agnostic core library that handles all the fundamentals@axiomhq/react
: React-specific hooks and components@axiomhq/nextjs
: Next.js-specific helpers for middleware, route handlers, and more
This modular approach resolves the biggest pain points we identified in next-axiom
:
- No more environment variable guessing games. Configuration is now explicit and customizable.
- Clear boundaries between framework-specific and framework-agnostic code. No more Vercel/Next.js code scattered throughout.
- Multiple transports support. Send logs to Axiom, the console, or anywhere else—simultaneously.
- Coherent logging across different parts of your application. Whether you’re logging from middleware, route handlers, or React components, the experience is consistent.
The power of transports
One of the most exciting additions is the concept of transports—pluggable destinations for your logs. Want to send logs to both Axiom and your console during development? Just configure two transports. Need different logging behavior in production? Easy.
import { Axiom } from '@axiomhq/js'
import { Logger, AxiomJSTransport, ConsoleTransport } from '@axiomhq/logging';
const axiom = new Axiom({
token: process.env.AXIOM_TOKEN,
});
export const logger = new Logger({
transports: [
new AxiomJSTransport({
axiom: Axiom
dataset: process.env.AXIOM_DATASET,
}),
new ConsoleTransport({ prettyPrint: true }),
],
});
Each transport can have its own configuration, filtering, and formatting options. This gives you real control over your logging pipeline.
A logging flow that works for you
Let’s look at what makes these libraries so powerful in practice.
Logging from React
Now you can log from your React apps directly using our new @axiomhq/react
library made just for React.
-
First, install the relevant libraries:
npm install --save @axiomhq/logging @axiomhq/react
-
Create a
Logger
instance and export the utils. The example below uses theuseLogger
andWebVitals
components.'use client'; import { Logger, AxiomJSTransport } from '@axiomhq/logging'; import { Axiom } from '@axiomhq/js'; import { createUseLogger, createWebVitalsComponent } from '@axiomhq/react'; const axiomClient = new Axiom({ token: process.env.AXIOM_TOKEN!, }); export const logger = new Logger({ transports: [ new AxiomJSTransport({ axiom: axiomClient, dataset: process.env.AXIOM_DATASET!, }), ], }); const useLogger = createUseLogger(logger); const WebVitals = createWebVitalsComponent(logger); export { useLogger, WebVitals };
Send logs from components
To send logs from components, use the useLogger
hook that returns your logger instance.
import { useLogger } from "@/lib/axiom/client";
import { useEffect } from "react";
export default function ClientComponent() {
const log = useLogger();
const handleClick = () => log.info("User logged out");
useEffect(() => {
log.info("User logged in", { userId: 42 });
}, []);
return (
<div>
<h1>Logged in</h1>
<button onClick={handleClick}>Log out</button>
</div>
);
}
Server-side logging in Next.js
Here’s how you can set up a route handler with our new libraries:
-
Create a
lib/axiom
folder to store configurations for Axiom. -
Create an
axiom.ts
file in thelib/axiom
folder with the following content: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 aserver.ts
file with the following content: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);
-
Now you can use the
withAxiom
function exported from the setup file inlib/axiom/server.ts
to wrap your route handlers.import { logger } from "@/lib/axiom/server"; import { withAxiom } from "@/lib/axiom/server"; export const GET = withAxiom(async () => { return new Response("Hello World!"); });
Simple enough, right? Check the docs for more advances use-cases - for example, if you need more control over how errors and successes are logged, while still leveraging our helpers to transform results into well-structured log entries.
Client-side logging in React
On the client side, we’ve created intuitive hooks that make logging from React components a breeze. To get started create a client.ts
file in the lib/axiom
folder, with the following content:
'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 };
The useLogger
hook automatically adds context to your logs, making it easy to trace where events originated.
Middleware logging
Next.js middleware gets its own streamlined API:
import { transformMiddlewareRequest } from '@axiomhq/nextjs';
import { logger } from '@/lib/axiom/server';
export const middleware = (req) => {
logger.info(...transformMiddlewareRequest(req));
event.waitUntil(logger.flush());
return NextResponse.next();
};
Web Vitals
To capture Web Vitals, add the WebVitals
component to the app/layout.tsx
file:
**import { WebVitals } from "@/lib/axiom/client";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<WebVitals />
<body>{children}</body>
</html>
);
}**
Beyond the basics: Advanced use cases
While we’ve made simple logging easier, we’ve also enabled powerful advanced scenarios:
Change the log level from Next.js built-in function errors
Need to adjust log levels based on error codes or other factors? Now you can provide a custom logLevelByStatusCode()
function when logging errors from your route handler:
import { Logger, AxiomJSTransport, LogLevel } from '@axiomhq/logging';
import {
createAxiomRouteHandler,
nextJsFormatters,
transformRouteHandlerErrorResult,
} from '@axiomhq/nextjs';
/* ... your logger setup ... */
const getLogLevelFromStatusCode = (statusCode: number) => {
if (statusCode >= 300 && statusCode < 400) {
return LogLevel.info;
} else if (statusCode >= 400 && statusCode < 500) {
return LogLevel.warn;
}
return LogLevel.error;
};
export const withAxiom = createAxiomRouteHandler(logger, {
onError: (error) => {
if (error.error instanceof Error) {
logger.error(error.error.message, error.error);
}
const [message, report] = transformRouteHandlerErrorResult(error);
report.customField = 'customValue';
report.request.searchParams = error.req.nextUrl.searchParams;
logger.log(getLogLevelFromStatusCode(report.statusCode), message, report);
logger.flush();
}
});
Migrating from next-axiom
If you’re currently using next-axiom
, we’ve designed the migration path to be as smooth as possible. The new libraries maintain the same core logging interface while giving you more options for customization.
Here’s a quick comparison of before and after:
Before (next-axiom):
import { withAxiom } from 'next-axiom';
export const GET = withAxiom(req, res) {
// Your code here
req.log.info('...')
}
After (@axiomhq/nextjs):
import { logger } from "@/lib/axiom/server";
import { withAxiom } from "@/lib/axiom/server";
export const GET = withAxiom(async () => {
logger.info('...')
return new Response("Hello World!");
});
The response from early adopters has been overwhelmingly positive:
The new Axiom logging libraries give me exactly what I need: sensible defaults when I want simplicity, and unlimited flexibility when I need customization.
Getting started
Ready to try it out? Here’s how to get started:
- Install the core package:
npm install @axiomhq/js @axiomhq/logging
- Install framework-specific helpers:
- For React:
npm install @axiomhq/react
- For Next.js:
npm install @axiomhq/nextjs
- For React:
- Set up your configuration files (see examples above)
- Start logging!
For detailed documentation, check out our comprehensive guide to the new logging libraries.
In summary
These new libraries represent a big step forward in how many customers will approach logging. By focusing on flexibility, customization, and framework-agnostic design, we’ve created a solution that grows with your needs—from the simplest “Hello World” app to complex distributed systems.
We can’t wait to see what you build with it. Share your feedback, ask questions, and let us know how these new libraries are working for you!