Next.js & Vite
Next.js (App Router)
LearnHouseActivity is a client component, so you can use it straight from a Server Component page. Import the
stylesheet once, in your root layout.
import '@learnhouse/react/styles.css'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}import { LearnHouseActivity } from '@learnhouse/react'
export default async function LessonPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
return <LearnHouseActivity baseUrl={process.env.NEXT_PUBLIC_LEARNHOUSE_URL!} activityId={id} />
}Fetch on the server, render with no client round trip
Load the activity in the Server Component with the SDK and hand it over as activity. The page is
server-rendered with the lesson already in the HTML, and your API token never leaves the server.
import { createClient } from '@learnhouse/sdk'
import { LearnHouseActivity } from '@learnhouse/react'
const lh = createClient({
baseUrl: process.env.LEARNHOUSE_URL!,
token: process.env.LEARNHOUSE_TOKEN!, // lh_… org API token, server only
})
export default async function LessonPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const activity = await lh.activities.get(id)
// Media blocks need the course uuid. Skip this call if you already store it.
const course = await lh.GET('/api/v1/courses/id/{course_id}', {
params: { path: { course_id: activity.course_id! } },
})
return (
<LearnHouseActivity
baseUrl={process.env.NEXT_PUBLIC_LEARNHOUSE_URL!}
activity={activity}
courseId={course.course_uuid}
/>
)
}The same works with content={doc} for documents you store yourself: Next prerenders the whole lesson as static HTML.
Event handlers need a client component
Props that are functions (onEvent, onLinkClick, components, transformContent, render-prop header/footer)
can't cross from a Server Component. Put them in a small client wrapper:
'use client'
import { LearnHouseActivity, type LearnHouseActivityData } from '@learnhouse/react'
import { useRouter } from 'next/navigation'
export function Lesson({ activity, courseId }: { activity: LearnHouseActivityData; courseId: string }) {
const router = useRouter()
return (
<LearnHouseActivity
baseUrl={process.env.NEXT_PUBLIC_LEARNHOUSE_URL!}
activity={activity}
courseId={courseId}
trackCompletion
onEvent={(e) => e.type === 'complete' && router.refresh()}
/>
)
}Vite
Nothing to configure.
import { createRoot } from 'react-dom/client'
import { LearnHouseActivity } from '@learnhouse/react'
import '@learnhouse/react/styles.css'
createRoot(document.getElementById('root')!).render(
<LearnHouseActivity baseUrl="https://learn.your-school.com" activityId="activity_…" courseId="course_…" />,
)Other setups
Any React 18+ app with a bundler that handles CSS imports and dynamic import() works (Remix, React Router,
Astro islands, Rsbuild…). Blocks are split into their own chunks, so a page only downloads the blocks it shows.
Not a React app? Vue and Nuxt have @learnhouse/vue, and anything else can use the
<learnhouse-activity> custom element from @learnhouse/react/element.
Bundle size
The core of the package is small; most of the weight is the blocks themselves, loaded on demand:
| What | Loaded when |
|---|---|
| Component, provider, data loading | Always |
| Page renderer and text blocks | The activity is a page |
| Quiz, flipcards, scenarios, math, code playground… | The page contains that block |
| Video player | The activity (or a block) is a video |
| PDF viewer | The activity (or a block) is a PDF |
| One language's strings | locale isn't English |
Where to fetch: browser or server
When you pass activityId, the component fetches the activity from the browser. LearnHouse's API only answers browsers
on origins it trusts:
| Instance | Browser requests are accepted from |
|---|---|
| Self-hosted, single organization | The instance's own domain, and localhost |
| Self-hosted, multi-organization | Origins matching LEARNHOUSE_ALLOWED_REGEXP (by default the instance domain and its subdomains) |
| LearnHouse Cloud | LearnHouse's own domains |
So if your app runs on another domain, fetch on your server and pass activity (or content), as in
Fetch on the server above. It is also the faster option:
the lesson arrives with the page. Images, video and audio in the lesson load straight from your instance.
Fetching from the browser with activityId works when your app runs on the instance's domain or a subdomain it
trusts, and on localhost while you develop.