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.