Install Flawsnap in a React app

Load the widget once in your HTML shell, not in a component, then tell it who is signed in from your auth code.

Steps

  1. 1

    Allow your dev and production hosts

    In the Flawsnap project’s Security settings, add your production domain, any staging host and your dev server, such as localhost:5173 for Vite.

  2. 2

    Add the snippet to index.html

    Paste the Flawsnap snippet into the <head> of index.html (the project root in Vite, public/index.html in older setups).

  3. 3

    Add the global type

    If you use TypeScript, add a small declaration so window.Flawsnap type-checks.

  4. 4

    Identify signed-in users

    Where your app learns who is signed in, call window.Flawsnap?.identify({ name, email }).

  5. 5

    Run the app and send a report

    Start the dev server, click the button and send a test report. It appears in the project’s reports.

Load it once, in index.html

The simplest and most complete install is the HTML shell your React app mounts into. The widget then loads before React does, sees console errors from the first render, and stays for the life of the tab.

index.html
<head>
  <meta charset="UTF-8" />
  <title>My app</title>
  <script src="https://backend.flawsnap.com/widget.js" data-key="pk_…" async></script>
</head>

Why not a component

Do not render the script tag from a page component or load it in a per-route effect. It only needs to run once per tab. Flawsnap follows client-side navigation itself: it watches history.pushState, replaceState and the back button, so React Router route changes are picked up, and recordings carry on across them.

If you must load it from code, do it once at the app root and guard it. Flawsnap ignores a second copy anyway, but the guard stops this file adding the script twice (for example after a hot reload) and keeps the console clean.

src/main.tsx (only if you cannot edit index.html)
if (!document.querySelector('script[data-flawsnap]')) {
  const s = document.createElement('script')
  s.src = 'https://backend.flawsnap.com/widget.js'
  s.async = true
  s.dataset.key = import.meta.env.VITE_FLAWSNAP_KEY
  s.dataset.flawsnap = ''
  document.head.appendChild(s)
}

Identify the signed-in user

Add the type once:

src/flawsnap.d.ts
// flawsnap.d.ts (anywhere your tsconfig includes)
export {}

declare global {
  interface Window {
    Flawsnap?: {
      open(): void
      identify(reporter: { name?: string; email?: string }): void
      setMetadata(details: Record<string, unknown>): void
      screen(name: string): void
      track(name: string): void
      resetUser(): void
    }
  }
}

Then call identify from your auth state

Put it next to wherever you already know the user, such as an auth provider. The optional chaining means nothing breaks if the widget is blocked or not loaded yet.

src/auth/AuthProvider.tsx
useEffect(() => {
  if (!user) return
  window.Flawsnap?.identify({ name: user.displayName, email: user.email })
  window.Flawsnap?.setMetadata({ plan: user.plan, accountId: user.accountId })
}, [user])

On sign-out, window.Flawsnap?.resetUser() starts a fresh Insights visit. The name and email from identify only fill in the report form and never reach Insights.

HashRouter and Insights screens

Insights (Pro) groups visits by screen, keyed on the URL path, with ids masked so /orders/123 becomes /orders/:id. With BrowserRouter that works with no code. With HashRouter, every route has the same path, so name screens yourself:

A layout component
const location = useLocation()
useEffect(() => {
  window.Flawsnap?.screen(location.pathname)
}, [location.pathname])

Troubleshooting

  • [flawsnap] widget disabled: on localhost: add the dev server host with its port. Vite usually runs on localhost:5173; Create React App on localhost:3000.
  • identify does nothing: it ran before the widget loaded. Optional chaining skipped it. Run it again when user changes, or queue it only while the widget isn’t there yet: const f = window.Flawsnap; if (f?.identify) f.identify({...}); else ((window as any).Flawsnap ??= { q: [] }).q.push(['identify', {...}]).
  • Your API calls are missing from reports: only the last 100 fetch/XHR requests are kept, and the widget must load before they are made.
  • Your CSP blocks it: allow https://backend.flawsnap.com in script-src and connect-src.

Questions

Is there an npm package for React?
No. The widget is a hosted script, so there is nothing to install or keep up to date in your package.json.
Does it work with React Router?
Yes. The widget follows pushState navigation on its own. Reports carry the current URL, and recordings continue across route changes.

Try it on your own site

7-day free trial, then $9 a month for Starter or $19 for Pro, per organization, with unlimited team members.

Updated