notion-kit

Getting started

Render a Notion page in a Next.js App Router project.

1. Create an integration

Create an internal integration and copy its token. Then share your page or database with it: open it in Notion, ⋯ → Connections, and add the integration.

.env.local
NOTION_TOKEN=ntn_...

2. Install

npm install @yokinist/notion-fetch @yokinist/notion-rsc @notionhq/client

3. Render a page

app/notion.ts
import { Client } from "@notionhq/client";
import { createRequestLimiter, createTimeoutFetch } from "@yokinist/notion-fetch";

export const notion = new Client({
  auth: process.env.NOTION_TOKEN,
  fetch: createTimeoutFetch(), // aborts requests that stall
});
export const limit = createRequestLimiter();
app/[id]/page.tsx
import { fetchBlockTree } from "@yokinist/notion-fetch";
import { NotionBlocks } from "@yokinist/notion-rsc";
import { limit, notion } from "../notion";

export const revalidate = 60;

export default async function Page({ params }: PageProps<"/[id]">) {
  const { id } = await params;
  const blocks = await fetchBlockTree(notion, id, { limit });
  return (
    <article className="prose">
      <NotionBlocks
        blocks={blocks}
        fileUrl={(blockId) => `/api/notion/file/${blockId}`}
      />
    </article>
  );
}
app/layout.tsx
import "@yokinist/notion-rsc/styles.css";

4. Serve uploaded files

Notion's URLs for uploaded files expire after an hour. Add a route that redirects to a fresh one, so cached pages keep their images:

app/api/notion/file/[id]/route.ts
import { createFileHandler } from "@yokinist/notion-fetch";
import { limit, notion } from "../../../../notion";

export const GET = createFileHandler({ notion, limit });

That's it. See the demo for what each block looks like.

Not using Next.js?

notion-rsc needs only React 19 Server Components, so it works in React Router, TanStack Start and other RSC frameworks; see the examples. createFileHandler takes a Web-standard Request and returns a Response, so it works with Hono, Cloudflare Workers and the like.

On this page