Renderers map Notion API types to markup (`@/notion`). NotionImage is async on the server (it calls dynamicImage); keep it out of Client Components. Demos use mocks. Compose manually or use NotionBlock to dispatch by block.type.
Inline renderer for `RichTextItemResponse[]` (annotations, colors, links, equations). Same-host links use Next.js `Link`; other origins open in a new tab. `item` null → renders nothing.
Pass-through (API-shaped item)
Plain text
Annotations
codeColors
Inline link
Mixed run
TypeScript and React (opens in new tab).className (text-lg)
className.null item
Maps a Notion `paragraph` block to a semantic `<p>`, with default body typography and `paragraph.color`. Empty `rich_text` → `null`.
Default color
A plain paragraph, nothing fancy.
Rich text runs
A paragraph with bold, italic, and code runs.
Block color (foreground)
Block color: blue.
Block color (background)
Block color: yellow_background.
Empty rich_text (null)
Maps a Notion `heading_1` block to `<h2>` (the page keeps the real `<h1>`). Default typography plus `heading_1.color` (same palette as paragraphs). Empty `rich_text` → `null`. Pure renderer — `is_toggleable` not handled here (disclose upstream if needed).
Default
Block color
Empty rich_text (null)
Maps a Notion `heading_2` block to `<h3>`. Default typography plus `heading_2.color`. Empty `rich_text` → `null`. Pure renderer — `is_toggleable` not handled here.
Default
Block color
Empty rich_text (null)
Maps a Notion `heading_3` block to `<h4>`. Default typography plus `heading_3.color`. Empty `rich_text` → `null`. Pure renderer — `is_toggleable` not handled here.
Default
Block color (background)
Empty rich_text (null)
One Notion `bulleted_list_item` → its own `<ul><li>` (Notion stores one block per row). Maps `bulleted_list_item.color`. Empty `rich_text` → `null`. Need a real list: merge sibling blocks upstream, or accept stacked `<ul>`s.
Default
Rich text + color
inline codeEmpty rich_text (null)
Notion `quote` block → `<blockquote>` with border, tint, shadow. Merges `quote.color` with that chrome. Empty `rich_text` → `null`.
Default
The best code is the one you don't write.
Rich text + color
Simplicity is the ultimate sophistication.
Empty rich_text (null)
Notion `divider` block → `<hr>`. No rich text or color on the API — pass the block for parity with other renderers (`block.id` on the element).
Pass the Notion `image` block only; the component awaits `dynamicImage()` (sharp + fetch) on the server. Do not import from Client Components. `alt` uses caption plain text when non-empty; otherwise `Image`. Caption row only when trimmed caption text exists. Demo uses picsum.photos.
Block only (server resolves URL + placeholder)
Caption → figcaption + alt from caption text
Orchestrates one Notion block: switches on `block.type` and renders the matching component. `image` blocks render `NotionImage`, which loads metadata via `dynamicImage()` on the server. Blocks with empty `rich_text` omit the outer `<section>` unless `children` (nested blocks) are provided. Otherwise each block is wrapped in `<section>` with shared vertical rhythm (`smallSpaces` tightens).
Sample strip (mixed block types)
NotionBlock picks the right renderer based on block.type.
Most block renderers are synchronous;imagedelegates to asyncNotionImage.