Server-side rendering
How @feelrift/react stays compatible with Next.js App Router (React
Server Components), Astro islands, and Vite SPAs without
framework-specific packages — and what consumers need to do to hold
the SDK's guarantees.
What the SDK guarantees
These invariants are tested on every release. You can rely on them in production.
1. "use client"; directives survive the build
Every hook and component module ships with "use client"; at the
top so Next.js App Router treats it as a client component. The
directive is preserved through the build pipeline; importing an SDK
hook or component into a server component fails at build time with a
clear error rather than at runtime with a hydration warning.
2. The /client entry is React-free
Non-React consumers (Vue, Svelte, server-side Node, edge runtimes)
can import the typed API client and RiftApiError without pulling
React into their bundle:
import { createRiftClient, RiftApiError } from "@feelrift/react/client";
const client = createRiftClient({ baseUrl: "https://api.feelrift.com" });
const config = await client.getConfig("evt_summerfest_2026");
3. The /client entry loads in Node without DOM globals
The /client entry has no load-time dependency on window,
document, or any other browser-only global. Safe to import from
server-side Node, edge runtimes, and worker contexts.
4. Heavy peers are externalized
React, ReactDOM, the JSX runtime, and the Stripe peers are externalized — the SDK doesn't bundle them, your tree provides them.
What the SDK does at runtime to stay hydration-safe
useHasHydrated() gate
The SDK defers reading any persisted state (reservation token, order
id) until after mount, so the first server render matches the first
client render — no React 19 hydration warnings. SDK components
already respect this; custom components that read persisted state
directly should gate on useHasHydrated() the same way:
import { useHasHydrated, useRiftStore } from "@feelrift/react";
function MyReservationBadge() {
const hasHydrated = useHasHydrated();
const reservation = useRiftStore((s) => s.reservation);
if (!hasHydrated) return null;
return reservation ? <span>{reservation.data.id}</span> : null;
}
clientSecret is never persisted
The Stripe PaymentIntent clientSecret is deliberately left out of
the persistence allow-list. PaymentIntents can be canceled
server-side between sessions; rehydrating a stale secret produces
raw Stripe errors. On resume, <CheckoutForm> re-calls the checkout
endpoint with the persisted reservation token to mint a fresh
PaymentIntent.
Stripe.js loads lazily
<RiftEvent> only imports the Stripe runtime when the event is paid
(ticketingConfig.stripe !== null). Disabled Ticketing and free-event
embeds never trigger the import, so the optional peer stays opt-in and
Stripe's payload never lands in your initial bundle.
Consumer checklist by framework
Next.js App Router
- Import + use the SDK from any client component (file with
"use client";). Importing into a server component fails at build time with a clear "client-only module" error. - Mount the provider chain (
<RiftProvider>+<RiftEvent>) inside a client component that wraps the affected layout segment. See Installation → Next.js App Router. - For server-side data fetching (RSC, route handlers, server
actions), import from
@feelrift/react/clientrather than the root. Build a typed client withcreateRiftClient(...)and call methods such asclient.getConfig(...),client.getTicketingConfig(...), andclient.getAvailability(...).
Next.js Pages Router
- Mount the provider chain in
_app.tsx. The SDK works inside both page components and any children of<Component />. - Use
getServerSideProps/getStaticPropswith the@feelrift/react/cliententry for server-side data — same pattern as App Router.
Astro
- Wrap SDK components in a
client:idleorclient:visibleisland. The"use client";directives are benign string literals in Astro's React integration; the islands are what actually defer hydration. - Server-side rendering inside an Astro page (no client directive) works for read-only flows that use only the typed client methods, not the hooks.
Vite SPA
- No special setup. The directives are no-ops; the SDK works like any React library.
- Code-split heavy SDK components (
<CheckoutForm>in particular — it pulls Stripe Elements) withReact.lazyif the embed isn't visible on first paint.
Troubleshooting hydration warnings
If you hit a hydration warning specific to one of the target frameworks:
- Check that you're importing from
@feelrift/react, not deep into@feelrift/react/dist/*. - Confirm your bundler hasn't stripped the
"use client"directives (most React 19+ tooling preserves them automatically; older toolchains may need a plugin). - File an issue with the framework version, the failing component, and the warning text.
Related
- Installation — framework-specific provider placement.
RiftProvider— the provider that triggers the post-mount rehydration.