Lightweight Error Monitoring in Next.js Without SaaS Overkill
Part of Building This Site, Making Money From a Small Site and Small Business Websites
By Paul Peery · September 3, 2026 · 5 min read

Heads up: this post contains affiliate links — if you buy through one, I may earn a commission at no extra cost to you. How that works
A silent 500 error in production is worse than a broken build because your deploy dashboard still shows green while real visitors bounce off a blank screen. Most solo developers only learn a route crashed when a prospective customer emails them to say a checkout button did nothing.
The usual advice is to install a heavy application performance monitoring suite with distributed tracing, session replay, and AI telemetry. But if you are running a lean web app or a client site, enterprise APM is pure overkill. You end up with twenty unread Slack alerts a day about browser extension conflicts and a surprise invoice when a scraper hits a non-existent API route.
You can get complete visibility over genuine production crashes in Next.js using built-in App Router error boundaries paired with a capped, free-tier error tracker.
Built-in App Router boundaries catch the crash before your user leaves
Next.js provides native error isolation through nested error.tsx and global-error.tsx files. If a Server Component throws during rendering or an async data fetch fails, Next.js catches that exception at the nearest boundary instead of tearing down your whole page.
At a minimum, every production project needs two files in the app directory:
app/error.tsx: A Client Component that wraps route segments, displays a fallback UI ("Something went wrong"), and offers areset()button so users can retry without hard-refreshing the page.app/global-error.tsx: The top-level safety net. This catches fatal failures in your rootlayout.tsx. Because it replaces the root layout when active, it must define its own<html>and<body>tags.
Here is how simple a standard app/error.tsx should look:
'use client';
import { useEffect } from 'react';
export default function ErrorBoundary({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Log the error to your monitoring service
console.error('Captured route error:', error.message, error.digest);
}, [error]);
return (
<div className="p-8 text-center">
<h2 className="text-lg font-bold">Something went wrong</h2>
<p className="text-sm text-gray-600 mt-2">We logged the issue and are looking into it.</p>
<button
onClick={() => reset()}
className="mt-4 px-4 py-2 bg-black text-white rounded text-sm"
>
Try again
</button>
</div>
);
}
Catching the error in the UI is only half the battle. If nobody logs it externally, that console.error vanishes into browser memory or ephemeral server logs. When designing UI architecture, understanding Server Components vs. Client Components makes it obvious why these boundary components must carry the 'use client' directive.
Connect a zero-cost tracking tier with sensible quotas
For solo builders, Sentry remains the easiest zero-dollar option to set up, provided you deliberately constrain what it records. On its free Developer plan, Sentry typically offers around 5,000 monthly errors, which is plenty for personal apps and small client sites (check current pricing and limits before launching, as quotas change over time).
If you prefer self-hosting or hate third-party scripts, lightweight alternatives like Bugsink or simple structured log drains (like Axiom or Better Stack piped from your hosting provider) offer similar peace of mind. But if you want sourcemap resolution out of the box without maintaining a Docker container, Sentry's automated Next.js SDK handles client, server, and edge runtimes simultaneously.
You can initialize it quickly via the command line:
npx @sentry/wizard@latest -i nextjs
The wizard creates three configuration files: sentry.client.config.ts, sentry.server.config.ts, and sentry.edge.config.ts. It also registers runtime hooks in instrumentation.ts.
Strip the noise before it drains your monthly quota
Out-of-the-box monitoring will burn through your event quota in three days if you do not filter out junk. Client-side errors are notoriously noisy—browser extensions inject bad scripts, crawlers probe random URLs, and mobile Safari occasionally throws hydration mismatches on stale tabs.
To prevent alert fatigue, configure beforeSend inside your sentry.client.config.ts to drop known garbage before it transmits:
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
// Disable heavy tracing to protect performance and quotas
tracesSampleRate: 0.05,
replaysSessionSampleRate: 0,
replaysOnErrorSampleRate: 0,
beforeSend(event, hint) {
const error = hint.originalException;
const message = typeof error === 'string' ? error : error?.message || '';
// Ignore common third-party browser noise
if (
message.includes('ResizeObserver loop limit exceeded') ||
message.includes('Non-Error promise rejection captured') ||
message.includes('chrome-extension://')
) {
return null;
}
return event;
},
});
By turning off heavy performance tracing and session replays, your site stays snappy and you stay far below the free-tier threshold. You only pay attention when your core logic actually breaks.
Server Actions require explicit try-catch reporting
One trap unique to the Next.js App Router involves Server Actions. When a Server Action fails, Next.js obscures the internal error message in production for security reasons and returns a generic hash digest to the browser.
If you handle form submissions—like the patterns I use for contact forms and spam prevention in Next.js—wrap your action internals in a clean helper so fatal crashes get captured with the actual server-side stack trace:
'use server';
import * as Sentry from '@sentry/nextjs';
export async function submitContactMessage(formData: FormData) {
try {
const email = formData.get('email') as string;
// Database or email dispatch logic here
return { success: true };
} catch (err) {
Sentry.captureException(err);
return { success: false, error: 'Unable to process your message right now.' };
}
}
This gives your user a helpful status message while sending the exact line number and payload context straight to your monitoring dashboard.
Set notification rules so you never check a dashboard manually
Logging errors is useless if you have to log into a web console every morning to see if something broke. But routing every single warning to your personal email will train your brain to ignore notifications within forty-eight hours.
Set up these two alert rules in your tracking project:
- First Seen Alert (Immediate Email/Webhook): Trigger a notification only when an issue occurs for the very first time. A brand-new error almost always points to a regression from your latest deploy.
- High-Frequency Threshold (Spike Alert): Trigger an alert only if an existing error happens more than 10 times inside a 5-minute window. This flags database timeouts, third-party API outages, or severe cascading bugs.
Turn off weekly digest emails, marketing summaries, and performance degradation tips. When your phone buzzes, it should mean exactly one thing: production needs a quick fix.
The trade-off: free tiers trade data retention for zero cost
The real trade-off with lightweight setups is data history. Free tiers across almost all hosted error monitors keep events for roughly 30 days before pruning them. If a subtle bug only triggers once every six weeks for an obscure browser version, you will not have months of historical breadcrumbs to compare against.
For solo businesses and micro SaaS projects, that is an acceptable compromise. You do not need six months of cold audit trails—you need to know if a deploy broke checkout twenty seconds ago. Just as I advocate automating pre-publish checks to prevent broken links before deployment, lightweight runtime monitoring is your safety net for the bugs that slip past local testing.
The 15-minute setup checklist
Here is the lean monitoring setup you can ship this afternoon:
- Add
app/error.tsxandapp/global-error.tsxto handle user-facing UI recovery. - Install an error tracker via official Next.js SDK integrations.
- Set
tracesSampleRatelow (0.05 or lower) and disable session replays unless actively debugging a launch. - Filter browser extension noise inside
beforeSend. - Wrap Server Actions in explicit
try/catchblocks withcaptureExceptioncalls. - Configure alerts to trigger only on first seen events and traffic spikes.
Keep reading
All posts
How I Handle Contact Forms and Spam in Next.js Without SaaS Fees
You do not need a monthly SaaS subscription or intrusive CAPTCHAs to accept contact inquiries. Here is how I build spam-resistant forms in Next.js using Server Actions.
August 28, 2026 · 6 min read