Nuxt Content で RSS フィードを配信する
- 投稿した日
- 更新した日
- 書いたひと
- ひらたけ
Nuxt の Nuxt Content モジュールを使用しているウェブサイトで、RSS フィードを配信したときの対応内容を、備忘録的に残しておこうと思います。
環境
今回作業した環境は以下のとおりです。
- Node.js - 18.17.1
- pnpm - 8.7.1
- Nuxt - 3.7.0
- Nuxt Content - 2.8.2
/blog
ディレクトリの下にブログ記事が存在し、そのブログ記事の更新情報を RSS フィードで配信します。
web/
├ content/
│ ├ blog/
│ │ ├ article01.md
│ │ ├ article02.md
│ │ └ index.md
│ └ index.md
├ server/
│ └ tsconfig.json
├ app.vue
├ nuxt.config.ts
├ package.json
├ pnpm-lock.yaml
└ tsconfig.json
サーバルートを追加する
Nuxt Content のドキュメントにはサイトマップを生成する方法が掲載されています。こちらを参考に、RSS フィード用のルートを作成します。
server/
ディレクトリ内に routes/
ディレクトリを作成し、その中に feed.xml.ts
というファイルを作成します。 server/routes/
にファイルを配置すると、ウェブサイト公開時に /feed.xml
という URL でアクセスが可能になります。
作成した feed.xml.ts
に、Nuxt の サーバディレクトリについてのドキュメント にある例のコードを追加して、表示を確認します。
export default defineEventHandler((event) => {
return {
hello: 'world'
}
})
pnpm dev
で開発サーバを起動し、http://localhost:3000/feed.xml
へアクセスすると、return
で返している情報が表示されることが確認できます。
RSS フィードを生成する
実際に配信する RSS フィードの内容を生成します。この記事では、npm で「rss generator」などで調べたときに一番上に出てくる feed パッケージを利用します。
先ほど作成した feed.xml.ts
で feed
パッケージをインポートし、defineEventHandler
の引数として与えている関数内に RSS フィードを生成する処理を追加します。 feed
パッケージの使用例を参考に、以下のようなコードを追加します。
import { Feed } from 'feed'
export default defineEventHandler((event) => {
const feed = new Feed({
title: 'ウェブサイトのタイトル',
description: 'ウェブサイトの説明',
id: 'https://example.com/',
link: 'https://example.com/blog/',
language: 'ja',
image: 'https://example.com/image.png',
copyright: 'コピーライト',
})
// RSS 2.0 形式で出力する
return feed.rss2()
})
表示を確認すると、Content-Type が text/html
となっています。nuxt.config.ts
を編集し、application/rss+xml
に変更します。
export default defineNuxtConfig({
modules: ['@nuxt/content'],
routeRules: {
'/feed.xml': {
headers: { 'content-type': 'application/rss+xml; charset=UTF-8' },
}
},
...
})
続いて、ブログ記事のデータを追加していきます。サーバディレクトリ内で記事のデータを取得するには serverQueryContent
関数を使用します。今回作業する環境では、ブログ記事は全て Markdown 形式で作成しているため、型引数に MarkdownParsedContent
を指定します。
また、ブログ記事には全て created
という名前で、投稿日の情報を YAML Front Matter に含めています。
import type { MarkdownParsedContent } from '@nuxt/content/dist/runtime/types'
import { Feed } from 'feed'
export default defineEventHandler((event) => {
...
const articles = await serverQueryContent<MarkdownParsedContent>(event, 'blog')
.limit(10)
.find()
articles
// 拡張子が「.md」かつ内容が空ではないものを絞り込み
.filter((article) => article?._extension === 'md' && !article._empty)
.forEach((article) => {
const url = `https://example.com${article._path}/`
// ブログ記事を追加
feed.addItem({
title: article.title,
id: url,
link: url,
description: article.description,
date: new Date(Date.parse(article.created)), // YAML Front Matter の created
})
})
...
}
これで表示を確認すると、ブログ記事の情報が RSS フィードに追加されていることが確認できます。
記事本文の内容を含める
RSS フィードには、ブログ記事の本文を含めることができます。serverQueryContent
から取得したデータには本文の情報も含まれていますが、MarkdownNode
という型のオブジェクトの配列となっており、そのままでは使用できません。
そのため、本文の内容を生成するための関数を実装する必要があります。方法は色々あるのではないかと思いますが、 今回は力技でどうにかします 。他に良き方法があれば教えてください…。
/server/utils/
ディレクトリに content.ts
ファイルを作成し、generateContentFromAst
という関数を作成します。
import type { MarkdownParsedContent } from '@nuxt/content/dist/runtime/types'
export const generateContentFromAst = (
children: MarkdownParsedContent['body']['children']
): string => {
let text = ''
for (const node of children) {
let startTag = ''
let endTag = ''
if (node.type === 'element' && node?.tag) {
if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'ul', 'ol', 'li', 'blockquate', 'em', 'strong', 'del', 'pre', 'code'].includes(node.tag)) {
// <p>テキスト</p> のようにテキストの前後にタグがついてるもの
startTag = `<${node.tag}>`
endTag = `</${node.tag}>`
} else if (['hr', 'br'].includes(node.tag)) {
// <hr /> のようにタグ単体で完結するもの
text += `<${node.tag} />`
} else if (['a'].includes(node.tag)) {
// リンク
const href = (node?.props?.href as string) || ''
startTag = `<${node.tag} href="${href}">`
endTag = `</${node.tag}>`
} else if (['img'].includes(node.tag)) {
// 画像
const src = (node?.props?.src as string) || ''
const alt = (node?.props?.alt as string) || ''
text += `<${node.tag} src="${src}" alt="${alt}" />`
}
}
if (node.type === 'text') {
text += `${(node?.value || '').trim()}`
}
if (node?.children) {
text += `${startTag}${generateContentFromAst(node.children).trim()}${endTag}`
}
}
return text
}
最後に、先ほど作成した generateContentFromAst
を使用して、RSS フィードに本文の内容を追加します。
/server/utils/
内のファイルでエクスポートした関数は自動的にインポートされるため、そのまま使用することができます。
export default defineEventHandler((event) => {
...
feed.addItem({
title: article.title,
id: url,
link: url,
description: article.description,
content: generateContentFromAst(article.body.children), // 本文の内容
date: new Date(Date.parse(article.created)),
})
...
}
これで表示を確認すると、記事本文の内容が追加されていることが確認できます。
SSG の場合の設定を追加する
nuxt generate
で SSG を行っている場合は、nuxt.config.ts
に以下のような設定を追加して /feed.xml
が生成されるようにする必要があります。
export default defineNuxtConfig({
modules: ['@nuxt/content'],
nitro: {
prerender: {
routes: ['/feed.xml'],
},
},
routeRules: {
'/feed.xml': {
headers: { 'content-type': 'application/rss+xml; charset=UTF-8' },
}
},
...
})
以上で完了です。おつかれさまでした。