Overview

Getting started

Silent error tracker for frontend applications.

Surfacer captures the errors that don't crash your app but silently ruin your users' experience unhandled promise rejections, silent network failures, and console.error calls that disappear into the void.

TypeExample
Unhandled promise rejectionsPromise.reject() without .catch()
Uncaught JS errorsthrow new Error() without try/catch
console.error callsErrors swallowed in catch blocks
Fetch HTTP failuresfetch() response with status outside 2xx
XHR HTTP failuresXMLHttpRequest response with status outside 2xx
console.warn calls (optional)Warnings when captureWarnings is enabled

Get your API key from your Surfacer dashboard, then initialize the SDK once in your app's entry point.

import { init } from 'surfacer';
init('your-api-key');

That's it. Surfacer is now listening for silent errors.

To also capture console.warn calls:

import { init } from 'surfacer';
init('your-api-key', true);

Create a free account at surfacer.dev to:

  • Create a project and get your API key
  • View captured errors grouped by type
  • See stack traces, filenames, line numbers and occurrence counts
  • Mark errors as resolved or ignored

Surfacer is intentionally lightweight under 2kb gzipped. No dependencies.

License: MIT.

Get your API key →

Setup

Installation

Install Surfacer with your preferred package manager.

npm install surfacer
# or
yarn add surfacer
# or
pnpm add surfacer

Framework integration

React

In src/main.tsx, before createRoot:

$ npm install surfacer

In src/main.tsx, before createRoot:

import { init } from 'surfacer';
init('your-api-key');
const root = createRoot(document.getElementById('root')!);
root.render(<App />);

Framework integration

Next.js

Next.js (App Router): create a SurfacerProvider component, then add it to your root layout.tsx.

$ npm install surfacer

// components/SurfacerProvider.tsx

'use client';
import { useEffect } from 'react';
import { init } from 'surfacer';
export default function SurfacerProvider() {
useEffect(() => {
init('your-api-key');
}, []);
return null;
}
// app/layout.tsx
import SurfacerProvider from '@/components/SurfacerProvider';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang='en'>
<body>
<SurfacerProvider />
{children}
</body>
</html>
);
}

Framework integration

Vue

Vue 3 in src/main.ts:

$ npm install surfacer

In src/main.ts:

import { createApp } from 'vue';
import { init } from 'surfacer';
import App from './App.vue';
init('your-api-key');
createApp(App).mount('#app');

Framework integration

Nuxt

Nuxt 3: create a plugin in plugins/surfacer.client.ts:

$ npm install surfacer

Create a plugin in plugins/surfacer.client.ts:

import { init } from 'surfacer';
export default defineNuxtPlugin(() => {
init('your-api-key');
});

Framework integration

Svelte

Svelte / SvelteKit in src/routes/+layout.svelte:

$ npm install surfacer

In src/routes/+layout.svelte:

<script>
import { onMount } from 'svelte';
import { init } from 'surfacer';
onMount(() => {
init('your-api-key');
});
</script>
<slot />

Framework integration

Angular

In src/main.ts:

$ npm install surfacer

In src/main.ts:

import { init } from 'surfacer';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
init('your-api-key');
bootstrapApplication(AppComponent);

Framework integration

Symfony (Stimulus)

Surfacer works natively with Stimulus controllers.

$ npm install surfacer

In assets/app.js:

import { init } from 'surfacer';
init('your-api-key');

No additional configuration is needed.

Framework integration

Alpine.js

Surfacer works natively with Alpine.js.

$ npm install surfacer

Initialize Surfacer before Alpine.start():

import Alpine from 'alpinejs';
import { init } from 'surfacer';
init('your-api-key');
window.Alpine = Alpine;
Alpine.start();

No additional configuration is needed.

Framework integration

Vanilla JS

Vanilla JS / TypeScript:

$ npm install surfacer
import { init } from 'surfacer';
init('your-api-key');

Runtime

How it works

  1. Call init() once in your app's entry point
  2. Surfacer registers global listeners on window and patches console.error
  3. Surfacer also patches fetch and XMLHttpRequest to capture failed HTTP requests
  4. Errors are queued locally and sent in batches every 10 seconds
  5. If your network is down, errors are retried automatically on the next batch
  6. Identical errors are deduplicated using fingerprinting — you see occurrence counts, not noise