Skip to content

Documentation

Framework guides

Where to initialize Wutbug in Next.js, React, Vue, Nuxt, SvelteKit, Angular and server-rendered sites.

The rule is the same in every framework: call wutbug.init() once, in the browser, as early as possible. Never during server rendering: init() throws an error if there is no window.

In the examples, the key and endpoint come from public environment variables. Use your framework's convention (NEXT_PUBLIC_*, VITE_*, PUBLIC_*, NUXT_PUBLIC_*).

Next.js (App Router)

Create a client component that initializes in useEffect and mount it in the root layout.

app/wutbug.tsx
'use client';

import { useEffect } from 'react';
import wutbug from '@wutbug/sdk';

export function Wutbug() {
  useEffect(() => {
    wutbug.init({
      projectKey: process.env.NEXT_PUBLIC_WUTBUG_KEY!,
      endpoint: process.env.NEXT_PUBLIC_WUTBUG_ENDPOINT,
      release: process.env.NEXT_PUBLIC_RELEASE, // optional
    });
    return () => wutbug.destroy();
  }, []);
  return null;
}
app/layout.tsx
import { Wutbug } from './wutbug';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Wutbug />
      </body>
    </html>
  );
}

The return () => wutbug.destroy() prevents duplicate instances with React's double mount in development. Either way, calling init() again replaces the previous instance.

To identify the user, call wutbug.setUser() from the client component that already knows the session:

TSX
'use client';
import { useEffect } from 'react';
import wutbug from '@wutbug/sdk';

export function IdentifyUser({ user }: { user: { id: string; email: string; name: string } | null }) {
  useEffect(() => {
    wutbug.setUser(user);
  }, [user]);
  return null;
}

Next.js (Pages Router)

pages/_app.tsx
import type { AppProps } from 'next/app';
import { useEffect } from 'react';
import wutbug from '@wutbug/sdk';

export default function App({ Component, pageProps }: AppProps) {
  useEffect(() => {
    wutbug.init({
      projectKey: process.env.NEXT_PUBLIC_WUTBUG_KEY!,
      endpoint: process.env.NEXT_PUBLIC_WUTBUG_ENDPOINT,
    });
  }, []);
  return <Component {...pageProps} />;
}

React with Vite (or any SPA)

An SPA has no server rendering: initialize in the entry file, before mounting the app.

src/main.tsx
import { createRoot } from 'react-dom/client';
import wutbug from '@wutbug/sdk';
import App from './App';

wutbug.init({
  projectKey: import.meta.env.VITE_WUTBUG_KEY,
  endpoint: import.meta.env.VITE_WUTBUG_ENDPOINT,
  release: import.meta.env.VITE_RELEASE,
});

createRoot(document.getElementById('root')!).render(<App />);

To open the form from an error boundary:

TSX
import wutbug from '@wutbug/sdk';

function ErrorFallback({ error }: { error: Error }) {
  return (
    <div role="alert">
      <p>Something went wrong.</p>
      <button onClick={() => wutbug.open({ description: `Error: ${error.message}` })}>Report the problem</button>
    </div>
  );
}

Vue 3 (Vite)

src/main.ts
import { createApp } from 'vue';
import wutbug from '@wutbug/sdk';
import App from './App.vue';

wutbug.init({
  projectKey: import.meta.env.VITE_WUTBUG_KEY,
  endpoint: import.meta.env.VITE_WUTBUG_ENDPOINT,
});

createApp(App).mount('#app');

Nuxt 3 / 4

A plugin with the .client suffix only runs in the browser.

plugins/wutbug.client.ts
import wutbug from '@wutbug/sdk';

export default defineNuxtPlugin(() => {
  const config = useRuntimeConfig();
  wutbug.init({
    projectKey: config.public.wutbugKey as string,
    endpoint: config.public.wutbugEndpoint as string,
  });
});
nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    public: { wutbugKey: '', wutbugEndpoint: '' }, // NUXT_PUBLIC_WUTBUG_KEY, NUXT_PUBLIC_WUTBUG_ENDPOINT
  },
});

SvelteKit

Initialize in onMount of the root layout (it only runs in the browser).

src/routes/+layout.svelte
<script lang="ts">
  import { onMount } from 'svelte';
  import wutbug from '@wutbug/sdk';
  import { PUBLIC_WUTBUG_KEY, PUBLIC_WUTBUG_ENDPOINT } from '$env/static/public';

  let { children } = $props();

  onMount(() => {
    wutbug.init({ projectKey: PUBLIC_WUTBUG_KEY, endpoint: PUBLIC_WUTBUG_ENDPOINT });
    return () => wutbug.destroy();
  });
</script>

{@render children()}

Angular

Initialize in main.ts before bootstrapping the app. If you use SSR, guard the call with isPlatformBrowser or run it in an afterNextRender.

src/main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import wutbug from '@wutbug/sdk';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';
import { environment } from './environments/environment';

wutbug.init({ projectKey: environment.wutbugKey, endpoint: environment.wutbugEndpoint });

bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));

HTML, WordPress, Rails, Django, Laravel and PHP

On server-rendered sites, use the script tag in the <head> of the base template. If the person is signed in, identify them in the same template using the call queue.

HTML
<script>
  window.wutbug = window.wutbug || { q: [] };
  ['init', 'setUser', 'setMetadata', 'open', 'close'].forEach(function (m) {
    window.wutbug[m] = window.wutbug[m] || function () { window.wutbug.q.push([m, arguments]); };
  });
</script>
<script type="module" src="https://cdn.wutbug.dev/v0/wutbug.js"
  data-project-key="pk_your_key" data-endpoint="https://api.wutbug.dev"></script>

WordPress

Add the script with wp_head in your child theme's functions.php (or with a code-snippets plugin):

functions.php
add_action('wp_head', function () {
  echo '<script type="module" src="https://cdn.wutbug.dev/v0/wutbug.js" data-project-key="pk_your_key" data-endpoint="https://api.wutbug.dev"></script>';
});

Rails

app/views/layouts/application.html.erb
<script type="module" src="https://cdn.wutbug.dev/v0/wutbug.js"
  data-project-key="<%= ENV['WUTBUG_KEY'] %>" data-endpoint="<%= ENV['WUTBUG_ENDPOINT'] %>"></script>
<% if current_user %>
  <script>
    window.wutbug = window.wutbug || { q: [] };
    (window.wutbug.setUser || function () { window.wutbug.q.push(['setUser', arguments]); })(
      <%= raw({ id: current_user.id.to_s, email: current_user.email }.to_json) %>
    );
  </script>
<% end %>

Django

templates/base.html
<script type="module" src="https://cdn.wutbug.dev/v0/wutbug.js"
  data-project-key="{{ WUTBUG_KEY }}" data-endpoint="{{ WUTBUG_ENDPOINT }}"></script>

Laravel (Blade)

resources/views/layouts/app.blade.php
<script type="module" src="https://cdn.wutbug.dev/v0/wutbug.js"
  data-project-key="{{ config('services.wutbug.key') }}" data-endpoint="{{ config('services.wutbug.endpoint') }}"></script>

Content Security Policy

If your site uses CSP, allow:

  • script-src: the CDN domain (only if you use the script tag).
  • connect-src: the API endpoint and the object storage that report files are uploaded to (the signed URLs the API returns).
  • img-src: data: and blob: (screenshot).