Notionでポートフォリオサイトを
管理しよう!

〜JSONからNotionDBへの移行ハンズオン〜

リンク

今日のゴール

既存のポートフォリオサイト

NotionDBで管理できるように改修する!


NotionDB
Notion API
Next.js
サイト表示

CMSとは?

CMS = Content Management System


Webサイトのコンテンツを簡単に管理・更新できるシステム

  • 非エンジニアでもコンテンツ更新が可能
  • コンテンツとデザインの分離
  • 一元管理で保守性アップ

代表的なCMS: WordPress, Strapi, Contentful, Notion

なぜNotionをCMSに?

メリット

  • 直感的なUI — 表計算感覚で編集
  • リアルタイム更新 — 編集が即座に反映
  • 無料枠が充実 — 個人利用なら十分
  • チーム共有 — 複数人で管理可能

従来の方法

  • JSONファイルを直接編集
  • エンジニアしか更新できない
  • 修正ミスが怖い
  • 運用コストが高い

完成イメージ

NotionDBで管理する項目

プロパティ
Name タイトル
説明 リッチテキスト
カテゴリ セレクト
URL URL
作成日 日付
サムネイル ファイル

できること

  • Notionでデータを追加 → サイトに自動反映
  • カテゴリでフィルター
  • サムネイル画像も管理

手順
1
: NotionDBの作成

  1. 「Notionで管理するポートフォリオサイト」ページで空のデータベースを作成
  2. 以下のプロパティを追加:
プロパティ名
Name タイトル(デフォルト)
URL URL
カテゴリ セレクト
サムネイル ファイル
説明 リッチテキスト
作成日 日付

⚠️ 注意: URLプロパティはAPIで userDefined:URL として返される場合あり

手順
2
: Notion Integrationの作成

  1. https://www.notion.so/my-integrations にアクセス
  2. 「新規コネクト」をクリック
  3. 名前を入力(例: portfolio_sample
  4. 発行されたアクセストークンntn_xxx)を控える

💡 このトークンは後で .env.local に設定します

手順
3
: DBとIntegrationを接続

  1. 作成したIntegrationを選択
  2. 「コンテンツへのアクセス」でアクセス権限を編集
  3. 作成したDBのページを追加

⚠️ 重要: この接続を忘れるとAPIからDBにアクセスできません!

手順
4
: 環境変数の設定

プロジェクトルートに .env.local を作成:

NOTION_API_KEY=ntn_xxx
NOTION_DATABASE_ID=xxxxxx

Database IDの見つけ方

https://www.notion.so/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx?v=yyy
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                      この32桁のUUIDがDatabase ID

手順
5
: Notion APIクライアント作成

lib/notion.ts を新規作成:

export const getPortfolioItems = async () => {
  const res = await fetch(
    `https://api.notion.com/v1/databases/${process.env.NOTION_DATABASE_ID}/query`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.NOTION_API_KEY}`,
        "Notion-Version": "2022-06-28",  // ← 必須ヘッダー
        "Content-Type": "application/json",
      },
    }
  );
  const data = await res.json();
  return data.results;
};

手順
6
: データ取得処理を書き換え

lib/portfolio.ts を編集:

function formatNotionItem(item: any): PortfolioItem {
  const p = item.properties;
  return {
    id: item.id,
    title: p.Name?.title?.[0]?.plain_text || "",
    description: p.説明?.rich_text?.[0]?.plain_text || "",
    category: p.カテゴリ?.select?.name || "",
    url: p["userDefined:URL"]?.url || p.URL?.url || "",
    createdAt: p.作成日?.date?.start || "",
    thumbnail: p.サムネイル?.files?.[0]?.file?.url || "",
  };
}

💡 Notion APIのレスポンスはネストが深いので、フラットに整形するのがポイント

手順
7
: ページ側のimportを修正

app/page.tsx のimportを変更:

// Before
import { getPortfolioItems } from "@/lib/portfolio";

// After
import { getPortfolioItemsFormatted } from "@/lib/portfolio";

データ取得も変更:

const items = await getPortfolioItemsFormatted();

手順
8
: リンクを条件付き表示

URLが空の場合に備えてガード:

// Before
<a href={item.url} target="_blank">...</a>

// After
{item.url && (
  <a href={item.url} target="_blank">...</a>
)}

⚠️ 空のURLで target="_blank" を設定すると about:blank が開いてしまう

動作確認


チェックリスト

  • [ ] .env.local が正しく設定されている
  • [ ] NotionDBとIntegrationが接続されている
  • [ ] npm run dev で開発サーバー起動
  • [ ] NotionDBのデータがサイトに表示される
  • [ ] DBのデータを編集 → サイトが連動して変わる

まとめ


今日学んだこと

  1. NotionをCMSとして使うメリット
  2. Notion APIの基本的な使い方
  3. プロパティ型ごとのレスポンス構造の違い
  4. Next.jsとの連携方法

次のステップ

  • フィルター・ソートを使ってみる
  • ページネーション対応(100件超の場合)

参考リンク



お疲れさまでした!