Examples
The repository has four example projects in examples/ . All four build the same site from one Notion database: an index page with the database as a table, and one page per row with its content. They differ in the framework, and in whether the pages are built ahead of time or rendered on a server.
| Example | Framework | Fetches data in |
|---|---|---|
| nextjs-approuter | Next.js App Router | Server components, generateStaticParams, generateMetadata |
| nextjs-server | Next.js App Router on a Node.js server | Server components rendered on request, with revalidate |
| nextjs-pagerouter | Next.js Pages Router | getStaticProps, getStaticPaths |
| astro | Astro with @astrojs/react | The frontmatter of .astro pages, getStaticPaths |
Prepare a database
Create a Notion database with these properties, add a few rows with some content, and connect your integration to it (see Getting started):
| Property | Type |
|---|---|
Title | Title |
Tags | Multi-select |
Date | Date |
The names must match: the examples pass ['Title', 'Tags', 'Date'] as keys and link the Title column to /[id].
Run an example
Each example needs two variables: NOTION_TOKEN, read by Rotion, and NOTION_DATABASE_ID, read by the example’s code. The Next.js examples load them from .env.local:
cd examples/nextjs-approuter
cat > .env.local <<'EOF'
NOTION_TOKEN=ntn_xxxxxxxxxxxxxxxxxxxxxxxx
NOTION_DATABASE_ID=668d797c76fa49349b05ad288df2d136
EOF
npm install
npm run devnpm run build writes a static export into out/, except in nextjs-server, which is started with npm start after the build. The examples install rotion from npm, not from the repository; their READMEs describe how to try a local build with npm pack.
nextjs-approuter
app/compornents/ClientLink.tsxwrapsnext/linkwithcreateClientLink.app/page.tsxfetches the database in a server component and renders it through a small client component around<Table>, withhref: { Title: '/[id]' }and the client link.app/compornents/Header.tsxis an async server component that fetches the same database again for its title; the second call is served from the cache.app/[id]/page.tsxlists the rows ingenerateStaticParams, fetches each page withFetchPage({ page_id, last_edited_time: 'force' })and its blocks with the page’slast_edited_time, reads the title frompage.meta, and showspage.icon?.srcwithnext/image.app/layout.tsximportsrotion/style-without-dark.css.next.config.tssetsoutput: 'export'andimages.unoptimized.
The walkthrough is in App Router.
nextjs-server
The same site as nextjs-approuter, rendered by next start instead of exported.
next.config.tshas nooutput: 'export'.app/page.tsxandapp/[id]/page.tsxexportrevalidate = 60.app/[id]/page.tsxhas nogenerateStaticParams, so each row’s page is rendered on its first request and regenerated at most once a minute..envis committed with settings that are not secret:ROTION_DOCROOT=storageandROTION_INCREMENTAL_CACHE=true.NOTION_TOKENandNOTION_DATABASE_IDgo in.env.localas in the other examples.npm run pruneremoves cache files and downloads that have not been used for a week, for a scheduled job. It is not part of the build: the pages are rendered on request, so a build touches only what it prerenders.app/images/[name]/route.tsandapp/files/[name]/route.tsserve the downloaded files fromstorage/throughlib/serveFile.ts, becausenext startserves only the files that were inpublic/when it started.
The walkthrough is in Server rendering.
nextjs-pagerouter
pages/index.tsxfetches the database ingetStaticPropsand renders<Table>withnext/linkpassed directly aslink.pages/[id].tsxreturns every row fromgetStaticPathswithfallback: false, and ingetStaticPropsfetches the page and the database in parallel, then the blocks.pages/_app.tsximportsrotion/style.css, so this example follows the system’s dark mode.
The walkthrough is in Pages Router.
astro
src/pages/index.astrofetches the database in its frontmatter and renders a React<Table>wrapper.src/pages/[id].astroreturns every row fromgetStaticPaths, and fetches each page and its blocks.src/components/NotionPage.tsxwraps<Page>. The pages hydrate the React components withclient:load.astro.config.mjsadds the React integration and setsoutput: 'static'.
The Astro example reads its variables from .env through import.meta.env:
cd examples/astro
cat > .env <<'EOF'
NOTION_TOKEN=ntn_xxxxxxxxxxxxxxxxxxxxxxxx
NOTION_DATABASE_ID=668d797c76fa49349b05ad288df2d136
EOF
npm install
npm run devAstro does not put .env into process.env, which is where Rotion looks for NOTION_TOKEN, so the pages hand the token over with configure({ auth: import.meta.env.NOTION_TOKEN }). Exporting the variable in the shell that runs the build (NOTION_TOKEN=... npm run build) works as well, regardless of how Astro loads .env. The development server runs at http://localhost:4321, and npm run build writes the site into dist/.