Netlify Functionsを利用してnote記事一覧をWebサイトに表示する方法
note記事一覧を自身の運営するWebサイトでも表示したくなるケースがあると思います。
noteのRSSは以下のようにURL末尾でrss
を付与することで取得できます。
ただし、利用時にはnote公式のヘルプで紹介されている注意点に留意しておく必要があります。
※RSS(XML)をカスタマイズする場合、読み込み後の挙動に関しては動作保証対象外となります。
※RSSをご自身のWebサイトに埋め込みたい場合は、一度サーバ上で取り込んでからご利用いただく必要があります。直接RSSを埋め込むことはできません。その仕組みはご自身にてご用意ください。この場合も動作保証対象外となります。
注意書きにもあるようにnoteのRSSはCORS対応されておらず、クライアントサイドから直接取得することができません。そのため、サーバーサイドでの処理を経由して取得する実装にする必要があります。
サーバーサイドの処理を前提とするプロジェクトであればそこまで気にする必要はありませんが、静的サイトを前提として運営しているケースでは回避策を講じる必要があります。
その回避策として、Netlifyが提供するNetlify Functionsを利用した方法を本記事では紹介します。
Netlify Functionsはサーバーサイドの機能を簡単に公開できるサービスとなっており、無料枠から気軽に試すことができます。
目標設定
デモサイトのように、自サイトの一部箇所に最新のnote記事一覧を出力することを目指します。GitHubに本記事で紹介するプロジェクトのソースコードを公開しています。記事内容の補助やすぐに試したい場合などでご活用ください。
前提条件
本記事では基本的に静的コンテンツがメインとなっており、一部動的な出力をクライアントサイドで行っているケースを想定します。また、以下環境を想定して開発を進めます。必要に応じて、ご自身の環境に合わせて読み換えていただけると幸いです。
Astroプロジェクトのセットアップ
このプロジェクトではWebサイトを構築するためのフレームワークで人気を集めるAstroを利用します。今回利用するNetlifyとも連携しやすく、ドキュメントも充実しています。
パッケージマネージャーは pnpm
を利用して進めたいと思います。
以下コマンドを実行し、ローカル環境にプロジェクトを作成します。
pnpm create astro@latest
プロジェクト選択では「Empty」を指定し、必要最低限のスケルトンな状態でセットアップします。TypeScriptの利用は「Yes」で進めます。
Netlify CLIのインストール
Netlify CLIをインストールしましょう。
npm install netlify-cli -g
以下コマンドを試しに実行し、問題なく動作するか確認してみましょう。
netlify
Netlify Functionsの作成
以下コマンドでNetlify Functionsの開発に必要なパッケージをインストールします。
pnpm add @netlify/functions
まずは「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
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("環境変数が設定されていません");
}
続けて設定値を以下のように定義します。
// 設定値
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",
};
今回作成する関数のメイン処理を記述します。
/**
* 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 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
データフェッチのためのReact Hooksライブラリとして定評のあるSWRを利用したいので、インストールします。
pnpm add swr
次にデータフェッチを実施するカスタムフックを作成します。
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の記事一覧を出力するコンポーネントを作成します。
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;
出力したいページでコンポーネントを読み込みます。
---
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との連携に関しては割愛します。
まとめ
今回の実装ではクライアントサイドで記事一覧を出力する内容になっていますが、紹介した手法が必ずしもベストな実装であるとは限りません。
即時反映をマストとしない場合は定期的に自動ビルドすることでコンテンツを静的に出力することも検討できます。要件に合わせて実装アプローチを選択していただけると幸いです。
Discussion