Skip to main content

Installation

Install @feelrift/react and its peer dependencies, then mount the provider chain in the framework you're using.

Install​

pnpm add @feelrift/react react react-dom

react and react-dom are peer dependencies — the SDK targets React 19+ and does not bundle them.

If any of your events have non-zero-priced ticket types, install the two Stripe peers as well:

pnpm add @stripe/stripe-js @stripe/react-stripe-js

The SDK loads @stripe/stripe-js dynamically — embeds that only serve free events never trigger the import and don't need either peer installed. <CheckoutForm> will warn in the console if you mount it on a paid event without the peers present.

Note <RiftEvent> calls loadStripe(ticketingConfig.stripe.publishableKey, { stripeAccount: ticketingConfig.stripe.connectedAccountId }) as soon as the Ticketing payment config arrives, so by the time the user reaches <CheckoutForm> the Stripe script is already downloaded. There is no separate "init Stripe" call to make.

Mount the provider chain​

The SDK has two wrapping components: <RiftProvider> (root, cross-cutting) and <RiftEvent> (event scope — owns eventId and the Stripe loader). Mount them in that order around any SDK component.

import { RiftEvent, RiftProvider } from "@feelrift/react";

export function TicketingEmbed() {
return (
<RiftProvider locale="es-MX">
<RiftEvent eventId="evt_summerfest_2026">
{/* SDK components go here */}
</RiftEvent>
</RiftProvider>
);
}

<RiftProvider> should appear once near the top of the embedded surface. <RiftEvent> wraps the event-scoped UI — most embeds have one, but you can mount more than one if you display multiple events on the same page.

Framework placement​

Where the provider chain lives depends on your framework.

Next.js App Router​

The SDK is client-only (every component carries "use client"). Wrap a layout segment with a client component that mounts the provider chain:

// app/embed/RiftRoot.tsx
"use client";

import type { ReactNode } from "react";
import { RiftEvent, RiftProvider } from "@feelrift/react";

export function RiftRoot({
eventId,
children,
}: {
eventId: string;
children: ReactNode;
}) {
return (
<RiftProvider>
<RiftEvent eventId={eventId}>{children}</RiftEvent>
</RiftProvider>
);
}
// app/embed/[eventId]/layout.tsx
import { RiftRoot } from "../RiftRoot";

export default function Layout({
params,
children,
}: {
params: { eventId: string };
children: React.ReactNode;
}) {
return <RiftRoot eventId={params.eventId}>{children}</RiftRoot>;
}

Importing any SDK component directly into a server component fails at build time with a clear "client-only module" error — the "use client" directives are preserved through the build.

Next.js Pages Router​

Mount the provider chain in _app.tsx:

// pages/_app.tsx
import type { AppProps } from "next/app";
import { RiftEvent, RiftProvider } from "@feelrift/react";

export default function App({ Component, pageProps }: AppProps) {
return (
<RiftProvider>
<RiftEvent eventId={pageProps.eventId}>
<Component {...pageProps} />
</RiftEvent>
</RiftProvider>
);
}

Vite SPA​

The directives are no-ops; the SDK works like any React library:

// src/main.tsx
import { createRoot } from "react-dom/client";
import { RiftEvent, RiftProvider } from "@feelrift/react";

import { App } from "./App";

createRoot(document.getElementById("root")!).render(
<RiftProvider>
<RiftEvent eventId="evt_summerfest_2026">
<App />
</RiftEvent>
</RiftProvider>,
);

Astro​

Wrap SDK components in a client:idle or client:visible island:

---
// MyRiftEmbed is your own client component that wraps
// <RiftProvider><RiftEvent>...</RiftEvent></RiftProvider>.
// The SDK doesn't ship it — you compose the providers
// and the SDK pieces you want inside it.
import { MyRiftEmbed } from "../components/MyRiftEmbed";
---

<MyRiftEmbed eventId="evt_summerfest_2026" client:idle />

The "use client" directives inside the SDK are benign in Astro's React integration.

Base URLs​

The SDK reaches the production Experiences API by default — both apiBaseUrl and altchaBaseUrl are baked in at build time. You only need to override them for staging or multi-environment setups:

<RiftProvider
apiBaseUrl="https://api.staging.feelrift.com"
altchaBaseUrl="https://altcha.staging.feelrift.com"
>
{/* … */}
</RiftProvider>
PropDefaultSet when
apiBaseUrlhttps://api.feelrift.comPointing at staging or a self-hosted Rift deployment.
altchaBaseUrlhttps://altcha.feelrift.comSame — the Altcha challenge endpoint runs on a separate host.

For local development against a localhost API, set the apiBaseUrl and altchaBaseUrl props on <RiftProvider> to your local endpoints — typically http://localhost:3000 (API) and http://localhost:8787 (Altcha). See <RiftProvider> props.

Get your first eventId​

Create an event in the Rift dashboard. The event's URL contains its ID — the bit after /events/ that starts with evt_…. That string is what you pass to <RiftEvent eventId="evt_…">. Drafts have IDs too; they just don't expose availability to anonymous attendees.

Verify​

Render this minimal embed, swapping evt_summerfest_2026 for one of your event IDs:

import { EventHeader, RiftEvent, RiftProvider } from "@feelrift/react";

export function SanityCheck() {
return (
<RiftProvider>
<RiftEvent eventId="evt_summerfest_2026">
<EventHeader />
</RiftEvent>
</RiftProvider>
);
}

You should see the event's name, start/end dates, location (if set), and organizer name paint without errors. The browser's network panel should show one GET /api/v1/events/<eventId>/config request with a 200 response.

If nothing renders:

  • 404 on /config — the event ID is wrong or scoped to a different organization than the API host serves.
  • 403 on /config — ingress rejected the request. Use the production apiBaseUrl (the default) rather than a private origin URL.
  • No request at all — <RiftEvent> is missing or the import path is wrong. <EventHeader> renders nothing without an event scope above it.

Next​

→ Your first checkout — the full end-to-end flow.