Structured logging in Next.js without the noise
A complete guide to logging from Next.js App Router, Route Handlers, and server actions with LoggerMan — tokens, metadata, environments, and alerts.
Why structure matters
Unstructured `console.log` output breaks down the moment you add a second service or a second developer. Search becomes guesswork, alerts fire on the wrong strings, and post-mortems turn into archaeology.
LoggerMan treats each event as a **typed log line** with an optional **metadata** object — the same model as our REST ingest API and the @marvink02/loggerman-sdk. If you are new to the product, start with the quick start guide before wiring production traffic.
One project per deployable
A common mistake is sharing one ingest token across staging, preview, and production. That collapses environments in Analytics and makes alert rules meaningless.
Instead:
- Create a project per environment (or pass `environment` from the SDK — see integrations).
- Copy **project ID**, **token**, and **base URL** from Settings → Integration in the dashboard.
- Store them in deployment secrets:
LOGGERMAN_PROJECT_ID=proj_... LOGGERMAN_TOKEN=lg_... LOGGERMAN_BASE_URL=https://your-app.example
Never commit tokens to git. Rotate via API keys if a preview deployment leaks.
Install the SDK
npm install @marvink02/loggerman-sdk
For App Router middleware correlation, import from `@marvink02/loggerman-sdk/next`. Full surface area is documented under Logger SDK.
Log at boundaries, not everywhere
High-signal logging focuses on **edges**:
- **HTTP in / out** — method, path, status, `durationMs`, authenticated user id (hashed if needed).
- **Errors** — one ERROR line with a stable `code` and serialized `err` metadata; avoid duplicate stack traces in three log levels.
- **Background work** — `job.started` / `job.completed` pairs with the same `jobId`.
- **External calls** — provider name, latency, and outcome — not full response bodies (PII risk; see security).
Example: Route Handler
import { createLogger } from "@marvink02/loggerman-sdk";const logger = createLogger({ projectId: process.env.LOGGERMAN_PROJECT_ID!, token: process.env.LOGGERMAN_TOKEN!, baseUrl: process.env.LOGGERMAN_BASE_URL!, source: "api", environment: process.env.VERCEL_ENV ?? "development", });
export async function POST(req: Request) { const started = Date.now(); try { // ... work await logger.info("checkout.completed", { orderId: "ord_1" }); return Response.json({ ok: true }); } catch (err) { await logger.error("checkout.failed", { code: "CHECKOUT_FAILED", err: err instanceof Error ? err.message : String(err), durationMs: Date.now() - started, }); throw err; } } ```
Metadata rules that keep ingest reliable
Our API rejects payloads that violate shape limits (documented in API reference):
- `metadata` must be a **plain JSON object**, not an array or string.
- Keep messages human-readable; put IDs and metrics in metadata.
- Stay under size limits — large blobs belong in object storage, not logs.
Environment tags vs separate projects
Passing `environment: "preview"` lets Analytics compare preview vs production inside one project. Separate projects are better when retention, alerts, or team access must differ. Read projects & settings for the trade-off.
Wire alerts after the first ERROR
Once logs flow, create a spike rule on ERROR rate (alert fatigue guide) and send a test line from Integration. Confirm **one** notification, then tune cooldowns.