Back to site

Notion Renderers

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.

NotionRichText

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)

From Notion API, verbatim.

Plain text

Hello, world.

Annotations

bold · italic · strikethrough · underline · code

Colors

orange · yellow · green · blue · purple · pink · red · brown · gray

Inline link

Mixed run

I build web apps with TypeScript and React (opens in new tab).

className (text-lg)

Larger text via className.

null item

NotionParagraph

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)

NotionHeading1

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

Section title

Block color

Blue heading.

Empty rich_text (null)

NotionHeading2

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

Subsection title

Block color

Red heading.

Empty rich_text (null)

NotionHeading3

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

Minor heading

Block color (background)

Green background.

Empty rich_text (null)

NotionBulletedListItem

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

  • First bulleted item

Rich text + color

  • Second item with inline code

Empty rich_text (null)

NotionQuote

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)

NotionDivider

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).

above
below

NotionImage

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)

Image

Caption → figcaption + alt from caption text

A mountain landscape, via picsum.photos.
A mountain landscape, via picsum.photos.

NotionBlock

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 — orchestrator demo

NotionBlock picks the right renderer based on block.type.

Supported so far

  • paragraph
  • heading_1, heading_2, heading_3
  • bulleted_list_item
  • quote
  • divider
  • image (NotionImage resolves on the server)

Most block renderers are synchronous; image delegates to async NotionImage.
Image via orchestrator.
Image via orchestrator.