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.
Paid events
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>callsloadStripe(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>
| Prop | Default | Set when |
|---|---|---|
apiBaseUrl | https://api.feelrift.com | Pointing at staging or a self-hosted Rift deployment. |
altchaBaseUrl | https://altcha.feelrift.com | Same — 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:
404on/config— the event ID is wrong or scoped to a different organization than the API host serves.403on/config— ingress rejected the request. Use the productionapiBaseUrl(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.