@dbosoft/nextjs-tutorials
v0.2.3
Published
Interactive tutorial/learning system for dbosoft sites
Downloads
51
Keywords
Readme
@dbosoft/nextjs-tutorials
Interactive tutorial system for Next.js sites. Server/client component architecture with data-driven blocks, variant paths, and progress tracking.
Installation
{
"dependencies": {
"@dbosoft/nextjs-tutorials": "workspace:*",
"@dbosoft/nextjs-site-core": "workspace:*",
"@dbosoft/react-uicore": "workspace:*"
},
"peerDependencies": {
"react": ">=18",
"next": ">=16",
"@headlessui/react": ">=2",
"@heroicons/react": ">=2",
"next-intl": ">=4",
"clsx": ">=2"
}
}Quick Start
1. Define a tutorial
// lib/tutorials-server.ts
import { createTutorialServer } from '@dbosoft/nextjs-tutorials/server'
import type { TutorialDefinition } from '@dbosoft/nextjs-tutorials/types'
export type MyVariant = 'cli' | 'gui'
const tutorial: TutorialDefinition<MyVariant> = {
slug: 'getting-started',
title: 'Getting Started',
description: 'Learn the basics.',
difficulty: 'beginner',
estimatedTime: '15 min',
variants: [
{ id: 'cli', label: 'CLI' },
{ id: 'gui', label: 'GUI' },
],
defaultVariant: 'cli',
prerequisites: ['Node.js 18 or later'],
learningObjectives: ['Set up the development environment'],
lessons: [
{
id: 'setup',
title: 'Setup',
description: 'Install and configure.',
steps: [
{ id: 'install', title: 'Installation', estimatedTime: 3 },
{ id: 'config', title: 'Configuration', estimatedTime: 5 },
],
},
],
}
export const tutorialServer = createTutorialServer<MyVariant>({
registry: [tutorial],
stepLoader: async (slug, lesson, step) => {
const mod = await import(`@/tutorials/${slug}/${lesson}/${step}`)
return mod.default
},
pathPrefix: '/tutorials',
})2. Create the pages
// app/tutorials/page.tsx
import { TutorialListPage } from '@dbosoft/nextjs-tutorials/layout'
import { tutorialServer } from '@/lib/tutorials-server'
export default async function TutorialsPage() {
const tutorials = await tutorialServer.getAllTutorials()
return <TutorialListPage tutorials={tutorials} title="Tutorials" />
}// app/tutorials/[slug]/page.tsx
import { TutorialPage } from '@dbosoft/nextjs-tutorials/layout'
import {
TutorialPathProvider,
TutorialProgress,
ProgressText,
} from '@dbosoft/nextjs-tutorials/components'
import { tutorialServer } from '@/lib/tutorials-server'
import type { MyVariant } from '@/lib/tutorials-server'
export default async function TutorialDetailPage({
params,
searchParams,
}: {
params: Promise<{ slug: string }>
searchParams: Promise<{ lesson?: string; step?: string; path?: string }>
}) {
const { slug } = await params
const { lesson, step, path } = await searchParams
const tutorial = await tutorialServer.getTutorialBySlug(slug)
if (!tutorial) notFound()
const totalSteps = tutorial.lessons.reduce((sum, l) => sum + l.steps.length, 0)
return (
<TutorialPathProvider<MyVariant>
tutorialSlug={tutorial.slug}
variants={tutorial.variants ?? []}
defaultVariant={tutorial.defaultVariant ?? 'cli'}
initialVariant={path as MyVariant | undefined}
>
<TutorialProgress tutorialSlug={tutorial.slug} totalSteps={totalSteps}>
<TutorialPage
tutorial={tutorial}
currentLesson={lesson}
currentStep={step}
stepLoader={tutorialServer.loadStep}
pathPrefix="/tutorials"
/>
</TutorialProgress>
</TutorialPathProvider>
)
}3. Write step content
tutorials/
getting-started/
setup/
install.tsx
config.tsxEach step is a React component that composes blocks:
// tutorials/getting-started/setup/install.tsx
import {
TutorialSection,
TutorialText,
CodeExample,
ConceptBox,
TipBox,
InlineCode,
Emphasis,
} from '@dbosoft/nextjs-tutorials/blocks'
import { PathBranch } from '@dbosoft/nextjs-tutorials/components'
import type { MyVariant } from '@/lib/tutorials-server'
export default function Install() {
return (
<TutorialSection>
<TutorialText>
Install <InlineCode>@example/toolkit</InlineCode> using your
preferred package manager.
</TutorialText>
<PathBranch<MyVariant>>
{{
cli: (
<CodeExample
title="Terminal"
language="bash"
code="npm install @example/toolkit"
explanation="This installs the toolkit and all dependencies."
/>
),
gui: (
<TutorialText>
Open the package manager UI and search for
<Emphasis> @example/toolkit</Emphasis>, then click Install.
</TutorialText>
),
}}
</PathBranch>
<ConceptBox title="What gets installed?">
<TutorialText>
The core library, CLI tools, and default templates.
</TutorialText>
</ConceptBox>
<TipBox title="Tip">
<TutorialText>
If you encounter permission errors, try elevated privileges.
</TutorialText>
</TipBox>
</TutorialSection>
)
}Block Components
Blocks are the building units for tutorial content. They enforce consistent design across all tutorials.
Design Rules
Blocks are data-driven. Pass strings, arrays, and structured objects as props. Never write raw HTML (
<li>,<strong>,<code>,<p>) inside step content.childrenis for nesting blocks. Container blocks (TutorialSection,ConceptBox,TipBox,Exercise,PrerequisiteHint) acceptchildren— but children must be other blocks, not raw content.Inline blocks go inside
TutorialText. UseInlineCode,Emphasis,ExternalLink, andStepIndicatorinsideTutorialTextfor inline formatting.
// CORRECT: data-driven blocks, nested composition
<TutorialSection>
<TutorialText>
Run <InlineCode>node --version</InlineCode> to verify.
</TutorialText>
<CodeExample language="bash" code="node --version" />
<TipBox title="Tip">
<TutorialText>Use Node.js 18 or later.</TutorialText>
</TipBox>
</TutorialSection>
// WRONG: raw HTML, content not wrapped in blocks
<TutorialSection>
<p>Run <code>node --version</code> to verify.</p>
<TipBox title="Tip">
Use Node.js 18 or later.
</TipBox>
</TutorialSection>Block Reference
Core (inline text)
| Block | Props | Purpose |
|-------|-------|---------|
| TutorialSection | children | Top-level container with space-y-4 |
| TutorialText | children | Body text paragraph (hosts inline blocks) |
| TutorialHeading | level: 3\|4\|5, children | Section heading |
| InlineCode | children, variant?: 'default'\|'accent' | Inline code span |
| Emphasis | children | Bold text |
| ExternalLink | href, children | External link with icon |
Content (data-driven)
| Block | Props | Purpose |
|-------|-------|---------|
| CodeExample | code, language?, title?, explanation? | Syntax-highlighted code block |
| TerminalOutput | output, title?, variant? | Command output display |
| TutorialList | items: (string\|ReactNode)[], ordered? | Bullet or numbered list |
| ComparisonTable | headers: [string, string], rows: [string, string][] | Two-column comparison |
| DefinitionList | items: {term, definition}[] | Term/definition pairs |
| FeatureGrid | items: {icon?, title, description}[], columns?: 2\|3 | Feature cards grid |
| FlowDiagram | steps: {icon?, label}[] | Horizontal step flow with arrows |
| WorkflowSteps | steps: {icon?, title, description}[] | Numbered vertical steps |
| BadgeWithDescription | items: {badge, description, variant?}[] | Badge + description list |
| TutorialImage | src, alt, width?, height?, caption?, inline? | Image with optional lightbox |
Containers (accept nested blocks)
| Block | Props | Purpose |
|-------|-------|---------|
| ConceptBox | title, children | Key concept callout with left accent |
| TipBox | type?: 'tip'\|'warning'\|'info'\|'success', title?, children | Tip/warning/info callout |
| Exercise | title, children | Interactive exercise prompt |
| PrerequisiteHint | children | Prerequisites warning callout |
Navigation
| Block | Props | Purpose |
|-------|-------|---------|
| NavigationHint | items: string[] | Breadcrumb-style path hint |
| StepIndicator | current, total | Progress badge (e.g. "3/5") |
Variant Paths
Tutorials support multiple instruction paths (e.g. CLI vs GUI). The variant is stored in the URL (?path=cli) for SSR agreement, with localStorage fallback.
PathBranch
Wraps variant-specific content. Renders the PathSwitcher ("Instructions for: CLI | GUI") automatically at the fork point.
import { PathBranch } from '@dbosoft/nextjs-tutorials/components'
<PathBranch<MyVariant>>
{{
cli: <CodeExample code="npm start" language="bash" />,
gui: <TutorialText>Click the Run button.</TutorialText>,
}}
</PathBranch>Use hideSwitcher on subsequent PathBranch blocks in the same step:
<PathBranch<MyVariant>>{{ /* first fork — shows switcher */ }}</PathBranch>
<PathBranch<MyVariant> hideSwitcher>{{ /* second fork — no switcher */ }}</PathBranch>PathContent
Shows content only for a specific variant. Does not render the switcher — use inside a PathBranch or when the switcher isn't needed.
import { PathContent } from '@dbosoft/nextjs-tutorials/components'
<PathContent<MyVariant> variant="cli">
<DefinitionList items={[...]} />
</PathContent>Architecture
Server/Client Split
TutorialPage(server component): Resolves active lesson/step from URL, pre-loads all step components for the active lesson, renders the full page markup.StepDisclosure(client component): Receives pre-loaded step content, manages toggle/collapse state client-side. No page reload for within-lesson navigation.- Lesson switching triggers a server round-trip (new lesson = new step components to load).
Progress Tracking
TutorialProgress uses useSyncExternalStore with localStorage. SSR renders with empty progress, client hydrates from storage without cascading re-renders.
URL Parameters
| Param | Purpose |
|-------|---------|
| lesson | Active lesson ID |
| step | Active step ID (updated via replaceState) |
| path | Active variant for SSR agreement |
Exports
| Path | Content |
|------|---------|
| @dbosoft/nextjs-tutorials/types | TypeScript types |
| @dbosoft/nextjs-tutorials/server | createTutorialServer() |
| @dbosoft/nextjs-tutorials/layout | TutorialPage, TutorialListPage |
| @dbosoft/nextjs-tutorials/components | Client components (providers, disclosure, path, progress) |
| @dbosoft/nextjs-tutorials/blocks | Content blocks |
| @dbosoft/nextjs-tutorials/seo | TutorialSchema structured data |
| @dbosoft/nextjs-tutorials/i18n | loadMessages() |
