astro-notion-blogに固定ページ機能を追加する方法

概要

astro-notion-blogをベースにしたブログサイトに、Aboutページやプライバシーポリシーなどの固定ページを追加する方法を解説します。

Notionのデータベース内の記事を固定ページとして利用しつつ、記事一覧には表示しない仕組みを実装します。

Image in a image block

要件

  • /about/privacy-policyなどのURLで固定ページにアクセスできる
  • 固定ページはNotionで管理(通常の記事と同じワークフロー)
  • 記事一覧やサイドバーには固定ページを表示しない
  • ヘッダーナビゲーションから固定ページにアクセスできる

実装方針

Slug方式を採用

環境変数でPage IDを指定する方式ではなく、Slugでフィルタする方式を採用しました。

観点 環境変数方式 Slug方式
シンプルさ △ 環境変数管理が必要 ◎ コードで完結
管理の一貫性 △ 別管理になる ◎ 他の記事と同じ
Notion側の操作 △ Page ID調べる必要 ◎ Slugを設定するだけ

ファイル構成

src/
├── lib/
│   └── blog-helpers.ts    # 除外Slugリストとフィルタ関数を追加
├── layouts/
│   └── Layout.astro       # ヘッダーナビゲーションにリンク追加
└── pages/
    ├── about.astro        # 新規作成
    ├── privacy-policy.astro # 新規作成
    ├── index.astro        # フィルタ適用
    └── posts/
        ├── [slug].astro   # フィルタ適用
        ├── page/[page].astro # フィルタ適用
        └── tag/
            ├── [tag].astro # フィルタ適用
            └── [tag]/page/[page].astro # フィルタ適用

実装手順

1. 除外Slugリストとフィルタ関数の定義

src/lib/blog-helpers.tsに追加:

import type { Post } from './interfaces'

// 記事一覧から除外する固定ページのSlugリスト
export const EXCLUDED_SLUGS = ['about', 'privacy-policy']

// 固定ページを除外するフィルタ関数
export const filterExcludedPosts = (posts: Post[]): Post[] => {
  return posts.filter((post) => !EXCLUDED_SLUGS.includes(post.Slug))
}
2. 固定ページの作成

src/pages/about.astroを新規作成:

---
import {
  getPosts,
  getRankedPosts,
  getPostBySlug,
  getBlock,
  getAllTags,
  getAllBlocksByBlockId,
  downloadFile,
} from '../lib/notion/client.ts'
import { filePath, extractTargetBlocks, filterExcludedPosts } from '../lib/blog-helpers.ts'
import Layout from '../layouts/Layout.astro'
import PostBody from '../components/PostBody.astro'
import BlogPostsLink from '../components/BlogPostsLink.astro'
import BlogTagsLink from '../components/BlogTagsLink.astro'
import styles from '../styles/blog.module.css'

const post = await getPostBySlug('about')
if (!post) {
  throw new Error('About page not found. Please create a post with slug "about" in Notion.')
}

const [blocks, rankedPostsRaw, recentPostsRaw, tags] = await Promise.all([
  getAllBlocksByBlockId(post.PageId),
  getRankedPosts(),
  getPosts(5),
  getAllTags(),
])

const rankedPosts = filterExcludedPosts(rankedPostsRaw)
const recentPosts = filterExcludedPosts(recentPostsRaw)

// 画像ダウンロード処理(省略)
---

<Layout
  title={post.Title}
  description={post.Excerpt}
  path="/about"
  ogImage={ogImage}
>
  <div slot="main" class={styles.main}>
    <div class={styles.post}>
      <PostBody blocks={blocks} />
    </div>
  </div>

  <div slot="aside" class="aside">
    <BlogPostsLink heading="Recommended" posts={rankedPosts} />
    <BlogPostsLink heading="Latest posts" posts={recentPosts} />
    <BlogTagsLink heading="Categories" tags={tags} />
  </div>
</Layout>
3. 記事一覧へのフィルタ適用

src/pages/index.astroなどでフィルタを適用:

---
import { filterExcludedPosts } from '../lib/blog-helpers.ts'

const [allPosts, allRankedPosts, tags, numberOfPages] = await Promise.all([
  getPosts(NUMBER_OF_POSTS_PER_PAGE),
  getRankedPosts(),
  getAllTags(),
  getNumberOfPages(),
])

// 固定ページを除外
const posts = filterExcludedPosts(allPosts)
const rankedPosts = filterExcludedPosts(allRankedPosts)
---
4. ヘッダーナビゲーションにリンク追加

src/layouts/Layout.astroのヘッダーナビゲーションに追加:

<a href={getNavLink('/about')} class="nav-item">
  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
    <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
    <circle cx="12" cy="7" r="4"></circle>
  </svg>
  <span>About</span>
</a>

Notion側の設定

  1. データベースで新しいページを作成
  2. Slugプロパティaboutまたはprivacy-policyに設定
  3. Publishedプロパティにチェック
  4. Dateプロパティに今日以前の日付を設定

拡張性

新しい固定ページを追加する場合:

  1. EXCLUDED_SLUGS配列に新しいSlugを追加
  2. 対応する.astroファイルを作成
  3. Notionで該当Slugの記事を作成
export const EXCLUDED_SLUGS = ['about', 'privacy-policy', 'terms', 'contact']

まとめ

Slug方式を採用することで、以下のメリットが得られます:

  • Notion側での管理が簡単(Slugを設定するだけ)
  • 環境変数の管理が不要
  • 他の記事と同じワークフローで編集可能
  • フィルタ関数の再利用で保守性が向上