A framework-agnostic client-side error logger for development that persists across Hot Module Replacement (HMR) reloads and streams errors to your terminal.
Never again lose an error message that happens right before a hot reload. This utility captures uncaught exceptions, unhandled promise rejections, and console.error calls, stores them in sessionStorage, and sends them to a server-side endpoint where they can be logged to your terminal.
- HMR Persistence: Errors survive hot reloads and are flushed to the server on the next load.
- Comprehensive Capture: Catches
window.onerror, unhandled promise rejections, andconsole.error. - Detailed Logging: Captures stack traces, line/column numbers, and properly serializes logged objects (handling circular references).
- Broad Framework Support: Provides ready-to-use handlers for Next.js, Nuxt, SvelteKit, Express, Remix, Astro, and more.
- Zero Production Overhead: The entire module is disabled when
process.env.NODE_ENVis not'development'. - Colored Terminal Output: ANSI color codes make errors stand out in your dev console.
- Explicit Configuration: No magic defaults—you must explicitly specify your endpoint.
Install the package as a development dependency:
npm install errplay --save-devSetup is a two-step process: initializing the client-side listener and creating the server-side API endpoint to receive the logs.
Import and call initErrplay in your main client-side entry point with the r99equired endpoint. This should be a file that runs once when your application loads in the browser.
There are two ways to set up errplay in the App Router: a quick start for immediate testing, and a best practice for optimized production apps.
1. Quick Start
This is the fastest way to get started and see errplay in action. It involves turning your root layout into a Client Component.
File: app/layout.js
'use client'; // This makes the root layout a Client Component
import { initErrplay } from 'errplay/client';
// Initialize with your endpoint
initErrplay({ endpoint: '/api/__dev__/errors' });
export default function RootLayout({ children }) {
return (
<html>
<body>{children}</body>
</html>
);
}Note: While this is the quickest method, it turns your entire root layout into a Client Component. For production applications, the Best Practice pattern below is recommended to keep your root layout as a high-performance Server Component.
2. Best Practice for Production Apps
This pattern keeps your root layout.js as a pure Server Component by isolating the dev-only client code into its own component.
First, create a DevTools component:
File: components/DevTools.js
'use client';
import { useEffect } from 'react';
// This component will be a no-op in production
export function DevTools() {
useEffect(() => {
// This check ensures the code is only included in development bundles.
// In production, the entire block is eliminated by dead-code elimination.
if (process.env.NODE_ENV === 'development') {
import('errplay/client').then(module => {
module.initErrplay({ endpoint: '/api/__dev__/errors' });
});
}
}, []);
// This component renders nothing in the DOM
return null;
}Then, add the component to your Root Layout:
Now, your layout.js can remain a clean Server Component.
File: app/layout.js
import { DevTools } from '../components/DevTools';
export const metadata = {
title: 'My Awesome App',
// ...
};
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<DevTools />
</body>
</html>
);
}File: pages/_app.js
import { initErrplay } from 'errplay/client';
initErrplay({ endpoint: '/api/__dev__/errors' });
export default function App({ Component, pageProps }) {
return <Component {...pageProps} />;
}File: src/routes/+layout.js
import { initErrplay } from 'errplay/client';
initErrplay({ endpoint: '/api/__dev__/errors' });File: your main client entry point (e.g., src/main.js)
import { initErrplay } from 'errplay/client';
initErrplay({ endpoint: '/api/__dev__/errors' });Create an API route at the endpoint you specified that uses the appropriate handler from the module.
File: app/api/__dev__/errors/route.js
import { ErrplayHandler } from 'errplay';
export const POST = ErrplayHandler;File: pages/api/__dev__/errors.js
import { ErrplayPagesHandler } from 'errplay';
export default ErrplayPagesHandler;File: server/api/__dev__/errors.post.ts (or .js)
import { ErrplayNuxtHandler } from 'errplay/nuxt';
// The Nuxt handler is imported from a separate entry point
export default defineEventHandler(ErrplayNuxtHandler);File: src/routes/api/__dev__/errors/+server.js
import { ErrplayHandler } from 'errplay';
export const POST = ErrplayHandler;File: app/routes/api/__dev__/errors.ts (or .js)
import { ErrplayHandler } from 'errplay';
// The handler works directly as a Remix action function
export const action = ErrplayHandler;File: src/pages/api/__dev__/errors.ts (or .js)
import { ErrplayHandler } from 'errplay';
export const POST = ErrplayHandler;In your main server file (e.g., server.js):
import express from 'express';
import { ErrplayExpressMiddleware } from 'errplay';
const app = express();
// This is required to parse the JSON body
app.use(express.json());
// Mount the error logging route
app.post('/api/__dev__/errors', ErrplayExpressMiddleware);
// ... rest of your server setup
app.listen(3000, () => console.log('Server is running...'));The endpoint is a required option. Specify it when calling initErrplay:
initErrplay({
endpoint: '/your/custom/error/endpoint'
});The endpoint must match the server-side route you create. If the endpoint is not provided, the function will throw an error.
- Client Initialization: When
initErrplay()is called, it attaches global error listeners to thewindowobject. - Error Capture: Any uncaught exception, unhandled promise rejection, or
console.errorcall is captured with full details. - Dual Action: Errors are both sent immediately to the server via
navigator.sendBeaconand stored insessionStoragefor recovery. - HMR Handling: On page reload (including HMR), the script checks for stored errors and flushes them to the server before listeners are re-attached.
- Terminal Output: The server-side handler logs formatted error details to your console with color coding.
initErrplay() is a complete no-op in production (process.env.NODE_ENV !== 'development'):
- No event listeners are attached.
- No data is stored or transmitted.
- There is zero runtime overhead.
You can safely leave the import and call in your code for all environments.
MIT