2026-07-16·Chiheb Nabil

The Next.js Logger Guide: Structured Logging for App Router & API Routes

How to add a real logger to Next.js — API routes, Server Actions, Route Handlers, and client errors. Capture structured logs with context that survives Vercel's 1-hour retention limit.

nextjsloggingvercelobservabilityserver-actions

Your Next.js app is in production. An API route throws a 500. You open the Vercel dashboard, click on Logs, and see — nothing useful. Or worse: the logs from 2 hours ago are gone entirely. You need a Next.js logger that actually works in production: one that captures structured context, survives beyond Vercel's 1-hour limit, and lets you search by route, user, or error.

This guide shows you how to add production-grade logging to any Next.js app in under 5 minutes — App Router, Pages Router, API routes, Server Actions, and client-side errors.

Why console.log isn't enough in Next.js

console.log works in development. In production on Vercel, it has three fatal flaws:

  1. Logs expire fast. Vercel Hobby keeps ~1 hour of runtime logs. Pro keeps more, but it's still temporary. That bug a user reported this morning? Gone by lunch.
  2. No structure. You can't search "show me every error from /api/checkout for user 4829." You're stuck with text grep.
  3. Server Actions give you a digest, not a log. When a Server Action fails, the client sees a generic NEXT_PUBLIC_SERVER_ERROR with a digest hash. No message, no stack trace, no payload.

You need a logger that writes structured JSON to a dedicated store — one that doesn't disappear after an hour.

What a good Next.js logger should do

Requirement Why it matters
Structured JSON Query by route, error code, user ID, or any custom field
Survives Vercel's retention 7–90 days instead of 1 hour
Catches Server Action errors With the action name and payload context
Captures client errors React render crashes, uncaught exceptions
Async-safe Logs flushed before the function freezes (no dropped background logs)
MCP-compatible Let Cursor or Claude read your production logs directly

Setting up a Next.js logger (3 steps)

1. Install the SDK

npm install @flarelog/sdk

One package covers your backend (API routes, Server Actions, Route Handlers) and your client (React error boundary).

2. Add your API key

# .env.local or Vercel project settings
FLARELOG_API_KEY=fl_your_api_key_here

The SDK auto-detects Vercel and attaches vercelRegion, deploymentUrl, and request context automatically.

3. Wrap your API route handler

Pages Router (API route):

// pages/api/checkout.ts
import { flarelog } from "@flarelog/sdk";
import { withFlareLog } from "@flarelog/sdk/next";

const logger = flarelog({ apiKey: process.env.FLARELOG_API_KEY });

export default withFlareLog(logger, async (req, res) => {
  req.logger.info("Checkout started", {
    userId: req.body.userId,
    plan: req.body.plan,
  });

  try {
    const order = await createOrder(req.body);
    req.logger.info("Order created", { orderId: order.id });
    res.status(200).json({ order });
  } catch (err) {
    req.logger.error("Checkout failed", {
      error: err,
      userId: req.body.userId,
    });
    res.status(500).json({ error: "Checkout failed" });
  }
});

App Router (Route Handler):

// app/api/products/route.ts
import { flarelog } from "@flarelog/sdk";

const logger = flarelog({ apiKey: process.env.FLARELOG_API_KEY });

export async function GET(request: Request) {
  const pathname = new URL(request.url).pathname;

  try {
    const products = await getProducts();
    logger.info("Products fetched", { count: products.length, pathname });
    return Response.json(products);
  } catch (err) {
    logger.error("Failed to fetch products", { error: err, pathname });
    return Response.json({ error: "Internal error" }, { status: 500 });
  }
}

That's it. Every log now carries structured context: the route path, HTTP method, status code, and any metadata you attach. And unlike console.log, these logs are searchable for days — not gone in an hour.

Logging in Server Actions

Server Actions are the trickiest part of Next.js logging because failures produce a digest hash, not a real error. Here's how to log them properly:

// app/actions/createPost.ts
"use server";
import { flarelog } from "@flarelog/sdk";

const logger = flarelog({ apiKey: process.env.FLARELOG_API_KEY });

export async function createPost(formData: FormData) {
  const title = formData.get("title") as string;

  try {
    const post = await db.post.create({ data: { title } });
    logger.info("Post created", { postId: post.id, title });
    return { success: true, post };
  } catch (err) {
    logger.error("Server action failed: createPost", {
      error: err,
      title,
      // Include enough context to debug without the client seeing it
      action: "createPost",
    });
    return { success: false, error: "Could not create post" };
  }
}

The key: log the action name ("createPost") and enough payload context that you can debug from the dashboard alone. The client never sees the error details — but your logs do.

Capturing client-side errors

React render crashes and uncaught browser exceptions are invisible to server-side logging. Add an error boundary:

// pages/_app.tsx or app/layout.tsx
import { flarelog } from "@flarelog/sdk";
import { FlareLogErrorBoundary, useFlareLog } from "@flarelog/sdk/react";

const logger = flarelog({
  apiKey: process.env.NEXT_PUBLIC_FLARELOG_API_KEY,
});

export default function App({ Component, pageProps }) {
  return (
    <FlareLogErrorBoundary logger={logger}>
      <Component {...pageProps} />
    </FlareLogErrorBoundary>
  );
}

Now when a component throws during render, the error — with component name, props, and stack trace — lands in the same dashboard as your server logs.

Searching your Next.js logs

Once your logger is running, you can query structured logs instead of grepping text:

  • "Every error from /api/checkout today" → filter by pathname + level: ERROR
  • "Why did user 4829 get a 500?" → filter by userId: 4829
  • "Server Action failures grouped by action" → filter by action field
  • "Slow Route Handlers over 500ms" → filter by duration

This is the difference between a real logger and console.log. You're not scrolling through a firehose — you're querying a database.

Beyond logging: AI-native debugging

If you use Cursor or Claude to edit your Next.js app, connect the FlareLog MCP server and skip the dashboard entirely:

{
  "mcpServers": {
    "flarelog": {
      "url": "https://mcp.flarelog.dev",
      "headers": {
        "Authorization": "Bearer fl_your_api_key"
      }
    }
  }
}

Then ask: "What errors happened in production in the last hour?" — and your AI reads the structured logs directly. No copy-pasting error messages, no switching tabs.

FAQ

Is there a built-in Next.js logger?

No. Next.js uses console.log under the hood, which streams to your hosting provider's runtime logs (Vercel, Netlify, etc.). There's no first-party structured logging. You need a third-party logger like FlareLog, Pino, or Winston — plus a destination to store and search the logs.

What's the best logger for Next.js?

For structured, production-ready logging with long retention and search, a dedicated observability SDK like FlareLog is the simplest option. Pino + a log shipper works but requires more setup. Winston is popular but needs a transport configured for edge runtimes.

How do I log Next.js API routes?

Wrap your handler with a logging middleware (like withFlareLog), or call logger.info() / logger.error() inside your Route Handler. Attach context like pathname, userId, and method so you can search later.

Do Next.js logs work on Vercel free tier?

console.log works but expires in ~1 hour on Vercel Hobby. A dedicated logger like FlareLog stores logs independently of your Vercel plan — so you get 7 days free and 90 days on Pro, regardless of your Vercel tier.

Can I see Server Action errors with full context?

Yes — if you log them. By default, failed Server Actions return a digest hash to the client. But if you logger.error() inside the action with the action name and payload, you get the full error + stack trace in your dashboard.

Summary

Stop relying on console.log and Vercel's expiring runtime logs. A real Next.js logger gives you:

  • Structured JSON you can query by route, user, or error
  • 7–90 day retention instead of 1 hour
  • Server Action errors with full context
  • Client error capture via React error boundaries
  • MCP integration for AI-assisted debugging

Install the SDK and start logging in 3 minutes — free tier includes 10,000 logs/month with 7-day retention.

Related reading:


Written by Chiheb Nabil, founder of FlareLog. Building observability tools for Next.js, Cloudflare Workers, and edge runtimes.

Never miss an invisible crash again

FlareLog catches the errors Cloudflare can't log. Set up the Tail Worker in 5 minutes and see every crash, timeout, and cost spike in real time.

Start logging free →