Error tracking for Next.js — server and browser, one SDK

Catch what App Router hides: errors thrown in server actions, route handlers, RSC and SSR renders never reach a global handler. Marsad captures those through Next's own error hook, alongside browser errors, traces and Web Vitals.

Install and initialize

Two touch points: re-export Next's instrumentation hooks for the server, and call initBrowser() once from a client component in your root layout. The key is read from NEXT_PUBLIC_MARSAD_KEY, so you never pass it in code.

npm install @marsadco/sdk
instrumentation.ts (server)
ts
export { register, onRequestError } from "@marsadco/sdk/nextjs";
app/marsad-init.tsx (client)
tsx
"use client";
import { useEffect } from "react";
import { initBrowser } from "@marsadco/sdk/nextjs";

export function MarsadInit() {
  useEffect(() => initBrowser(), []);
  return null;
}

Render <MarsadInit /> once inside app/layout.tsx. That is the whole setup.

The errors App Router usually swallows

A server action that throws does not reach window.onerror, and it does not reach a browser SDK at all. Next surfaces these through onRequestError, which is why re-exporting it matters more than any other line on this page: it is what turns a silent 500 into a grouped issue with a stack trace.

  • Server actions — including ones that fail during a form submission.
  • Route handlers in app/api, with the request context attached.
  • React Server Component render errors.
  • SSR render failures, before any client JS has run.

Prefer to wrap one thing rather than instrument globally? withMarsad(action) and wrapRouteHandler(handler) capture, flush and re-throw, so behaviour is unchanged and the error still propagates to your own handling.

Readable stacks from a minified build

A production Next.js stack trace is minified chunk names and column offsets. Marsad matches each build to its source map by debug ID, so frames resolve to your real files and line numbers with no release bookkeeping on your side — no version strings to keep in sync, no upload step to remember on every deploy.

next.config.mjs
bash
// emit browser source maps so frames can be resolved
export default { productionBrowserSourceMaps: true };

What you get

  • Server-side errors from server actions, route handlers, RSC and SSR via onRequestError.
  • Browser errors and unhandled promise rejections, grouped into issues by fingerprint.
  • Source-mapped stack traces matched by debug ID, with no manual release step.
  • Every fetch and XHR logged with method, status and duration, secrets scrubbed before they leave the browser.
  • Performance traces: pageloads, navigations, requests and assets as one waterfall per page, with traceparent carried into your backend.
  • Web Vitals — LCP, CLS, INP, FCP and TTFB — captured automatically and shown as p75.
  • Navigation, click and console breadcrumbs, with no wiring.
  • Cookieless page analytics on the same events, so traffic and errors share one dashboard.

Why not just an error tracker

When a Next.js page is slow, the useful question is which request inside the pageload was slow — not whether an exception was thrown. Because the same SDK records the trace, the requests and the vitals, an issue arrives with the requests that preceded it and the trace it happened inside. That is the part a standalone error tracker cannot give you, and the reason this is one install rather than three.

The same package instruments Expo and React Native, and adds cookieless analytics with no consent banner. Free plan includes 5,000 errors and 10,000 page views a month with no credit card; paid plans are flat per organization from $9/mo, never per seat. There is also a direct comparison with Sentry if you are weighing the two.

Start watching your apps in minutes

Install the SDK, throw a test error from a server action, and watch it arrive with a readable stack. Free plan, no credit card.