📘

Netlify Functionsを利用してnote記事一覧をWebサイトに表示する方法

2023/09/18に公開

note記事一覧を自身の運営するWebサイトでも表示したくなるケースがあると思います。
noteのRSSは以下のようにURL末尾でrssを付与することで取得できます。

https://note.com/yonemoto/rss

ただし、利用時にはnote公式のヘルプで紹介されている注意点に留意しておく必要があります。

※RSS(XML)をカスタマイズする場合、読み込み後の挙動に関しては動作保証対象外となります。
※RSSをご自身のWebサイトに埋め込みたい場合は、一度サーバ上で取り込んでからご利用いただく必要があります。直接RSSを埋め込むことはできません。その仕組みはご自身にてご用意ください。この場合も動作保証対象外となります。

更新情報を自動で表示したい -「RSS」

注意書きにもあるようにnoteのRSSはCORS対応されておらず、クライアントサイドから直接取得することができません。そのため、サーバーサイドでの処理を経由して取得する実装にする必要があります。

サーバーサイドの処理を前提とするプロジェクトであればそこまで気にする必要はありませんが、静的サイトを前提として運営しているケースでは回避策を講じる必要があります。
その回避策として、Netlifyが提供するNetlify Functionsを利用した方法を本記事では紹介します。

https://www.netlify.com/products/functions/

Netlify Functions - Example use cases

Netlify Functionsはサーバーサイドの機能を簡単に公開できるサービスとなっており、無料枠から気軽に試すことができます。

目標設定

デモサイトのように、自サイトの一部箇所に最新のnote記事一覧を出力することを目指します。GitHubに本記事で紹介するプロジェクトのソースコードを公開しています。記事内容の補助やすぐに試したい場合などでご活用ください。

note記事一覧出力デモサイト

https://github.com/Kazuki-tam/note-netlify-function-sample/tree/main

前提条件

本記事では基本的に静的コンテンツがメインとなっており、一部動的な出力をクライアントサイドで行っているケースを想定します。また、以下環境を想定して開発を進めます。必要に応じて、ご自身の環境に合わせて読み換えていただけると幸いです。

https://www.netlify.com/

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

このプロジェクトではWebサイトを構築するためのフレームワークで人気を集めるAstroを利用します。今回利用するNetlifyとも連携しやすく、ドキュメントも充実しています。

https://docs.astro.build/ja/guides/deploy/netlify/#netlify-functionsを使用する方法

パッケージマネージャーは pnpm を利用して進めたいと思います。

https://pnpm.io/ja/

以下コマンドを実行し、ローカル環境にプロジェクトを作成します。

pnpm create astro@latest

プロジェクト選択では「Empty」を指定し、必要最低限のスケルトンな状態でセットアップします。TypeScriptの利用は「Yes」で進めます。

Netlify CLIのインストール

Netlify CLIをインストールしましょう。

npm install netlify-cli -g

以下コマンドを試しに実行し、問題なく動作するか確認してみましょう。

netlify

https://docs.netlify.com/cli/get-started/

Netlify Functionsの作成

以下コマンドでNetlify Functionsの開発に必要なパッケージをインストールします。

pnpm add @netlify/functions

https://docs.netlify.com/functions/create/?fn-language=ts

まずは「netlify」フォルダーをプロジェクトルートに作成します。
次に「functions」フォルダーを作成し、fetch-note-rss.tsファイルを作成します。

Netlifyではプロジェクトルート/netlify/functionsが標準ディレクトリーとして設定されています。

.
├── dist
├── netlify
│   └── functions
│       └── fetch-note-rss.ts
├── node_modules
├── public
└── src
    └── pages

ここではプロジェクトルート/netlify/functions/fetch-note-rss.tsのようになっていればOKです。

次にfetch-note-rss.ts内の処理で必要なパッケージをインストールします。

pnpm add axios xml2js dotenv
  • axios: HTTP通信の処理記述を簡易化するライブラリ
  • xml2js: XMLをJavaScriptオブジェクトへ変換するライブラリ
  • dotenv: 環境変数を参照するために利用されるライブラリ

fetch-note-rss.tsを開き、必要パッケージのインポートと環境変数の取得確認を行う処理まで記述します。環境変数の設定は後ほど解説します。

fetch-note-rss.ts
import type { Handler, HandlerEvent, HandlerContext } from "@netlify/functions";
import axios from "axios";
import xml2js from "xml2js";
import { config } from "dotenv";

config();

// 環境変数のチェック
if (!process.env.ALLOWED_ORIGIN || !process.env.RSS_URL) {
 throw new Error("環境変数が設定されていません");
}

続けて設定値を以下のように定義します。

fetch-note-rss.ts
 // 設定値
const ALLOWED_ORIGIN = process.env.ALLOWED_ORIGIN;
const RSS_URL = process.env.RSS_URL;
const HEADERS = {
  "Access-Control-Allow-Origin": ALLOWED_ORIGIN,
  "Access-Control-Allow-Headers": "Content-Type",
  "Access-Control-Allow-Methods": "GET",
  "Content-Type": "application/json",
};

今回作成する関数のメイン処理を記述します。

fetch-note-rss.ts
/**
 * RSS フィードを取得し、JSON形式で返却するNetlify Functions
 * @param event - NetlifyのHandlerEvent
 * @param context - NetlifyのHandlerContext
 * @returns レスポンスオブジェクト
 */
const handler: Handler = async (
  event: HandlerEvent,
  context: HandlerContext
) => {
  if (event.httpMethod !== "GET") {
    return {
      statusCode: 405,
      body: "Method Not Allowed",
    };
  }

  try {
    // RSSフィードを取得
    const response = await axios.get(RSS_URL);

    // XMLをJSONに変換
    const parsedData = await xml2js.parseStringPromise(response.data);

    return {
      statusCode: 200,
      headers: HEADERS,
      body: JSON.stringify(parsedData),
    };
  } catch (error) {
    console.error("Error fetching RSS:", error);

    return {
      statusCode: 500,
      headers: HEADERS,
      body: "RSSの取得に失敗しました",
    };
  }
};

export { handler };

RSSで出力される情報はXMLのフォーマットになっています。そのため、xml2jsのようなコンバーターを活用してJSONに変換して扱いやすくします。

環境変数の設定

環境変数は.envファイルとNetlifyの管理画面でそれぞれ設定します。
まず、プロジェクトルートに.envファイルを作成します。ALLOWED_ORIGIN にリクエスト元のURL、RSS_URLにRSS取得先のURLを設定します。

ALLOWED_ORIGIN=https://your-site.netlify.app/
RSS_URL=https://note.com/your-account/rss

Netlifyにデプロイする際にはEnvironment variablesで同様に環境変数を設定してください。

Netlify Environment variables

Netlify Functionsの動作確認

以上でNetlify Functionsの実装は完了です。試しにローカル環境で動作確認をしてみましょう。
以下netlifyコマンドでローカルサーバーを起動できます。

netlify dev

サーバーの起動が確認できれば、以下エンドポイントへアクセスしてみます。

http://localhost:8888/.netlify/functions/fetch-note-rss

RSSの内容が取得できていればOKです。

クライアントサイドからのリクエスト

Netlify FunctionsでRSSの内容がJSONで取得できるようになったら後はお好みの方法でフェッチ処理を実装してください。このセクションの内容はスキップしても問題ありません。

ここではReactでコンポーネントを作成し、note記事一覧をCSR(クライアントサイドレンダリング)させてみます。

AstroにはReactなどモダンフレームワークとのインテグレーションが用意されており、好みのUIコンポーネントフレームワークで実装が行えます。
以下コマンドで簡単にReactのインテグレーションが行えます。

pnpm astro add react

https://docs.astro.build/ja/guides/integrations-guide/react/

データフェッチのためのReact Hooksライブラリとして定評のあるSWRを利用したいので、インストールします。

pnpm add swr

https://swr.vercel.app/ja

次にデータフェッチを実施するカスタムフックを作成します。

useFetchRss.ts
import useSWR, { type SWRResponse } from "swr";

type Article = {
  title: string[];
  link: string[];
  "media:thumbnail": string[];
};

type FetcherResponse = {
  rss: {
    channel: [
      {
        item: Article[];
      }
    ];
  };
};

const fetcher = async (url: string): Promise<Article[]> => {
  const res = await fetch(url);
  if (!res.ok) {
    throw new Error(`Failed to fetch: ${res.statusText}`);
  }

  const data: FetcherResponse = await res.json();

  if (Array.isArray(data.rss.channel[0].item)) {
    return data.rss.channel[0].item;
  } else {
    throw new Error("Articles is not an array.");
  }
};

/**
 * useFetchRss はRSSフィードから記事を取得するためのカスタムReactフックです。
 *
 * @returns オブジェクトを返します:
 * - `articles`: `Article`オブジェクトの配列。各オブジェクトは個々の記事を表します。
 * - `error`: エラーが発生した場合はErrorオブジェクト、それ以外はundefined。
 * - `isLoading`: データの読み込み中はtrue、それ以外はfalse。
 */
export const useFetchRss = (): {
  articles: Article[] | undefined;
  error: Error | undefined;
  isLoading: boolean;
} => {
  const { data, error }: SWRResponse<Article[], Error> = useSWR(
    "/.netlify/functions/fetch-note-rss",
    fetcher
  );

  const isLoading = !data && !error;

  return {
    articles: data,
    error,
    isLoading,
  };
};

noteの記事一覧を出力するコンポーネントを作成します。

RssFeed.tsx
import React from 'react';
import { useFetchRss } from '../hooks/useFetchRss';

const RssFeed: React.FC = () => {
  const { articles, error } = useFetchRss();

  // エラーが発生した場合はエラーメッセージを表示
  if (error) {
    return <div>Error: {error.message}</div>;
  }

  // データがまだロードされていない場合は、ローディングメッセージを表示
  if (!articles) {
    return <div>Loading...</div>;
  }

  return (
    <ul>
      {articles.map((article, index: React.Key) => (
        <li key={index}>
          <a href={article.link[0]} target="_blank" rel="noopener noreferrer">
            <h2>{article.title[0]}</h2>
            {/* 画像サムネイル */}
            {article['media:thumbnail'] && article['media:thumbnail'][0] && (
              <div>
                <img src={article['media:thumbnail'][0]} alt="" width="300" loading='lazy' />
              </div>
            )}
            {/*// 画像サムネイル */}
          </a>
        </li>
      ))}
    </ul>
  );
};

export default RssFeed;

出力したいページでコンポーネントを読み込みます。

index.astro
---
import RssFeed from "../components/RssFeed.tsx";
---

<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width" />
    <meta name="generator" content={Astro.generator} />
    <title>Netlify Functionsでnote記事を取得</title>
  </head>
  <body>
    <h1>Netlify FunctionsとAstroでnote記事を表示する</h1>
    <RssFeed client:load />
  </body>
</html>

以上の実装でデモサイトのように記事情報を出力できれば完了です。
Netlifyへのデプロイ方法やGitHubとの連携に関しては割愛します。

https://www.newt.so/docs/tutorials/connect-to-netlify

まとめ

今回の実装ではクライアントサイドで記事一覧を出力する内容になっていますが、紹介した手法が必ずしもベストな実装であるとは限りません。
即時反映をマストとしない場合は定期的に自動ビルドすることでコンテンツを静的に出力することも検討できます。要件に合わせて実装アプローチを選択していただけると幸いです。

https://zenn.dev/kazuki_tam/articles/f7d3c0f1074969

TAM

Discussion