The SDK is framework-agnostic and touches the DOM directly, so it works anywhere. Two rules cover every framework:
Initialize only in the browser. The SDK needs
window, so it must not run during server-side rendering.Initialize once. Call
destroy()on teardown if your root can unmount and remount.
React
Use @bugdump/react. Install it with the SDK and wrap your app in its provider once, near the root:
npm install @bugdump/react @bugdump/sdk
import { BugdumpProvider } from '@bugdump/react';
<BugdumpProvider config={{ apiKey: import.meta.env.VITE_BUGDUMP_KEY }}>
<App />
</BugdumpProvider>;
The provider starts the widget after the first render and destroys it on unmount, so server rendering and React Strict Mode need nothing from you. useBugdump() gives your components the SDK, and BugdumpErrorBoundary sends the errors it catches; Adding BugDump to a React or Next.js app covers both.
Next.js (App Router)
The provider is a client component, so it goes straight into app/layout.tsx:
import { BugdumpProvider } from '@bugdump/react';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<BugdumpProvider config={{ apiKey: process.env.NEXT_PUBLIC_BUGDUMP_KEY! }}>{children}</BugdumpProvider>
</body>
</html>
);
}
The layout runs on the server, so any variable name works here; inside a 'use client' file the key needs the NEXT_PUBLIC_ prefix. A config with functions or regular expressions cannot come from a Server Component; the React article shows the 'use client' file for that, and what to put in error.tsx and global-error.tsx.
The script tag also works in Next.js: put it in your root layout via next/script with the afterInteractive strategy, which loads it without blocking hydration. Use the tag or the package, not both, or the page gets two widgets.
Vue 3
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue';
import { Bugdump } from '@bugdump/sdk';
let bugdump: ReturnType<typeof Bugdump.init> | null = null;
onMounted(() => {
bugdump = Bugdump.init({ apiKey: import.meta.env.VITE_BUGDUMP_KEY });
});
onUnmounted(() => {
bugdump?.destroy();
});
</script>
For Nuxt, put this in a client-only plugin (~/plugins/bugdump.client.ts) — the .client suffix keeps it out of the server bundle.
Svelte
<script lang="ts">
import { onMount } from 'svelte';
import { Bugdump } from '@bugdump/sdk';
onMount(() => {
const bugdump = Bugdump.init({ apiKey: import.meta.env.VITE_BUGDUMP_KEY });
return () => bugdump.destroy();
});
</script>
onMount is browser-only, and returning a function from it registers cleanup — so SvelteKit SSR is handled.
Angular
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Bugdump } from '@bugdump/sdk';
@Component({ selector: 'app-root', template: '<router-outlet />' })
export class AppComponent implements OnInit, OnDestroy {
private bugdump: ReturnType<typeof Bugdump.init> | null = null;
ngOnInit() {
this.bugdump = Bugdump.init({ apiKey: environment.bugdumpKey });
}
ngOnDestroy() {
this.bugdump?.destroy();
}
}
With Angular Universal, guard on isPlatformBrowser before initializing.
Plain HTML
Nothing to manage:
<script src="https://bugdump.com/sdk/latest.js" async data-api-key="your-api-key"></script>
async keeps the SDK off your page's critical path — it downloads alongside your own assets instead of blocking parsing and DOMContentLoaded.
A note on bundle size
Installing through npm bundles the SDK into your own build, which includes the screenshot and session replay libraries. The script tag build splits those into chunks it fetches only when they are needed: the entry is about 32 KB gzipped, the session replay recorder follows once the widget starts unless you turn replay off, and the screenshot library loads only for a DOM screenshot.
If bundle size is a concern in a framework app, the script tag is the lighter option — and it works in every framework on this page.
Identifying users after login
The most valuable addition in any framework — call identify() once your user is authenticated:
bugdump.identify({
id: user.id,
email: user.email,
name: user.name,
});
And reset() on logout, so the next person is not reported as the previous one.
Passing your app's language
The widget picks its language from <html lang> and the browser on its own. If your app has a language setting of its own, pass it at init, so the widget matches the app even when the two disagree:
Bugdump.init({
apiKey: import.meta.env.VITE_BUGDUMP_KEY,
locale: i18n.language, // 'fr', 'pt-BR', ...
});
The language is read once. If users switch language without a page reload, destroy and re-initialize the widget when it changes; Translating and rebranding the widget shows how. With @bugdump/react, change the provider's locale instead and it restarts the widget for you.
SPA routing
The widget persists across client-side navigation automatically — initialize once at the root, not per route. Navigation is recorded in the actions log, so a report shows which routes the user passed through.
Common problems
"window is not defined" at build time. The SDK is being imported at module scope in server-rendered code. Move init() into a browser-only lifecycle hook.
Two widgets appear. Two copies of the SDK are running, usually the script tag and the npm package on one page. Keep one.
The widget vanishes after navigation. It was initialized inside a route component that unmounted. Move it to the root.
See also
Quickstart
SDK API reference
Sending reports from your own form — React and Angular examples of a custom report dialog