Error tracking for Vercel apps: errors, dependencies, and analytics in one
If your Next.js app lives on Vercel, your error tracker should fit the platform: Edge runtime support, source maps from Vercel deploys, instrumentation.ts hooks, and one SDK that also tells you when next or react ships a CVE patch. Here's how to wire it up.
Error tracking for Vercel apps: errors, dependencies, and analytics in one
Vercel changed the shape of frontend deployment in a way that error trackers haven't fully caught up to. Edge runtime functions don't have the Node-shaped global error handlers most SDKs assume. Source maps live in .next/ and get uploaded to Vercel's CDN, not your error vendor's. Builds happen on Vercel's machines, not your CI, which means whatever step uploads source maps has to run inside the Vercel build, not after it. And the new App Router's instrumentation.ts is the canonical hook surface for "I want to be notified about errors at the framework level," but plenty of SDKs still tell you to wrap things in custom error boundaries instead.
If you're shipping a Next.js or Remix app on Vercel, this post is the concrete setup for an error tracker that fits the shape of the platform — including the new bits — without per-event billing and with dependency-CVE alerts thrown in. The platform-fit details are the same ones that apply if you swap the vendor; substitute your tool of choice if you've already picked one.
What "fits Vercel" actually means
Five concrete things make an error tracker work cleanly with a Vercel deployment:
- Edge runtime support. Vercel's Edge functions run in a V8 isolate, not Node.
process.on('uncaughtException')doesn't exist. The SDK has to useaddEventListener('error')andaddEventListener('unhandledrejection')on the global. Some SDKs handle this; others treat it as "out of scope" and you lose Edge errors silently. instrumentation.tshooks. Next.js 13+ ships an opinionated entry point for telemetry:instrumentation.tsat the project root. Itsregister()function is called once at server startup, and itsonRequestError()hook (Next 15+) is called for every server-side error. SDKs that wire into both of these don't need you to wrap your app in a custom error boundary.- Source map upload during the Vercel build. You don't have a CI you control; Vercel builds your app on its machines. The source-map upload step has to be a
next.config.jsplugin or a postbuild step that runs inside Vercel's environment, with the upload token in a Vercel env var. - Release tagging tied to the Vercel deployment. Vercel exposes
VERCEL_GIT_COMMIT_SHAandVERCEL_DEPLOYMENT_IDenv vars. Your release tag should be one of those, automatically, so a stack trace can be tied back to the deployment that introduced it. NEXT_PUBLIC_*discipline for the DSN. The browser SDK needs a public init key. Vercel'sNEXT_PUBLIC_*prefix is the right channel; the server SDK uses a private key, kept un-prefixed.
A vendor that handles all five is one you can install and forget. A vendor that handles three of five is one that will silently drop Edge errors or fail to symbolicate browser stacks for half a year before you notice.
The setup, concretely (Netwarden Apps)
I'll walk through Netwarden Apps because that's the SDK I know and because the post would be fiction if I claimed to know the others' edge cases at the same depth. The pattern transfers to other vendors with minor renames.
Step 1 — install
npm install @netwarden/apps
(Or pnpm add / bun add / yarn add — same package on every registry.)
Step 2 — instrumentation.ts
Create instrumentation.ts at the project root (next to next.config.js):
import * as Netwarden from '@netwarden/apps';
export async function register() {
Netwarden.init({
dsn: process.env.NETWARDEN_DSN!,
release: process.env.VERCEL_GIT_COMMIT_SHA,
environment: process.env.VERCEL_ENV ?? 'development',
});
}
export async function onRequestError(err: unknown, req: { path: string }) {
Netwarden.captureException(err, { tags: { path: req.path } });
}
The register() function fires once at server startup; the onRequestError() hook fires on every unhandled server error in Next 15+. You don't need a custom error boundary for the framework-handled cases.
Step 3 — client-side init
For the App Router, create or extend app/providers.tsx:
'use client';
import { useEffect } from 'react';
import * as Netwarden from '@netwarden/apps';
export function NetwardenProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
Netwarden.init({
dsn: process.env.NEXT_PUBLIC_NETWARDEN_DSN!,
release: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA,
environment: process.env.NEXT_PUBLIC_VERCEL_ENV ?? 'development',
});
}, []);
return <>{children}</>;
}
Then mount it once in app/layout.tsx. The browser SDK installs window.onerror and unhandledrejection listeners; you don't need to call captureException manually for unhandled errors.
Step 4 — Vercel env vars
In your Vercel project's Settings → Environment Variables:
NETWARDEN_DSN— server-side, all environments.NEXT_PUBLIC_NETWARDEN_DSN— same value as above; theNEXT_PUBLIC_prefix exposes it to the browser bundle.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA— Vercel exposesVERCEL_GIT_COMMIT_SHAserver-side automatically; you have to manually re-expose it asNEXT_PUBLIC_*for the browser bundle. Same forVERCEL_ENV.NETWARDEN_UPLOAD_TOKEN— for the source-map upload step (next).
Step 5 — source-map upload during the build
Add the postbuild step in next.config.js:
const { withNetwarden } = require('@netwarden/apps/next');
module.exports = withNetwarden({
// ... your existing Next.js config
}, {
uploadToken: process.env.NETWARDEN_UPLOAD_TOKEN,
release: process.env.VERCEL_GIT_COMMIT_SHA,
silent: true,
});
The plugin uploads .next/static/**/*.map after the build completes, tags them with the release, and deletes them from the deployed bundle so they don't leak to the public CDN. The release tag matches the one your instrumentation.ts sets — the symbolication backend uses that match to resolve a browser stack back to source.
Step 6 — verify
Push to a Vercel preview branch. Visit the deploy URL. Throw something:
// app/test-error/page.tsx
export default function TestErrorPage() {
throw new Error('Hello from Vercel');
}
The error should show up in your Apps dashboard within ~30 seconds, with the stack trace symbolicated back to your source files (not the minified Next.js bundle). If the stack trace shows minified output, source-map upload didn't fire — check the build logs for the @netwarden/apps/next postbuild output.
That's the whole setup. ~10 minutes if you're touching instrumentation.ts for the first time, ~2 minutes if you've done this dance before.
What you get for free, beyond errors
The reason I'd recommend Apps for a Vercel app over a pure error tracker is the same reason I'd recommend it generally: the SDK reads your package-lock.json (or pnpm-lock.yaml / bun.lock) on init and reports the package + version pairs. A daily cron at the platform side cross-references those against OSV.dev's npm advisory feed and pages you when a CVE patch ships for something you're shipping.
For a Next.js app that's the difference between "I find out about the next CVE when the GitHub blog post lands and I check Twitter" and "I find out at 6 AM via push notification with npm install [email protected] in the body." The vulnerability surface for a typical Vercel app is next, react, react-dom, and a long tail of npm packages — the daily cross-reference covers all of it without a separate tool.
The same SDK init also picks up lite analytics — pageviews, uniques, custom events — for the half of the buyer profile that wants "did anyone use my app today" answered without reaching for PostHog or Plausible. You can keep using PostHog for funnels and feature flags if those matter; the analytics in Apps are deliberately the basics.
Edge runtime: the sharp edges
A few things to know if you have Edge functions in your app:
- Console-method patching is limited in V8 isolates. Some SDKs that capture
console.errorcalls implicitly will silently no-op on Edge. The Apps SDK detects the runtime and only installs the listeners that work; you may want to callcaptureExceptionexplicitly inside Edge handlers for non-thrown errors you still want tracked. - No filesystem access. SDKs that try to read source maps from disk at runtime (some old patterns do) will fail on Edge. Source-map upload at build time avoids this — the platform looks them up by release ID, not by reading the deploy.
- Cold starts add latency. The first request after a cold start has to initialize the SDK; Apps' init is sync and ~5ms, but if you're chasing sub-100ms cold starts on a free Hobby plan, it's a number to know.
- Streaming responses don't fire
onRequestErroruntil the stream throws. If you stream a partial response and then hit an error mid-stream, you'll see the partial response on the client and the error in your tracker, but they may not be temporally adjacent. That's a Next behavior, not an SDK behavior.
None of these are blockers; they're the kind of detail that costs you a debugging afternoon if nobody documents them.
What about Sentry?
Sentry is the entrenched option for Vercel apps and it works fine. Two reasons you might be reading this and looking for an alternative:
- The pricing model. Sentry bills per event with a reservoir-and-overage structure. A Reddit-thread traffic spike on a Vercel app can produce a $300+ overage in a week. The full breakdown lives in the Sentry alternatives post.
- The dependency-CVE gap. Sentry tracks errors in your code; it does not page you when
nextships a CVE. That's the wedge Apps was built around.
If neither bothers you, Sentry is a perfectly cromulent choice and I'm not here to talk you out of it. The Sentry comparison page has the side-by-side at-a-glance.
Other Vercel-platform notes
- Multi-environment routing. Use
process.env.VERCEL_ENV(production,preview,development) to route alerts. You probably don't want to be paged for preview-deploy errors; Apps' filter syntax lets you exclude them with one rule. - Preview deployments are ephemeral. Don't tie alert routing rules to preview URLs; tie them to
VERCEL_ENV !== 'production'and you're future-proof. - The
VERCEL_DEPLOYMENT_IDenv var can be useful as a secondary release tag if you have multiple deployments per commit. We default toVERCEL_GIT_COMMIT_SHAbecause it's the one humans recognize.
Other platforms: Railway, Fly.io, Render
The same SDK works the same way on Railway, Fly.io, Render, Cloudflare Pages, and Workers. The Vercel-specifics in this post are the VERCEL_* env var conventions and the source-map upload plugin; everything else (the init shape, instrumentation.ts hook, lockfile cross-reference) is platform-agnostic. If you're on one of those and want a similar walkthrough, the installation docs have per-platform notes.
Getting started
If you want to wire this up, the two-minute version:
npm install @netwarden/appsin your Next.js app.- Sign up at app.netwarden.com — Free tier covers one project.
- Copy the DSN, drop it in Vercel env vars, follow steps 2-5 above.
- Push to a preview deploy.
Total time including signup: about ten minutes for a first-time setup, five if you've done this with another vendor before.
Keep reading
- Sentry alternatives without per-event bills — the pricing-model breakdown if Sentry is what you're leaving.
- Migrating from Sentry to Netwarden in 30 minutes — the SDK swap if you have an existing Sentry setup.
- Dependabot alternative that actually pings you — the dependency-CVE side of the same SDK.
- Apps features page — what's in, what isn't.
- Pricing — fixed monthly, no per-event bills.
Get More Monitoring Insights
Subscribe to our weekly newsletter for monitoring tips and industry insights.
Related Articles
WordPress Monitoring, Honestly: What to Watch and What to Skip
Most WordPress monitoring guides promise the moon — Core Web Vitals, real-user analytics, synthetic browser tests from twenty cities. This one is the honest version: here's what's worth watching, what we actually monitor, and what we don't.
How Netwarden's Security Wedge Works
Most monitoring tools don't surface security signals. Most security tools don't surface monitoring signals. We built one tool that does both — because the people we sell to don't want to pay for two. Here's how the security wedge actually works under the hood.
Migrating from Sentry to Netwarden in 30 minutes
If you've decided to leave Sentry, here's the concrete 30-minute path to swap @sentry/browser for @netwarden/sdk. Most of it is search-and-replace.
Ready for Simple Monitoring?
Stop wrestling with complex monitoring tools. Get started with Netwarden today.