コンテンツへスキップ
My Site

loader()

The loader() function is a custom Astro Content Loader that fetches pages from a Notion data source and stores them in the Content Collection store.

Import

import { loader } from "notro-loader";

Usage

// src/content.config.ts
import { defineCollection } from "astro:content";
import { loader } from "notro-loader";

export const collections = {
  posts: defineCollection({
    loader: loader({
      queryParameters: {
        data_source_id: import.meta.env.NOTION_DATASOURCE_ID,
        filter: { property: "Public", checkbox: { equals: true } },
      },
      clientOptions: { auth: import.meta.env.NOTION_TOKEN },
    }),
  }),
};

Options

interface LoaderOptions {
  queryParameters: DataSourceQueryParameters;
  clientOptions: ClientOptions;
}

queryParameters

Parameters passed to notion.dataSources.query. The data_source_id is required; all other keys are optional.

queryParameters: {
  data_source_id: string;       // Required: Notion database UUID
  filter?: FilterObject;        // Optional: Notion filter
  sorts?: SortObject[];         // Optional: sort order
  page_size?: number;           // Optional: results per page (max 100)
}
Filter examples:
// Simple checkbox filter
filter: { property: "Public", checkbox: { equals: true } }

// AND filter
filter: {
  and: [
    { property: "Public", checkbox: { equals: true } },
    { property: "Tags", multi_select: { contains: "featured" } },
  ],
}
Sort examples:
// Sort by Date descending
sorts: [{ property: "Date", direction: "descending" }]

// Sort by last edited time
sorts: [{ timestamp: "last_edited_time", direction: "descending" }]

clientOptions

Options passed to the @notionhq/client Client constructor.

clientOptions: {
  auth: string;               // Required: Notion API token
  notionVersion?: string;     // Optional: API version (default: latest)
  timeoutMs?: number;         // Optional: request timeout in ms
}

What the loader stores

For each Notion page, the loader stores an entry with:

{
  id: string;              // Notion page UUID
  markdown: string;        // Preprocessed markdown content
  last_edited_time: string; // ISO 8601 timestamp
  properties: {
    // Raw Notion property objects — shape depends on your database schema
    Name: { title: [...] },
    Slug: { rich_text: [...] },
    // ...
  };
}

Use pageWithMarkdownSchema from notro-loader as the base Zod schema to type these fields, then extend it with your database's specific properties.

Cache behavior

The loader caches pages between builds using Astro's Content Layer store. An entry is refreshed when:

  • The Notion last_edited_time has advanced since the last build

  • The cached markdown contains expired Notion pre-signed S3 URLs (detected by X-Amz-Expires in image URLs)

  • The page no longer exists in Notion (entry is deleted from the store)

Pages whose last_edited_time has not changed are not re-fetched, making incremental builds fast.

Error handling

SituationBehavior
429 rate_limitedRetry with exponential backoff (1 s, 2 s, 4 s; max 3 retries)
500 / 503 server errorRetry with exponential backoff
401 unauthorizedLog warning, skip page
403 restricted_resourceLog warning, skip page
404 object_not_foundLog warning, remove from store
Content truncatedLog warning, use truncated content
Unknown block IDsLog warning with block ID list, continue

The build continues even when individual pages fail.

Live loader (dev only)

For development environments that require real-time content updates without a server restart, use liveLoader():

import { liveLoader } from "notro-loader";

export const collections = {
  posts: defineCollection({
    loader: liveLoader({
      queryParameters: { data_source_id: import.meta.env.NOTION_DATASOURCE_ID },
      clientOptions: { auth: import.meta.env.NOTION_TOKEN },
    }),
  }),
};

liveLoader() re-fetches content on every request during astro dev. It is not recommended for production builds.

Type reference

function loader(options: LoaderOptions): AstroContentLoader;
function liveLoader(options: LoaderOptions): AstroContentLoader;

interface LoaderOptions {
  queryParameters: {
    data_source_id: string;
    filter?: unknown;
    sorts?: unknown[];
    page_size?: number;
  };
  clientOptions: {
    auth: string;
    notionVersion?: string;
    timeoutMs?: number;
  };
}