May 26, 2025

#product, #engineering

Axiom’s new JS logging libraries: Flexible, powerful, framework-agnostic

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:

  1. No more environment variable guessing games. Configuration is now explicit and customizable.
  2. Clear boundaries between framework-specific and framework-agnostic code. No more Vercel/Next.js code scattered throughout.
  3. Multiple transports support. Send logs to Axiom, the console, or anywhere else—simultaneously.
  4. 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.

  1. First, install the relevant libraries:

    npm install --save @axiomhq/logging @axiomhq/react
  2. Create a Logger instance and export the utils. The example below uses the useLogger and WebVitals 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:

  1. Create a lib/axiom folder to store configurations for Axiom.

  2. Create an axiom.ts file in the lib/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;
  3. In the lib/axiom folder, create a server.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);
  4. Now you can use the withAxiom function exported from the setup file in lib/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:

  1. Install the core package: npm install @axiomhq/js @axiomhq/logging
  2. Install framework-specific helpers:
    • For React: npm install @axiomhq/react
    • For Next.js: npm install @axiomhq/nextjs
  3. Set up your configuration files (see examples above)
  4. 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!

Share
Get started with Axiom

Learn how to start ingesting, streaming, and querying data into Axiom in less than 10 minutes.