<MarkdownRenderer content={body}> parses a markdown string and emits the same components a hand-built page would use (SectionHeading, BulletList, CodeBlock, TipBox/WarningBox/InfoBox, body <Text>). It's the bridge for content authored in .md rather than TSX.
Source: src/components/MarkdownRenderer.tsx. Parser: src/utils/markdownParser.ts.
Two pages — 01-markdown-automation.tsx and 03-supported-elements.tsx — read content/chapters/12-markdown-demo.md at build time. Each strips the YAML-style frontmatter, splits on the authored page-break marker, and passes one half to <MarkdownRenderer>:
const mdPath = path.join(process.cwd(), 'content/chapters/12-markdown-demo.md');
const content = fs.readFileSync(mdPath, 'utf-8');
const body = content.replace(/^---[\s\S]*?---/, '').trim();
const [partOne, partTwo] = body.split('\n<!-- page-break -->\n');
<MarkdownRenderer content={partOne.trim()} /> // first page
<MarkdownRenderer content={partTwo.trim()} /> // second pageThe other files in content/chapters/ are author drafts, not loaded by the build.
The parser is intentionally minimal — it covers what this project's chapter format needs and nothing else.
| Markdown | Renders as |
|---|---|
# Heading |
<Text style={styles.h1}> |
## Heading |
<SectionHeading> (gold bar + h2) |
### Heading |
<Text style={styles.h3}> |
* item, - item, or + item (consecutive lines) |
<BulletList items={[...]} /> |
```lang\ncode\n``` |
<CodeBlock language="lang">{code}</CodeBlock> |
> [!TIP] block (see below) |
<TipBox> |
> [!WARNING] block |
<WarningBox> |
> [!INFO] block |
<InfoBox> |
**bold** (inline) |
<Text style={styles.bold}> span |
*italic* / _italic_ (inline) |
<Text style={styles.italic}> span |
`code` (inline) |
<Text style={styles.inlineCode}> span |
| Other text (consecutive lines) | <Text style={styles.body}> (joined with spaces) |
Anything not in this table generally falls through to body text, so links, images, horizontal rules, HTML comments, and markdown tables render as literal characters. Plain blockquotes and malformed or unsupported alert variants are the exception: the parser strips the leading > and renders the remaining text as ordinary body copy. Inline **bold**, *italic*, and `code` runs are recognized inside headings, paragraphs, list items, and callout bodies (underscore italics require non-alphanumeric boundaries, so snake_case identifiers are left alone).
GitHub-flavored alert syntax with an optional inline label:
> [!TIP] label="Pro tip"
> Multi-line callout
> body lines start with `>`.
> [!WARNING]
> Defaults to label "WARNING".The variant token (TIP/WARNING/INFO) is matched case-insensitively. The label string falls back to the uppercase variant name.
Consecutive non-blank, non-special lines are joined with a single space — newlines inside a paragraph become spaces. Use a blank line to start a new paragraph.
- Loading authored content from
.mdfiles at build time (current pattern: one demo chapter split across two page components). - Roundtripping content from a CMS or a markdown-first authoring workflow.
For chapters where the layout matters (cross-page splits, custom callouts, side-by-side comparisons), write TSX directly — the parser doesn't expose enough of the component library to compose those. See add-a-page.
- Build-time read. Both markdown-automation pages use
fs.readFileSyncsynchronously inside their React component bodies. That works becausetsxruns the files in Node — but it ties the build to the markdown file's presence on disk. Move the read into a build-time prep step if you want to ship the rendered chapter without the source file. - No links, no images. Link and image syntax pass through as raw characters. Use TSX if you need those.
- Code-block language label is cosmetic.
langis a string passed to<CodeBlock>, which renders it as the gold label only. It does not select a highlighter:src/utils/syntaxHighlight.tsis one language-agnostic JS/TS tokenizer applied to every block regardless of the label. See syntax-highlighting reference for what it recognizes.