Files
boos e0a5f57793 feat(desktop): embed BoosAPI Desktop client as a subproject
Vendor the rebranded BoosAPI Desktop client (Tauri + Vue) into metapi as
a monorepo subdirectory under desktop/. Source only — node_modules,
dist, Cargo target and the upstream .git history are excluded.

desktop/ keeps its own independent Cargo workspace (desktop/src-tauri
with crates/*); it is not part of the backend boosapi crate, so a
`cargo check` at the metapi root still compiles only the backend. Build
the desktop app from desktop/src-tauri per desktop/README.md.

Note: app icons are still placeholder assets pending the final BoosAPI
logo. The updater signing keys live outside the repo and are not
included.
2026-06-22 11:32:57 +08:00

9.1 KiB

React Quick Start

Get started with markstream-react in your React project.

::: tip Migrating from react-markdown? If your app already uses react-markdown, start with the migration guide and the migration cookbook before wiring new code by hand. :::

Basic Setup

1. Installation

First, install the package:

pnpm add markstream-react

2. Import Styles

In your main entry file (e.g., main.tsx, index.tsx, or App.tsx):

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import 'markstream-react/index.css'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
)

3. Use the Component

import MarkdownRender from 'markstream-react'

function App() {
  const markdown = `# Hello React!

This is **markstream-react** - a streaming-friendly Markdown renderer for React.

## Features

- Code syntax highlighting
- Mermaid diagrams
- Math formulas
- And much more!

\`\`\`javascript
console.log('Hello from React!')
\`\`\`
`

  return (
    <div>
      <MarkdownRender content={markdown} />
    </div>
  )
}

export default App

Using with TypeScript

markstream-react is built with TypeScript and includes full type definitions:

import type { NodeRendererProps } from 'markstream-react'
import MarkdownRender from 'markstream-react'

const props: NodeRendererProps = {
  content: '# Hello TypeScript!',
}

function App() {
  return <MarkdownRender {...props} />
}

Using with Next.js

Use the dedicated SSR entrypoints instead of mounted guards or ssr: false.

See React Next SSR for the full contract and examples.

App Router (Next.js 14 / 15)

import MarkdownRender from 'markstream-react/next'

export default function MarkdownPage() {
  const markdown = `# Hello Next.js!

This route ships real SSR HTML first.
`

  return <MarkdownRender content={markdown} final />
}

Pages Router

import MarkdownRender from 'markstream-react/next'
import 'markstream-react/index.css'

export default function MarkdownPage() {
  const markdown = `# Hello Next.js Pages Router!`

  return <MarkdownRender content={markdown} final />
}

Pure Server Rendering

import MarkdownRender from 'markstream-react/server'

export default function MarkdownPage() {
  const markdown = `# Pure server entry`

  return <MarkdownRender content={markdown} final />
}

Enabling Optional Features

Code Syntax Highlighting

Install dependencies:

pnpm add stream-markdown
import MarkdownRender, { MarkdownCodeBlockNode, setCustomComponents } from 'markstream-react'

// Use Shiki-based code blocks inside MarkdownRender
setCustomComponents({
  code_block: ({ node, isDark, ctx }: any) => (
    <MarkdownCodeBlockNode
      node={node}
      isDark={isDark}
      stream={ctx?.codeBlockStream}
      {...(ctx?.codeBlockProps || {})}
    />
  ),
})

function App() {
  const markdown = `\`\`\`javascript
const hello = 'world'
console.log(hello)
\`\`\``

  return <MarkdownRender content={markdown} />
}

Mermaid Diagrams

Install mermaid:

pnpm add mermaid

Import styles (Mermaid auto-loads when installed):

import 'markstream-react/index.css'

function App() {
  const markdown = `#### Mermaid Diagram

\`\`\`mermaid
graph TD
    A[Start] --> B{Is it working?}
    B -->|Yes| C[Great!]
    B -->|No| D[Keep trying]
\`\`\``

  return <MarkdownRender content={markdown} />
}

D2 Diagrams

Install D2:

pnpm add @terrastruct/d2
import 'markstream-react/index.css'

function App() {
  const markdown = `#### D2 Diagram

\`\`\`d2
direction: right
Client -> API: request
API -> DB: query
DB -> API: rows
API -> Client: response
\`\`\``

  return <MarkdownRender content={markdown} />
}

Math Formulas (KaTeX)

Install katex:

pnpm add katex

Import styles (KaTeX auto-loads when installed):

import 'markstream-react/index.css'
import 'katex/dist/katex.min.css'

function App() {
  const markdown = `#### Math Example

Inline math: $E = mc^2$

Block math:

$$
\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}
$$`

  return <MarkdownRender content={markdown} />
}

Custom Components

You can customize how specific nodes are rendered by passing custom component mappings:

import MarkdownRender, { setCustomComponents } from 'markstream-react'

// Custom heading component
function CustomHeading({ node, customId }: any) {
  const level = node.level || 1
  const Tag = `h${level}` as keyof JSX.IntrinsicElements

  return (
    <Tag className="custom-heading" data-custom-id={customId}>
      {node.children?.map((child: any, i: number) => (
        <span key={i}>{child.content || ''}</span>
      ))}
    </Tag>
  )
}

// Register for a scoped customId
setCustomComponents('docs', { heading: CustomHeading })

function App() {
  const markdown = `# Custom Heading

This heading is rendered with a custom component.
`

  return <MarkdownRender customId="docs" content={markdown} />
}

Streaming Content

markstream-react supports streaming markdown content, which is useful for AI-generated content:

import MarkdownRender from 'markstream-react'
import { useState } from 'react'

function StreamingDemo() {
  const [markdown, setMarkdown] = useState('')
  const [isStreaming, setIsStreaming] = useState(false)

  const fullText = `# Streaming Demo

This content is being streamed **in small chunks**.

## Features

1. Progressive rendering
2. No layout shift
3. Smooth animations

\`\`\`javascript
const streaming = true
console.log('Streaming enabled:', streaming)
\`\`\`
`

  const startStreaming = () => {
    setIsStreaming(true)
    setMarkdown('')
    let i = 0

    const interval = setInterval(() => {
      if (i < fullText.length) {
        setMarkdown(prev => prev + fullText[i])
        i++
      }
      else {
        clearInterval(interval)
        setIsStreaming(false)
      }
    }, 20)

    return () => clearInterval(interval)
  }

  return (
    <div>
      <button onClick={startStreaming} disabled={isStreaming}>
        {isStreaming ? 'Streaming...' : 'Start Streaming'}
      </button>
      <MarkdownRender content={markdown} />
    </div>
  )
}

Using with React Hooks

import type { ChangeEvent } from 'react'
import MarkdownRender from 'markstream-react'
import { useCallback, useEffect, useState } from 'react'

function MarkdownEditor() {
  const [content, setContent] = useState('# Edit me!')
  const [html, setHtml] = useState('')

  const handleChange = useCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
    setContent(e.target.value)
  }, [])

  return (
    <div className="markdown-editor">
      <textarea
        value={content}
        onChange={handleChange}
        placeholder="Write your markdown here..."
        className="editor-input"
      />
      <div className="editor-preview">
        <MarkdownRender content={content} />
      </div>
    </div>
  )
}

Virtualized Lists

For large markdown documents, you can use virtualization:

import MarkdownRender from 'markstream-react'

function LongDocument() {
  // Your very long markdown content
  const markdown = `# Long Document`

  return (
    <MarkdownRender
      content={markdown}
      maxLiveNodes={200}
      liveNodeBuffer={10}
    />
  )
}

Common Props

Below are the most commonly used props. For the full list, see React Components.

Prop Type Default Description
content string - Markdown content to render
nodes BaseNode[] - Pre-parsed AST nodes (typically ParsedNode[])
customId string - Identifier for scoping
maxLiveNodes number 320 Max nodes for virtualization
liveNodeBuffer number 60 Buffer for overscan
batchRendering boolean true Enable batched rendering
deferNodesUntilVisible boolean true Defer heavy nodes
renderCodeBlocksAsPre boolean false Use <pre><code> fallback (Mermaid blocks will also fall back)

Styling

The default styles are scoped under .markstream-react class. You can override styles:

/* Your global styles */
.markstream-react {
  font-family: system-ui, -apple-system, sans-serif;
  line-height: 1.6;
}

.markstream-react h1 {
  font-size: 2.5rem;
  font-weight: 700;
  border-bottom: 2px solid #e5e7eb;
  padding-bottom: 0.5rem;
}

.markstream-react code {
  background: #f3f4f6;
  padding: 0.2rem 0.4rem;
  border-radius: 0.25rem;
}

Using with Tailwind CSS

import MarkdownRender from 'markstream-react'
import 'markstream-react/index.css'
import './output.css' // Your Tailwind output

function App() {
  return (
    <div className="max-w-4xl mx-auto py-8">
      <MarkdownRender content="# Hello Tailwind!" />
    </div>
  )
}

Next Steps