INDEX

    Notionの記事データを取得して、Webサイトに一覧を表示させるまでを紹介します。

    掲載先のサイトは下記のレアゾン・キャピタルサイトになります。

    https://capital.reazon.jp/

    アゾン・キャピタルは、レアゾン・ホールディングスの自己資金投資部門であり、主に、スタートアップ企業への資金提供を行っています。

    今回、こちらのサイトにNotion内で各Deep Tech領域について体系的にまとめられたBlog記事の一覧を表示させます。

    1. 環境のセットアップ

    まず、Webサイトのホスティング環境やツールを以下のように整えます。

    ・ホスティング:Cloudflare Pages

    静的サイトをホスティングするサービスで、Nuxt.jsアプリケーションをデプロイするのに適しています。

    ・API処理:Cloudflare Workers

    Notion APIへのリクエストを処理するサーバーレス環境です。フロントエンド側からのAPIリクエストをキャッシュし、画像の期限切れ対応などを行います。

    ・フレームワーク:Nuxt.js

    Notion APIから取得したデータを表示します。

    ・CI/CD:GitHub Actions

    ビルドおよびデプロイの自動化に使用し、50分ごとにビルドタスクを実行するように設定します。この理由については後述します。

    2. Cloudflare WorkersでのNotion API連携の作成

    Notion APIから記事やブロックデータを取得し、Cloudflare Workersを通じてこれをNuxt.jsで扱える形に加工します。

    Cloudflare Workersで、URLに基づいてNotion APIのエンドポイントを呼び出し、データベースクエリやブロックデータを取得します。

    const NOTION_API_KEY = 'your-notion-api-key';
    
    // リクエストを処理する関数
    async function handleRequest(request) {
        const url = new URL(request.url);
        const path = url.pathname.split('/');
    
        if (path[1] === 'databases' && path[3] === 'query') {
            // データベースクエリの処理
            const databaseId = path[2];
            const notionUrl = `https://api.notion.com/v1/databases/${databaseId}/query`;
    
            const response = await fetch(notionUrl, {
                method: 'POST',
                headers: {
                    'Authorization': `Bearer ${NOTION_API_KEY}`,
                    'Notion-Version': '2022-06-28',
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({})
            });
    
            const data = await response.json();
            return new Response(JSON.stringify(data), {
                headers: { 'Content-Type': 'application/json' }
            });
        }
    }
    


    このコードは、指定されたNotionデータベースIDに基づいて記事データを取得し、JSON形式でフロントエンドに返します。

    3. Nuxt.jsでの記事表示の実装

    Nuxt.js側では、Cloudflare Workersを介して取得した記事データを表示します。ここでは、Notionのデータベースから取得した記事を表示するためのコードを紹介します。

    <template>
      <div class="BlogArticle">
        <h2 class="BlogArticle__title">BLOG</h2>
        <ul class="BlogArticle__articles">
          <li v-for="article in articles" :key="article.id">
            <a :href="generateArticleUrl(article)" target="_blank">
              <div class="BlogArticle__thumb">
                <img v-if="article.thumbnail" :src="fetchCachedImage(article.thumbnail)" :alt="article.title || 'No Title'">
              </div>
              <span class="BlogArticle__date">{{ formatDate(article.last_edited_time) }}</span>
              <span class="BlogArticle__headline">{{ article.title || 'No Title' }}</span>
            </a>
          </li>
        </ul>
      </div>
    </template>
    
    <script setup>
    const baseUrl = 'https://your-cloudflare-workers-url.com'  // Cloudflare Workersのエンドポイント
    
    // 画像をキャッシュする関数
    const fetchCachedImage = (imageUrl) => {
      const encodedUrl = encodeURIComponent(imageUrl)
      return `${baseUrl}/images/${encodedUrl}`
    }
    
    const { data: articles } = await useAsyncData('articles', async () => {
      const response = await fetch(`${baseUrl}/databases/your-database-id/query`)
      const data = await response.json()
      return data.results
    })
    
    const formatDate = (date) => {
      const d = new Date(date)
      const year = d.getFullYear()
      const month = String(d.getMonth() + 1).padStart(2, '0')
      const day = String(d.getDate()).padStart(2, '0')
      return `${year}.${month}.${day}`
    }
    </script>
    


    このコードでは、記事リストを表示し、各記事に対してサムネイル画像とタイトル、日付を表示します。

    4. カテゴリデータやブロックデータの取得

    Notionでは、カテゴリや階層的な構造を持ったデータを扱うことができます。Cloudflare Workersを使って、カテゴリやブロックごとのデータを取得し、フロントエンドで表示する方法も考慮します。

    // カテゴリデータの取得処理
    async function handleRequest(request) {
        const url = new URL(request.url);
        const path = url.pathname.split('/');
    
        if (path[1] === 'blocks' && path[3] === 'children') {
            const blockId = path[2];
            const notionUrl = `https://api.notion.com/v1/blocks/${blockId}/children`;
    
            const response = await fetch(notionUrl, {
                headers: {
                    'Authorization': `Bearer ${NOTION_API_KEY}`,
                    'Notion-Version': '2022-06-28',
                }
            });
    
            const data = await response.json();
            return new Response(JSON.stringify(data), {
                headers: { 'Content-Type': 'application/json' }
            });
        }
    }
    


    5. 画像の処理と1時間の有効期限

    Notion APIから取得した画像には1時間の有効期限があり、1時間を過ぎると画像が表示されなくなる問題があります。これに対処するために、Cloudflare Workersで期限切れを検出し、新しい署名付きURLを取得する処理を追加します。

    if (path[1] === 'images' && path[2]) {
        const imageUrl = decodeURIComponent(path[2]);
        let response = await fetch(imageUrl);
    
        if (response.status === 500 || response.status === 403 || response.status === 401 || response.status === 400) {
            const newSignedUrl = await getNewSignedUrl(imageUrl); // 新しいURLを取得
            response = await fetch(newSignedUrl);
        }
    
        return new Response(response.body, {
            headers: {
                'Content-Type': response.headers.get('Content-Type'),
                'Cache-Control': 'max-age=86400'
            }
        });
    }
    
    
    


    このコードでは、画像のURLが無効になった際に自動的に新しいURLを取得し、常に最新の画像を表示できるようにします。

    6. GitHub Actionsでの自動ビルドとデプロイ

    GitHub Actionsを使って50分ごとにビルドタスクを実行する理由は、Notionのコンテンツを定期的に更新するためです。Notionでの記事や画像はリアルタイムに変更されることがありますが、静的サイトとしてデプロイされたページにこれを反映させるには定期的なビルドが必要です。

    50分ごとにビルドタスクを実行する理由には以下のポイントがあります:

    ・リソースのバランス:頻繁すぎるビルドはリソースを消費しすぎるため、50分ごとという頻度が適切と判断されました。

    ・APIリクエストの制限:Notion APIにはリクエストの制限があるため、頻繁にAPIリクエストを送らないようにするためにも、50分ごとにビルドを設定しています。

    on:
      schedule:
        - cron: '*/50 * * * *'
    

    このスケジュール設定によって、ビルドとデプロイが定期的に実行され、最新のコンテンツがWebサイトに反映される仕組みを構築します。

    7. まとめ

    実装する中で厄介だった点が、Blog記事内の画像URLが1時間で期限切れとなる点でした。

    Webサイト内に保存する方法も考えたのですが、Cloudflare Pages内に画像を格納する仕様が思いつかなかったので、50分毎にビルドタスクを走らせて画像を再取得させる仕様で落ち着かせました。

    今後の課題としては、記事作成をトリガーとしてビルドタスクを走らせることができれば理想的だと思いますので、今後、手が空いた時にでも追加実装してこちらにまた記事を掲載したいと思います。