Skip to main content
Static pages are built from individual Notion pages that are fetched at build time and rendered as HTML.

Overview

Unlike blog posts and projects which use Notion databases, static pages are individual Notion pages that are fetched at build time. This is useful for content-heavy pages like your homepage, about page, or custom landing pages.

Static Pages vs Collections

This site uses two different Notion content strategies:
TypeSourceUse CaseSync Method
Static PagesIndividual Notion pagesHomepage, about, custom pagesFetched at build time via API
CollectionsNotion databasesBlog posts, projects, repeated contentPre-build sync to MDX files
Static pages are fetched fresh during each build using the Notion API, while collection content is synced to local MDX files during the pre-build step.

Environment Variables

Static pages are referenced by their Notion page IDs in environment variables:
# Individual page IDs
NOTION_PAGE_ID_HOME=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
NOTION_PAGE_ID_BLOG=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
NOTION_PAGE_ID_READ=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
NOTION_PAGE_ID_PLACES=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# You can add more custom pages
NOTION_PAGE_ID_ABOUT=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
NOTION_PAGE_ID_CONTACT=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Getting a Notion Page ID

1

Find your Notion page

Navigate to the page you want to use in Notion.
2

Copy the page link

Click “Share” → “Copy link” or click the ... menu → “Copy link”.
3

Extract the page ID

The URL looks like:
https://www.notion.so/Page-Title-abc123def456...
The page ID is the 32-character string at the end (with dashes removed):
abc123def456...
4

Add to environment variables

Add it to your .env file:
NOTION_PAGE_ID_ABOUT=abc123def456...

Creating a Static Page

1

Create a Notion page

Create a new page in Notion with your content. Use any Notion blocks you want - headings, paragraphs, images, etc.
2

Share with integration

Make sure your Notion integration has access to this page:
  • Click “Share” in the top right
  • Invite your integration
3

Get the page ID

Follow the steps above to extract the page ID.
4

Create an Astro page

Create a new file in src/pages/ (e.g., src/pages/about.astro):
---
import Layout from '@layouts/Layout.astro';
import { getPage } from '@lib/notion-cms-page';
import { getBlock } from '@lib/notion-cms';
import { parseBlocks } from '@lib/notion-parse';

// Fetch page data at build time
const pageId = import.meta.env.NOTION_PAGE_ID_ABOUT;
const page = await getPage(pageId);
const blocks = await getBlock(pageId);
const content = parseBlocks(blocks);

// Get page title from properties
const title = page.properties.title?.title?.[0]?.plain_text || 'About';
---

<Layout 
  title={title} 
  description="About me" 
  path="/about"
>
  <Fragment slot="content">
    <article class="prose prose-stone" set:html={content} />
  </Fragment>
</Layout>
5

Build and test

Run your development server:
pnpm dev
Navigate to /about to see your page.

Fetching Page Content

There are two main functions for working with Notion pages:

getPage()

Fetches page metadata and properties (src/lib/notion-cms-page.ts:9-25):
import { getPage } from '@lib/notion-cms-page';

const page = await getPage(pageId);

// Access properties
const title = page.properties.title?.title?.[0]?.plain_text;

getBlock()

Fetches page content blocks recursively (src/lib/notion-cms.ts):
import { getBlock } from '@lib/notion-cms';
import { parseBlocks } from '@lib/notion-parse';

const blocks = await getBlock(pageId);
const markdown = parseBlocks(blocks);

Full Example

---
import { getPage } from '@lib/notion-cms-page';
import { getBlock } from '@lib/notion-cms';
import { parseBlocks } from '@lib/notion-parse';

// 1. Fetch page metadata
const page = await getPage(import.meta.env.NOTION_PAGE_ID_HOME);

// 2. Fetch content blocks
const blocks = await getBlock(page.id);

// 3. Convert to markdown/HTML
const content = parseBlocks(blocks);
---

<!-- 4. Render the content -->
<div set:html={content} />

Build-Time Rendering

Static pages are rendered at build time, not on every request:
Build starts → Fetch Notion page → Parse blocks → Generate HTML → Static site
This means updates to your Notion page won’t appear on your site until you rebuild and redeploy. This is intentional for performance and reliability.

Updating Static Pages

1

Edit in Notion

Make changes to your Notion page content.
2

Trigger a rebuild

Rebuild your site locally or trigger a deployment:
pnpm build
Or push to your main branch to trigger automatic deployment.
3

Verify changes

The updated content will be fetched and rendered during the build process.

Advanced: Custom Content Processing

You can process Notion content before rendering:
---
import { getBlock } from '@lib/notion-cms';
import { parseBlocks } from '@lib/notion-parse';
import { marked } from 'marked';

const blocks = await getBlock(pageId);
const markdown = parseBlocks(blocks);

// Custom markdown processing
const html = marked.parse(markdown, {
  renderer: new marked.Renderer(),
  gfm: true,
  breaks: true
});
---

<div set:html={html} />

Example: Homepage with Sections

You can structure complex pages by using Notion’s heading blocks as section dividers:
---
import { getBlock } from '@lib/notion-cms';

const blocks = await getBlock(import.meta.env.NOTION_PAGE_ID_HOME);

// Find heading blocks to create sections
const sections = [];
let currentSection = { title: '', blocks: [] };

for (const block of blocks) {
  if (block.type === 'heading_1') {
    if (currentSection.title) sections.push(currentSection);
    currentSection = { 
      title: block.heading_1.rich_text[0]?.plain_text,
      blocks: [] 
    };
  } else {
    currentSection.blocks.push(block);
  }
}
sections.push(currentSection);
---

{sections.map(section => (
  <section>
    <h2>{section.title}</h2>
    <div set:html={parseBlocks(section.blocks)} />
  </section>
))}
The site’s navigation menu is also dynamically generated from Notion (NOTION_DB_ID_PAGES):
// From src/components/Navigation.astro
const pages = await queryNotionDatabase(
  import.meta.env.NOTION_DB_ID_PAGES,
  { sorts: [{ property: 'order', direction: 'ascending' }] }
);
To add a page to navigation:
  1. Add entry to the NOTION_DB_ID_PAGES database
  2. Set the name, path, and order properties
  3. Rebuild your site

Best Practices

Name your page IDs clearly:
NOTION_PAGE_ID_ABOUT=xxx
NOTION_PAGE_ID_CONTACT=xxx
NOTION_PAGE_ID_PORTFOLIO=xxx
This makes it easy to understand which page is which.
Static pages work best for:
  • Landing pages (homepage, about)
  • Policy pages (privacy, terms)
  • Long-form content pages
Use collections (blog/projects) for repeated content types.
Since content is fetched at build time, make sure your deployment triggers a fresh build when you want updates to go live.
Use Notion’s heading blocks (H1, H2, H3) to create clear content structure. This helps with SEO and makes content easier to navigate.

Troubleshooting

Page content not updating

Check:
  • You’ve triggered a rebuild after editing the Notion page
  • The page ID in your environment variables is correct
  • Your Notion integration still has access to the page

Build fails when fetching page

Check:
  • Page ID is correct (32 characters, no dashes)
  • Environment variable is set and accessible
  • Notion integration has access to the page
  • Notion API is accessible (check Notion status page)

Content rendering incorrectly

Check:
  • You’re using set:html to render the parsed content
  • The content is wrapped in an element with proper styling (e.g., prose class)
  • Block types are supported (see Notion Blocks)

When to Use Static Pages vs Collections

Choose static pages when:
  • Content is unique (homepage, about, contact)
  • You need full control over the page structure
  • Content doesn’t follow a repeating template
Choose collections (blog/projects) when:
  • You have multiple similar items (blog posts, projects)
  • Content follows a consistent template
  • You want to query, filter, and sort items
  • You need pagination or category pages

Build docs developers (and LLMs) love