Posted at
DeNADay 6

Nuxt.js の静的生成で作ったブログに RSS2.0 を生やす方法

こんにちは、Pちゃんです。

これは DeNA Advent Calendar 2018 の6日目の記事です。


TL;DR


  • DeNA のデザイン本部が運営する DeNA DESIGN BLOG というブログがある

  • 最近のリニューアルで WordPress から Nuxt.js (静的生成) に移行した

  • RSSが消失した:innocent:

  • 生やした


DeNA DESIGN BLOG の仕組みにについて

DeNA DESIGN BLOG は、主に以下のようなスタックで構成されています。


  • Nuxt v2.x

  • TypeScript

  • Markdown

上の図のとおり、 $ nuxt generate をすると


  1. 自作スクリプトや processmd を使って、全記事データを1つのJSONファイルに固める

  2. 全記事データが入ったJSONファイルを元に、Vueテンプレートに流し込んでトップページや記事ページのHTMLファイルを書き出す

と言った処理が行われます。 Nuxt.js の静的生成でブログを作っているところは、どこもこんな感じの流れなのではないでしょうか?


@nuxt/feed は静的生成するときは向かない

さて、 Nuxt.js で RSS を生やそうとググると、まず初めに目にするのが nuxt-community によってメンテされている @nuxtjs/feed だと思います。

良さそうに見えるんですが、静的生成するときには向かないです。

理由は2つあって、1つは axios で本番サーバーからデータを取ってきて RSS を生成するみたいな使い方が想定されていること、2つは RSS の生成が generate:before というタイミングで実行されることです。


axios で本番サーバーからデータを取ってくることを想定されていることの何がいけないのか?

@nuxtjs/feed の example を見てみると、以下のように書かれています。


example.js

//Import axios into your nuxt.config.js

const axios = require('axios')

// In your `feed` array:
async create (feed) {
feed.options = {
title: 'My blog',
link: 'https://my-url.com/feed.xml',
description: 'This is my personal feed!',
}

const posts = await (axios.get('https://api.blog.lichter.io/posts')).data
posts.forEach(post => {
feed.addItem({
title: post.title,
id: post.url,
link: post.url,
description: post.description,
content: post.content
})
})

feed.addCategory('Nuxt.js')

feed.addContributor({
name: 'Alexander Lichter',
email: 'example@lichter.io',
link: 'https://lichter.io/'
})
}


axios.get でブログのAPIを叩いて、それを元に RSS を生成と言った処理が書かれていますが、これができるのはサーバーで動的にコンテンツを生成できる環境だけなので、静的生成でブログを作る際は利用できないです。

静的生成の場合、サーバーに上がっているのは、新しい記事を書く前にデプロイされたデータですからね...。

とは言え、別に axios で API を叩くところを fs.readFile とかで、記事一覧の JSON ファイル取ってくれば良くね? となるんですが...

次項の問題があります。


RSS の生成が generate:before というタイミングで実行される

上記のとおりなんですが、 @nuxtjs/feedgenerate:before というタイミングに hook して動作するように書かれています。

generate:before がどのようなタイミングかと言うと、まあ読んで字の如くなのですが、 generate が完了する前なので、最新の状態ではない記事一覧を参照して RSS を生成してしまいます。

しかもこれは固定なので、外から引数を渡したりして変更することができません。

generate が完了したあとに RSS 生成できるようになれば完璧なのに...!


どうやって解決したのか

どうしようもないので、自前でモジュールを作りました。

generate:done に hook させて RSS を生成するようにしました。 RSS を生成する部分は @nuxtjs/feed の中を参考にして feed を使用しました。

以下、様子です。(フィード生成の部分、ちょっと端折ってます)


/modules/feed.js

const fs = require('fs')

const { Feed } = require('feed')
const { promisify } = require('util')

module.exports = function() {
// generate が終わったタイミングで実行する
this.nuxt.hook('generate:done', async generator => {
// 全ての記事データが1つになった JSON ファイルを読み込む
const entriesJson = await promisify(fs.readFile)('記事一覧JSONのパス', 'utf-8')
const entries = await JSON.parse(entriesJson).data

// 全ての記事を公開日が新しい順にソートする
entries.sort(function(a, b) {
return a.published_at < b.published_at ? 1 : -1
})

// ブログ自体のデータを流し込む
const feed = new Feed({
title: 'ブログ自体のタイトルを書くよ',
description:
'ブログ自体の説明を書くよ',
generator: 'feed',
})

// 最新10件の記事データを流し込む
for (let i = 0; i < 10; i++) {
let entry = entries[i]

// ブログ記事のデータを流し込む
feed.addItem({
title: entry.title),
id: `https://design.dena.com/${entry.category}/${entry.id}`,
link: `https://design.dena.com/${entry.category}/${entry.id}`,
author: [
{
name: entry.author
}
],
date: new Date(entry.published_at)
})
}

// RSS 2.0 形式で ./dist/feed.xml に書き込む
await promisify(fs.writeFile)('./dist/feed.xml', feed.rss2())

// ログ
console.log('Output feed.xml')
})
}



まとめ