React / Next.js
Add ClassFlow to a React or Next.js App
Use the hosted ClassFlow loader with a data-configured widget container. The same production integration works in React, Vite, Create React App, and Next.js without adding a package dependency.
Prerequisites
- ✓ React 18+ or Next.js 13+ (App Router or Pages Router both work).
- ✓ A ClassFlow studio account with Widget Studio enabled.
- ✓ Your studio slug and studio URL from Widget Studio.
- ✓ Your app domain added to the Widget Studio allowlist.
Next.js (recommended)
Use Next.js's built-innext/scriptcomponent to load the ClassFlow script. Setstrategy="afterInteractive"so it loads after hydration and doesn't block the page.
// app/schedule/page.tsx (Next.js App Router)
import Script from 'next/script'; export default function SchedulePage() { return ( <> <div data-classflow="schedule" data-studio="your-studio-slug" data-studio-url="https://your-studio.classflow.studio" data-api-url="https://api.getclassflow.com" data-theme="light" data-days="14" /> <Script type="module" src="https://widget.classflow.studio/v1/loader.js" strategy="afterInteractive" /> </> );
}Works with both App Router (Server and Client components) and Pages Router. The<Script>component handles deduplication - you can include it on multiple pages without loading the script twice.
Vite / Create React App
For non-Next.js React apps, add the script tag to yourindex.htmland render the div in a component. Because React re-renders can detach and re-attach DOM nodes, trigger widget initialization in auseEffect:
// For Vite / Create React App (index.html or public/index.html)
// Add the script tag once in the <head> or before </body>:
// <script type="module" src="https://widget.classflow.studio/v1/loader.js"></script>
//
// Then in your React component:
import { useEffect, useRef } from 'react'; export function ClassFlowSchedule({ studioSlug, studioUrl }: { studioSlug: string; studioUrl: string }) { const ref = useRef<HTMLDivElement>(null); useEffect(() => { // The loader observes new widget nodes; refresh is a safe SPA fallback. (window as Window & { ClassFlow?: { refresh: () => void } }).ClassFlow?.refresh(); }, []); return ( <div ref={ref} data-classflow="schedule" data-studio={studioSlug} data-studio-url={studioUrl} data-api-url="https://api.getclassflow.com" data-theme="light" data-days="14" /> );
}Raw HTML snippet (for dangerouslySetInnerHTML)
If you need to inject the snippet as raw HTML (e.g. from a CMS), usedangerouslySetInnerHTML. Only do this if the content is your own - never inject untrusted HTML.
<div data-classflow="schedule" data-studio="your-studio-slug" data-studio-url="https://your-studio.classflow.studio" data-api-url="https://api.getclassflow.com" data-theme="light" data-days="14">
</div>
<script type="module" src="https://widget.classflow.studio/v1/loader.js"></script>Theming
Set CSS custom properties on a wrapping element or in your global CSS. React apps don't use iframes so your app's CSS applies to the widget - keep resets minimal to avoid conflicts:
:root { --cf-primary: #0f766e; /* buttons, links */ --cf-bg: #ffffff; /* widget background */ --cf-text: #111827; /* body text */ --cf-radius: 0.75rem; /* border radius */
}Multiple widgets on one page
Render multiple widget containers on the same page. Include the<Script>tag once—the ClassFlow runtime scans the DOM and initializes everydata-classflowelement it finds.
Common gotchas
- SSR hydration mismatch.If you render the widget div server-side, the script runs before React hydration and can conflict. Use
strategy="afterInteractive"in Next.js or add auseEffectguard to ensure the script runs client-side only. - Widget unmounts on route change.In Next.js App Router, navigating away and back re-mounts the component. This is fine - the widget re-initializes on mount. Use a
keyprop to force clean re-mounts if needed. - Strict Mode double-invoke.React Strict Mode in development double-invokes effects. This may cause a brief flash. The ClassFlow widget deduplicates initialization - it is safe in Strict Mode.
- Content Security Policy.If your app sets a CSP header, whitelist
widget.classflow.studioinscript-srcandapi.getclassflow.cominconnect-src.
Get your personalized snippet
Widget Studio generates the code with your studio slug pre-filled.
Open Widget Studio