Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

こんにちは、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.png

上の図のとおり、 $ 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')
  })
}

まとめ

0x50
コーダーです。インターフェイス周りのデザインや実装をやります。フロントエンドからサーバーサイドまで、色んな領域に足を突っ込んでは引き抜いてを繰り返しています。
http://p1ch.jp
dena_coltd
    Delight and Impact the World
https://dena.com/jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away