【Netlify】Functionsを使ってNotion APIと安全に連携する方法 - セットアップから実装まで

PUBLISHED 2026-02-07
UPDATED 2026-02-08

Notion をデータソースとして Web アプリを構築する場合、API トークンの扱いが問題になります。フロントエンドから直接 Notion API を呼ぶとトークンがブラウザに露出してしまうため、サーバー側にプロキシが必要です。

Netlify Functions を使えば、別途サーバーを用意せずにこの問題を解決できます。この記事では、Notion インテグレーションの作成から Netlify Functions による安全な API プロキシの構築までを解説します。

全体のアーキテクチャ

%%{init: {'theme':'neutral'}}%% graph LR A[ブラウザ] -->|/api/fetchNotion| B[Netlify CDN] B --> C[Netlify Functions] C -->|ntn_xxx...| D[Notion API] D --> C C --> B B --> A

ブラウザからは /api/fetchNotion を叩くだけで、Notion のトークンを知る必要がありません。トークンは Netlify Functions のサーバー側でのみ使用されます。

なぜ直接呼び出してはいけないのか

✅ Netlify Functions 経由(安全)
  • トークンはサーバー側の環境変数にのみ存在
  • ブラウザの開発者ツールからトークンが見えない
  • APIの呼び出し制御が可能
❌ フロントエンドから直接(危険)
  • トークンがJavaScriptに埋め込まれる
  • 開発者ツールのネットワークタブから丸見え
  • 第三者にデータベースを操作されるリスク
⚠️

Vite プロジェクトで VITE_NOTION_TOKEN のように VITE_ プレフィックスを付けると、トークンがフロントエンドのバンドルに含まれてしまいます。Notion トークンの環境変数名には VITE_ を付けず、NOTION_TOKEN としてください。

Notion 側の準備

インテグレーションの作成

1
Notion Integrations ページにアクセス

https://www.notion.so/profile/integrations にアクセスし、「新しいインテグレーション」を作成します。

2
トークンを取得

作成後に表示される ntn_xxx... 形式のトークンを控えます。このトークンが Notion API の認証に使われます。

3
データベースにインテグレーションを接続

使用する Notion データベースを開き、右上の「…」→「コネクト」から作成したインテグレーションを追加します。

⚠️

インテグレーションをデータベースに接続しないと、APIからアクセスできません。403エラーが返る場合はこの設定を確認してください。

🔑 トークンの形式について

2024年9月以降に作成されたインテグレーションのトークンは ntn_ プレフィックスで始まります。それ以前に作成されたトークン(secret_ プレフィックス)も引き続き使用できます。

データベースIDの取得

Notion データベースの URL からIDを取得します。

https://www.notion.so/ワークスペース名/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx?v=...
                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                      この部分がデータベースID

データベースのプロパティ設計

Notion データベースには、アプリで必要なプロパティを設定します。例えばブログ用途の場合は以下のようになります。

プロパティ名用途
ContentTitle記事タイトル
ImageFilesサムネイル画像
DateDate公開日
TagsMulti-selectタグ分類
CategoriesMulti-selectカテゴリ分類
SlugRich textURLスラッグ
💡 プロパティ名はAPIレスポンスのキーになる

Notion API のレスポンスでは properties.Contentproperties.Tags のようにプロパティ名がそのままキーとして返されます。スペースや日本語も使えますが、コードでの扱いやすさを考慮して英語の命名がおすすめです。

プロジェクトのセットアップ

ディレクトリ構成

project-root/
├── netlify/
│   └── functions/
│       └── fetchNotion.ts    ← Notion API プロキシ
├── netlify.toml
├── src/
│   └── services/
│       └── NotionData.ts     ← フロントエンドからの呼び出し
├── .env                      ← 環境変数(gitignore対象)
└── package.json

netlify.toml の設定

/api/* へのリクエストを Netlify Functions にルーティングします。

[build]
  command = "npm run build"
  publish = "dist"
  functions = "netlify/functions"

[dev]
  targetPort = 5173

[[redirects]]
  from = "/api/*"
  to = "/.netlify/functions/:splat"
  status = 200

環境変数の設定

Notion 連携に必要な環境変数は2つです。

変数名説明
NOTION_TOKENインテグレーショントークンntn_xxxxxxxxxxxxxxxx
NOTION_DATABASE_IDデータベースIDxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

ローカル開発用(.env ファイル)

プロジェクトルートに .env ファイルを作成します。

NOTION_TOKEN=ntn_xxxxxxxxxxxxxxxxxxxxxxxx
NOTION_DATABASE_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
⚠️

.env ファイルは必ず .gitignore に含めてください。トークンがリポジトリにコミットされると、誰でもデータベースにアクセスできてしまいます。

📌

環境変数名に VITE_ プレフィックスを付けないでください。VITE_ 付きの変数はビルド時にフロントエンドのコードにインライン展開され、ブラウザからトークンが見えてしまいます。Netlify Functions はサーバー側で実行されるため、VITE_ なしの変数名で安全にアクセスできます。

本番環境(Netlify ダッシュボード)

1
Netlify ダッシュボードにアクセス

対象サイトの管理画面を開きます。

2
Environment variables を開く

Site configurationEnvironment variables に移動します。

3
変数を追加

NOTION_TOKENNOTION_DATABASE_ID を登録します。値はローカルの .env と同じものを設定します。

🔄 コードの変更は不要

ローカルでも本番でも process.env.NOTION_TOKEN で参照できます。Netlify が環境に応じて .env またはダッシュボードの値を自動的に読み込みます。

Netlify Functions の実装

データベース取得関数

Notion API からデータベースの内容を取得するプロキシ関数を実装します。

// netlify/functions/fetchNotion.ts
export async function handler(event, context) {
  const token = process.env.NOTION_TOKEN;
  const databaseId = process.env.NOTION_DATABASE_ID;

  if (!token || !databaseId) {
    return {
      statusCode: 500,
      body: JSON.stringify({ error: "環境変数が設定されていません" }),
    };
  }

  try {
    const response = await fetch(
      `https://api.notion.com/v1/databases/${databaseId}/query`,
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${token}`,
          "Notion-Version": "2022-06-28",
          "Content-Type": "application/json",
        },
      }
    );

    if (!response.ok) {
      const errorData = await response.json();
      return {
        statusCode: response.status,
        body: JSON.stringify(errorData),
      };
    }

    const data = await response.json();

    return {
      statusCode: 200,
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(data),
    };
  } catch (error) {
    return {
      statusCode: 500,
      body: JSON.stringify({ error: "Notion APIリクエストに失敗しました" }),
    };
  }
}

Notion API のレスポンス構造

API から返されるデータは以下のような構造です。各プロパティの型に応じて値の取り出し方が異なります。

// レスポンスの results 配列の各要素
{
  id: "page-id",
  properties: {
    Content: {
      type: "title",
      title: [{ plain_text: "記事タイトル" }]
    },
    Tags: {
      type: "multi_select",
      multi_select: [{ name: "JavaScript" }, { name: "React" }]
    },
    Date: {
      type: "date",
      date: { start: "2026-02-07" }
    },
    Slug: {
      type: "rich_text",
      rich_text: [{ plain_text: "my-article-slug" }]
    }
  }
}

プロパティの値を取り出すヘルパー

// レスポンスデータの整形例
function formatResults(results) {
  return results.map((page) => ({
    id: page.id,
    title: page.properties.Content.title[0]?.plain_text || "",
    tags: page.properties.Tags.multi_select.map((t) => t.name),
    date: page.properties.Date.date?.start || "",
    slug: page.properties.Slug.rich_text[0]?.plain_text || "",
    image: page.properties.Image.files[0]?.file?.url || "",
  }));
}
⚠️ 画像URLの有効期限

Notion API から返される画像URLには有効期限(約1時間)があります。長期間キャッシュする場合は、画像を別のストレージにコピーする必要があります。

フロントエンドからの呼び出し

フロントエンドからは Notion の存在を意識せず、自分のAPIとして呼び出すだけです。

// src/services/NotionData.ts
export async function fetchArticles() {
  const response = await fetch("/api/fetchNotion");

  if (!response.ok) {
    throw new Error("データの取得に失敗しました");
  }

  const data = await response.json();
  return data.results;
}

フロントエンドのコードにはトークンもデータベースIDも一切含まれません。

ローカル開発

Netlify CLI でのテスト

Notion 連携を含めたローカル開発には netlify dev を使います。

npx netlify dev

このコマンドで、フロントエンドの開発サーバーと Netlify Functions が同時に起動します。

対象URL
フロントエンドhttp://localhost:8888
Notion API プロキシhttp://localhost:8888/api/fetchNotion
💡 npm run dev との違い

npm run dev はフロントエンドのみ起動するため、/api/fetchNotion にアクセスできません。Notion 連携をテストする場合は必ず npx netlify dev を使ってください。

動作確認

ブラウザで http://localhost:8888/api/fetchNotion に直接アクセスすると、Notion データベースの JSON レスポンスが確認できます。

Functions の拡張

Notion に対する操作を増やす場合は、netlify/functions/ にファイルを追加するだけです。

netlify/functions/
├── fetchNotion.ts        → GET /api/fetchNotion(一覧取得)
├── getNotionPage.ts      → GET /api/getNotionPage?id=xxx(個別取得)
└── searchNotion.ts       → POST /api/searchNotion(検索)

個別ページの取得例を示します。

// netlify/functions/getNotionPage.ts
export async function handler(event, context) {
  const pageId = event.queryStringParameters?.id;

  if (!pageId) {
    return {
      statusCode: 400,
      body: JSON.stringify({ error: "idパラメータが必要です" }),
    };
  }

  const token = process.env.NOTION_TOKEN;

  const response = await fetch(
    `https://api.notion.com/v1/pages/${pageId}`,
    {
      headers: {
        Authorization: `Bearer ${token}`,
        "Notion-Version": "2022-06-28",
      },
    }
  );

  const data = await response.json();

  return {
    statusCode: response.status,
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(data),
  };
}

Notion API のレート制限

Notion API にはリクエストレート制限があります。

制限
リクエスト上限平均 3 リクエスト/秒
レスポンスヘッダRetry-After で待機秒数を通知

429 エラー(Too Many Requests)が返された場合は、Retry-After ヘッダの値を待ってからリトライします。

// リトライ処理の例
async function fetchWithRetry(url: string, options: RequestInit, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const response = await fetch(url, options);

    if (response.status === 429) {
      const retryAfter = Number(response.headers.get("Retry-After")) || 1;
      await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
      continue;
    }

    return response;
  }

  throw new Error("リトライ回数の上限に達しました");
}

無料枠の制限

Netlify の無料プランでの上限です。

リソース月間上限
Functions 呼び出し125,000 回
Edge Functions 呼び出し1,000,000 回
帯域幅100 GB
ビルド時間300 分
📊 1日あたりの目安

月125,000回 ÷ 30日 = 1日あたり約4,000回。ページ表示ごとに1回の API 呼び出しなら、個人サイトには十分な量です。

⚠️

無料枠を超えるとサイト全体が月末まで停止されます。従量課金ではないため、トラフィックが増える見込みがある場合は有料プラン(Starter: $8.50/月〜)を検討してください。

デプロイ

Netlify と Git リポジトリを連携していれば、git push するだけで自動的にデプロイされます。Functions も含めてまとめてビルドされるため、特別な設定は不要です。

デプロイ前に Netlify ダッシュボードの Environment variables に NOTION_TOKENNOTION_DATABASE_ID が設定済みであることを確認してください。

まとめ

Netlify Functions × Notion 連携のポイント
  • Notion 準備 インテグレーション作成 → データベースに接続 → トークン(ntn_xxx)とIDを取得する
  • 環境変数 NOTION_TOKEN と NOTION_DATABASE_ID をローカルは .env、本番はダッシュボードに設定する
  • Functions 実装 netlify/functions/ にプロキシ関数を作成し、トークンをサーバー側に隠す
  • フロントエンド /api/fetchNotion を叩くだけでNotionのデータを取得できる
  • セキュリティ トークンは絶対にフロントエンドに含めない。VITE_ プレフィックスは付けない。.env は .gitignore に含める

参考文献

CATEGORY
TAGS
円