NEXT.js / TypeScriptでnoteとZennのRSSフィードするブログを作った
なぜRSSフィードなのか?
ReactやTypeScriptを使ってブログを作りたい。どうも、森川です。
当初使ったことのあるmicroCMSを使ってブログを作ろうと思ったのですが、noteに投稿した記事をブログに載せたい!Zennで記事を書きたいという思いからRSSフィードでサイトに集約する方法に至りました。
もともとAPIで経由して記事の閲覧数やタグとか付けられないかな~~??と思っていたのですが、今回RSSに登録するnoteとZennは公式のAPIが存在しないということを知り、どうしたものかと模索した結果、下記の記事を見つけました。
この記事から、RSSフィードを使って集約すればいいということを知りました。ただ、デザインが決まっていたのと、今回の技術スタックではコンポーネントごとにCSSが書けるstyled-components
を採用した背景もあり、そのまま使うことはありませんでした。async
とawait
の使い方もわかってきた段階なので、RSSフィードはなんかできそう・・・という謎の自信がありました。
noteのRSSはCORS対応をしていない
RSSフィードならうまくできるかなと思いつつ、noteやZennそれぞれのRSSフィードの使い方を調べているとこんな記事が、
noteってなんか制約多くないですか・・・?自分はVercelで作ろうとしていたので、この記事の要素を取り出しつつ、Vercelで開発可能か検討、調査したところ
- NEXT.jsにはAPIルートが作成できる(
NextApiRequest, NextApiResponse
を使って)
ことが分かったので、上記記事のようなGoogle Cloud Functionsを使わずともできそうだったので早速試してみた。
RSSフィードのコア部分
まずはNEXTプロジェクト内にutilフォルダを作りその中にrss.ts
とfetchRSS.ts
を作成した。
import axios from 'axios'
import { parseStringPromise } from 'xml2js'
export interface RssPost {
title: string
link: string
publishedAt: string
thumbnail?: string
source: string
}
const fetchRssPosts = async (url: string): Promise<RssPost[]> => {
// RSSフィードを取得
const response = await axios.get(url)
const xml = response.data
// XMLをJavaScriptオブジェクトに変換
const parsedXml = await parseStringPromise(xml)
// 記事データを抽出
const rssPosts = parsedXml.rss.channel[0].item
// 省略
return rssPosts
}
export { fetchRssPosts }
次にfetchRSS
でnoteとZennのフィードを読み込む
import { RssPost, fetchRssPosts } from './rss'
//Vercelのドメインの入力
const vercelDomain = process.env.NEXT_PUBLIC_VERCEL_URL
//ZennのRSSフィードのURL
const zennFeedDomain = process.env.NEXT_PUBLIC_ZENN_URL
//note-rss-proxyのAPIルートからnoteのRSSフィードのURLを取得
const noteUrl = `${vercelDomain}/api/note-rss-proxy`
//ZennのRSSフィードのURLを取得
const zennUrl = `${zennFeedDomain}`
const getAggregatedPosts = async (): Promise<RssPost[]> => {
// 各サービスから記事データを取得
const notePosts = await fetchRssPosts(noteUrl)
const zennPosts = await fetchRssPosts(zennUrl)
// 記事データを結合
const aggregatedPosts: RssPost[] = [...notePosts, ...zennPosts]
//省略
return aggregatedPosts
}
export { getAggregatedPosts }
という感じで上記のコードで出てきていますが、note-rss-proxy.ts
がnoteを取得する際のAPIルート(サーバーレス関数)になります。
noteのためのAPIルート
ということでNEXTプロジェクト内のpages/api/
以下にnote-rss-proxy.ts
を作成します。
コードは以下のようになります。
import axios from 'axios'
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
// noteのRSSフィードのURL
const NoteRSSDomain = process.env.NEXT_PUBLIC_NOTE_URL
const noteRssUrl = `${NoteRSSDomain}`
// axios.get()を使って非同期的にnoteRssUrlからRSSフィードを取得し、そのレスポンスをresponseに格納
const response = await axios.get(noteRssUrl, { responseType: 'text' })
res.setHeader('Content-Type', 'application/rss+xml')
// CORSヘッダーを設定
res.setHeader('Access-Control-Allow-Origin', '*')
res.status(200).send(response.data)
} catch (error) {
res.status(500).json({ error: 'Error fetching note RSS feed' })
}
}
上記のAccess-Control-Allow-Origin
ヘッダーは、CORSポリシーに関連するもので、ウェブアプリケーションが異なるオリジンからリソースにアクセスすることを許可するかどうかを制御するものです。
このヘッダーを'*'
に設定することで、CORSの問題を回避し、どのオリジンからでもAPIルートにアクセスできるようになります。
このようにNEXT.jsではサーバーレス関数を作ることができ、そのAPIルートでnoteのRSSフィードも表示されるようになります。
まとめ
ブログサイトを作ってNEXT.jsの良さを知ることができました。簡易的なAPIルートが作れるのはNEXT.jsならではだと思っています。
質問やご指摘がありましたら、コメントしてください。
最後までお読み頂きありがとうございました!
Discussion