[relaybell docs › Install › React (Vite or Create React App)](https://relaybell.com/docs/install-react) · [All docs as Markdown](https://relaybell.com/docs.md) · [Index for AI assistants](https://relaybell.com/llms.txt)

# React (Vite or Create React App)

> Add relaybell to a single-page React app built with Vite or Create React App: the snippet in index.html, sw.js in public/, a subscribe button and signed-in users.

A single-page React app loads one HTML page, so the plain [Quickstart](https://relaybell.com/docs/quickstart) snippet is all you need: it goes in `index.html`, and your components talk to `window.RelayBell`. Your **Project ID** is on the dashboard's **Install** page. (Using Next.js? See [Next.js](https://relaybell.com/docs/install-nextjs).)

## 1. Add the snippet to index.html {#snippet}

Paste it just before `</body>` — in Vite that's `index.html` at the project root, in Create React App `public/index.html`:

```html
<script src="https://relaybell.com/relaybell-sdk.js"></script>
<script>
  RelayBell.init({ projectId: "YOUR_PROJECT_ID", apiBase: "https://relaybell.com" });
</script>
```

The page loads it once, and it stays loaded while your router changes pages, so `init()` runs once per page load — as it should (a second call starts again from its own options). Don't call `init()` from a component.

Both tools load your app's own code after these classic scripts have run, so `window.RelayBell` is there by the time your components mount.

## 2. Add the service worker {#service-worker}

Download [sw.js](https://relaybell.com/sw.js) and save it as `public/sw.js`. Vite and Create React App both serve `public/` from the root of your site, so it opens at `https://yoursite.com/sw.js` — the default `swPath`.

> **Note:** If your app already registers its own service worker at `/sw.js` (a PWA plugin, for example), keep it: save relaybell's as `public/relaybell/sw.js` and add `swPath: "/relaybell/sw.js", swScope: "/relaybell/"` to `init()`. See [Installing the SDK](https://relaybell.com/docs/install).

## 3. TypeScript types {#types}

If your app uses TypeScript, declare what you use from `window.RelayBell`, for example in `src/relaybell.d.ts`:

```ts
export {};

declare global {
  interface Window {
    RelayBell?: {
      subscribeNow(): Promise<{ supported: boolean; subscribed: boolean; permission?: NotificationPermission; reason?: string }>;
      isSubscribed(): Promise<boolean>;
      identify(externalId: string | null, userHash?: string): Promise<void>;
      unsubscribe(): Promise<{ unsubscribed: boolean }>;
      setTags(tags: Record<string, string | number | boolean>): Promise<Record<string, unknown>>;
      track(event: string, properties?: Record<string, unknown>): Promise<unknown>;
    };
  }
}
```

## 4. A subscribe button (optional) {#subscribe-button}

relaybell shows your opt-in prompt by itself ([Opt-in prompts](https://relaybell.com/docs/prompts)). For your own button, call `subscribeNow()` from the click — browsers only show the permission prompt in answer to one:

```tsx
import { useEffect, useState } from "react";

export function NotifyButton() {
  const [on, setOn] = useState(false);
  useEffect(() => {
    window.RelayBell?.isSubscribed().then(setOn);
  }, []);
  if (on) return <p>Notifications are on.</p>;
  return (
    <button
      onClick={async () => {
        const result = await window.RelayBell?.subscribeNow();
        if (result?.subscribed) setOn(true);
        else if (result && !result.supported) alert("This browser can't show notifications from this site.");
      }}
    >
      Turn on notifications
    </button>
  );
}
```

On an iPhone or iPad, `supported` is `false` until the visitor adds your site to their Home Screen — see [Browser support](https://relaybell.com/docs/browser-support).

## 5. Link signed-in users {#identify}

To [send to one of your users](https://relaybell.com/docs/send-to-one-user) by your own user id, call `identify()` once you know who is signed in. With [identity verification](https://relaybell.com/docs/identity), pass the `userHash` your server made for that user (the HMAC-SHA256 of the id with your website's secret key) — make it on the server and send it to the app with the signed-in user's details; never put the secret key in the app.

```tsx
import { useEffect } from "react";

// user = the signed-in user from your own auth code, with userHash added by your server
export function useRelayBellUser(user: { id: string; relaybellHash: string } | null) {
  const id = user?.id;
  const hash = user?.relaybellHash;
  useEffect(() => {
    if (id) window.RelayBell?.identify(id, hash);
  }, [id, hash]);
}
```

Calling `identify()` again with the same values does nothing, so it's fine in an effect. On a shared browser, call `window.RelayBell?.unsubscribe()` when someone signs out of a site without identity verification — see `identify()` under [Methods](https://relaybell.com/docs/browser-sdk#sdk-methods).

## 6. Track events {#events}

`track()` feeds your [automations](https://relaybell.com/docs/events-automation) (abandoned cart, product alerts, journeys) and conversion reports:

```tsx
window.RelayBell?.track("cart.updated", { productId: "1234", product: "Blue Shoes", price: 49, url: location.href });
window.RelayBell?.track("purchase", { value: 79.99, orderId: "A123" });
```

Or add `autoCapture: true` to `init()`: it records page views — including your router's page changes — and clicks on elements with `data-rb-event`. See [Auto-capture](https://relaybell.com/docs/events-automation#auto-capture).
