Edited at

Vue.jsのプリレンダリングで手軽なOGP対応


はじめに

プリレンダリングは、Nuxt.js の Generate のようなもので、ビルド時にルートごとの HTML ファイルを生成する仕組みです。去年参加した v-meetup で、おいちゃんさんが「プリレンダリングでもわりと十分なんだよ~」とお話をされていたので、ずっとやろうと思ってました(遅い)。公式ガイドでも小規模な静的ページならプリレンダリングを推奨しているようです。

🐹 SSR vs プリレンダリング (事前描画)

最近チョコチョコ使うようになった Netlify にプリレンダリングの機能がありましたが、まだベータ版なのと、キャッシュされるタイミングは調整できないみたい?

課金すれば外部サービスを利用できるらしく、Vue.js の SPA でどのぐらいシームレスに導入できるのかちょっと気になります。近いうちにホスティングサービスを乗り換えると思うので、また今度試してみようと思います。

そんなわけで、今回は Netlify の機能ではなく「Prerender SPA Plugin」を使用しました。


  • Vue.js 2.5.13

  • Vue CLI 3.8.2

  • Prerender SPA Plugin 3.4.0


プロジェクトのセットアップ

Node.js と yarn は導入しているものとして、Vue CLI3(自己責任Ver)を使用しています。

Vue CLI3 では、対話式にいろいろな機能が選択できるようになったので、適当に選んでプロジェクトを作成します。

yarn global add @vue/cli

vue create my-project

もちろん、Vue Router はオンにします。


Prerender SPA Plugin のインストール

yarn add prerender-spa-plugin


Vue Router の設定

ルーターのモードは「history」にします。他はデフォルトの状態です。


src/router.js

export default new Router({

mode: 'history', // ← 必須
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: About
}
]
})


vue.config.js の設定

プロジェクトルートに vue.config.js を作成して、プラグインを追加します。

基本的な設定方法は、次のような感じです。


vue.config.js

const path = require('path')

const PrerenderSPAPlugin = require('prerender-spa-plugin')

module.exports = {
configureWebpack: config => {
// プロダクトモードでのみ追加
if (process.env.NODE_ENV === 'production') {
return {
plugins: [
// ★ Prerender SPA Plugin を登録
new PrerenderSPAPlugin({
// 出力先 dist や www など
staticDir: path.join(__dirname, 'dist'),
// 生成したいページ
routes: [ '/', '/about' ]
})
]
}
}
}
}



ビルドする

yarn build

で OK!

この設定では、プロジェクトルートの「dist」ディレクトリの中にファイルが作成されます。

dist/

├ about/
│ └ index.html
├ css/
├ img/
├ js/
└ index.html


パラメータのあるページ

次のように、パラメータのある動的ルートの場合…


src/router.js

export default new Router({

mode: 'history',
routes: [
{
path: '/note/:id',
name: 'note',
component: Note,
props: route => ({ id: Number(route.params.id) })
}
]
})

別個に登録する必要があるみたいです。こんなかんじ(´•ω•`)?


vue.config.js

new PrerenderSPAPlugin({

// 出力先 dist や www など
staticDir: path.join(__dirname, 'dist'),
// 生成したいページ
routes: [ '/', '/about', '/note/1', '/note/2', '/note/3' ]
})

ちょっとつらいので、数が多ければ for とかで動的に作成するのがよさそう。

といっても、ページ数に比例してビルドに時間がかかるようなので、あまりにもページ数の多いサイトでは素直に SSR した方がいいです。


ページによってタイトルやメタ情報を変更

Vue.js で出力された HTML をそのまま拾ってくれるため、このへんのメタ情報を更新するプラグインを使えば簡単に OGP などの対応ができます。

🐹 vue-meta

フックを使って自分で更新してもいい。


src/views/Note.vue

export default {

props: { id: Number },
created() {
document.title = `Note:${ this.id }`
},
destroyed() {
document.title = `Default Title`
}
}

複数の項目を変更するには Mixin かプラグイン作った方がスッキリしますね。

公式の説明では、次のようにpostProcessを使ってテキストベースで変換します。

わりとアナログな感じですね!


vue.config.js

new PrerenderSPAPlugin({

staticDir: path.join(__dirname, 'dist'),
routes: [ '/', '/about', '/note/1', '/note/2' ],
postProcess (renderedRoute) {
const titles = {
'/': 'Home',
'/about': 'Our Story',
'/note/1': 'Note1',
'/note/2': 'Note2'
}
renderedRoute.html = renderedRoute.html.replace(
/<title>[^<]*<\/title>/i,
'<title>' + titles[renderedRoute.route] + '</title>'
)
return renderedRoute
}
})

個人的に、プリレンダリングの目的は OGP の対応のみなので、低コストなものだったりパラメータを使って動的にしたいものは、コンポーネントやルーターのフックでやってもいいかな~と思いました。


非同期処理

初期データのロードなど非同期処理を待つ場合は、カスタムイベントを明示的に発火するまでコンテンツの取得を遅延できます。


vue.config.js

const path = require('path')

const PrerenderSPAPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer

module.exports = {
configureWebpack: config => {
if (process.env.NODE_ENV === 'production') {
return {
plugins: [
new PrerenderSPAPlugin({
staticDir: path.join(__dirname, 'dist'),
routes: ['/', '/about'],
renderer: new Renderer({
// document.dispatchEvent(new Event('custom-render-trigger'))
// されるまで読み込みを待つ
renderAfterDocumentEvent: 'custom-render-trigger'
})
})
]
}
}
}
}


たとえば、コンポーネントのルーターフックで非同期にデータを読み込みます。


src/views/Note.vue

beforeRouteEnter(to, from, next) {

const id = Number(to.params.id)
axios.get(`/note/${id}.md`).then(res => {
next(vm => {
vm.content = marked(res.data)
})
})
}

content をバインドした DOM の更新を待つため、グローバルのルーターフック afterEachnextTick を使って custom-render-trigger を発火します。


src/router.js

// すべてのルート遷移後

router.afterEach((to, from, next) => {
Vue.nextTick(() => {
document.dispatchEvent(new Event('custom-render-trigger'))
})
})

これで、非同期に取得したデータを使って OGP を生成したりできます👍


ついでに Netlify へのデプロイ

「dist」ディレクトリにファイルを生成しているので、リポジトリを作成して次のような設定でデプロイできます。

項目

Build command
yarn build

Publish directory
dist


おわりに

めっちゃお手軽です(๑'ᴗ'๑)❤❤ Nuxt も最近使っていたんですけど、私の PC がレガシーなためか、ホットリロードがちょっともっさりするのが気になっていたので、静的ページならプリレンダリングでいいなと思いました!

あと、Netlify CLI 使ったことがないので今度試す。