🌈

NEXT.js / TypeScriptでnoteとZennのRSSフィードするブログを作った

2023/04/22に公開

なぜRSSフィードなのか?

ReactやTypeScriptを使ってブログを作りたい。どうも、森川です。

当初使ったことのあるmicroCMSを使ってブログを作ろうと思ったのですが、noteに投稿した記事をブログに載せたい!Zennで記事を書きたいという思いからRSSフィードでサイトに集約する方法に至りました。

もともとAPIで経由して記事の閲覧数やタグとか付けられないかな~~??と思っていたのですが、今回RSSに登録するnoteとZennは公式のAPIが存在しないということを知り、どうしたものかと模索した結果、下記の記事を見つけました。

https://zenn.dev/catnose99/articles/cb72a73368a547756862

この記事から、RSSフィードを使って集約すればいいということを知りました。ただ、デザインが決まっていたのと、今回の技術スタックではコンポーネントごとにCSSが書けるstyled-componentsを採用した背景もあり、そのまま使うことはありませんでした。asyncawaitの使い方もわかってきた段階なので、RSSフィードはなんかできそう・・・という謎の自信がありました。

noteのRSSはCORS対応をしていない

RSSフィードならうまくできるかなと思いつつ、noteやZennそれぞれのRSSフィードの使い方を調べているとこんな記事が、

https://www.to-r.net/media/note-rss/

noteってなんか制約多くないですか・・・?自分はVercelで作ろうとしていたので、この記事の要素を取り出しつつ、Vercelで開発可能か検討、調査したところ

  • NEXT.jsにはAPIルートが作成できる(NextApiRequest, NextApiResponseを使って)

ことが分かったので、上記記事のようなGoogle Cloud Functionsを使わずともできそうだったので早速試してみた。

RSSフィードのコア部分

まずはNEXTプロジェクト内にutilフォルダを作りその中にrss.tsfetchRSS.tsを作成した。

rss.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のフィードを読み込む

fetchRSS.ts
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を作成します。
コードは以下のようになります。

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