Skip to main content
Live chat is a credibility signal — a product without it looks like a hobby project, a product with it looks like there’s a team behind it. But every chat tool ships its own SDK, auth model, and unmount quirks; rolling your own integration burns days when you should be shipping features. vibestrap mounts a <CustomerService /> widget in the locale layout that switches between four major providers (Crisp, Tawk.to, Intercom, Chatwoot) with a single config flip and a public env var. Providers self-gate on their env vars, so an unconfigured one renders nothing instead of crashing the page.

Prerequisites

  • An account with one of: Crisp, Tawk.to, Intercom, or Chatwoot.
  • The site/property/app ID from that provider’s dashboard.
  • Your production domain allow-listed in the provider’s settings (most reject embedded widgets from unknown origins).

Step-by-step

  1. Pick a provider in src/config/site.ts:
  2. Add the matching env vars to .env.local. All are NEXT_PUBLIC_* — they need to ship to the browser so the loader script can boot.
  3. Allow-list your domain in the provider’s dashboard (Crisp → Settings → Website Settings, Tawk → Property → Widget → Restrictions, etc).
  4. Restart pnpm dev — public env vars are baked at build time.

Pick the right provider

Verify it works

  1. Open the site in an incognito window — the bubble should appear bottom-right within ~1 second of page load.
  2. Send a test message; check the inbox in the provider’s dashboard.
  3. Visit /login or /register — the widget is intentionally hidden on auth pages (the <CustomerService /> component lives in the app layout, not the auth layout).
  4. View source: you should see exactly one loader script tag for your provider and zero for the others.

Common pitfalls

  1. Missing NEXT_PUBLIC_ prefix. Anything the browser reads needs the prefix; otherwise Next.js strips it from the bundle and the widget silently fails to mount.
  2. Forgot to allow-list your domain. Most providers reject the widget on unknown origins (you’ll see an empty bubble or a CORS error in devtools).
  3. CSP blocking the script. If you’ve added a Content-Security-Policy header, add the provider’s CDN to script-src and connect-src (e.g. https://client.crisp.chat, https://embed.tawk.to, https://widget.intercom.io, your Chatwoot base URL).
  4. Two widgets at once. Setting enable: true plus a stale env var from a previous provider doesn’t double-render — the switch in src/customer-service/index.tsx only mounts the active one. But leftover env vars are harmless; clean them up.
  5. Widget on auth pages. It’s hidden by design (separate layout). If you want it everywhere, mount <CustomerService /> in the root layout instead of the app layout.

Add a new provider

The pattern is intentionally tiny — a single client component that reads its env var and returns a <Script> tag (or null when unset). Look at any of src/customer-service/provider/*.tsx for the shape, then:
  1. Add the provider to the union in src/customer-service/types.ts.
  2. Add a new case to the switch in src/customer-service/index.tsx.
  3. Add the env var to src/env.ts under client.NEXT_PUBLIC_*.

Official docs