NuxtのSPAモードで開発してるとき、
急にページ単位で違うOGP入れたくなったりした事ってありませんか?
私はありません。
が、業務上そういう必要に駆られてしまうことは稀にあります。
そういう場合はサーバーサイドレンダリングをやってくれるUniversalモードを使えばいいんですが、
とはいえSPAモードで開発してたものを後からUniversalモードに切り替えたりすると、
ブラウザ情報を取得して動かしてたものが、サーバーサイドに移ったことで動かなくなったりするので
検証したりロジック修正したりとかなり面倒で手間をかけされられます。
というわけで、Nuxtを書き出すレンダラや配信するサーバに介入して無理やりHTMLにmetaタグをぶち込みます。
※ この記事ではどうやってNuxtが書き出したHTMLを配信する前に捕まえるかについて書いています。 実際にNode.jsでHTMLを編集するにはどうしたらいいかについてはまた別途記事を書く予定です。
参考にした資料: 【Nuxt(SPA)でもOGPしたい/want to render ogp with nuxt spa mode】 @fruitriin
Nuxt自体をExpressモジュールとして使おう!
↑の公式リファレンスで触れられている通り、Nuxtはそれ自体を別のNode.jsプログラムに組み込んで使うことができる。
特にNuxtのページ自体を書き出す Nuxt.render(req, res)
はリクエスト/レスポンス要素を引数に取るため、
ExpressサーバであればNuxt.render
をそのままモジュールとして使うことができる。
const express = require('express');
const { Nuxt, Builder } = require('nuxt');
const config = require('./nuxt.config.js');
// Expressサーバを作成
const app = express();
// Nuxtを起動
const nuxt = new Nuxt(config);
nuxt.ready();
// ExpressにNuxtモジュールを使う
app.use('/*', nuxt.render);
app.listen(3000);
こんな感じ。
で、nuxt.render
はページのレンダリングから配信まで一気にやってしまうため、
何とかしてレンダリング結果を捕まえて、そこでmetaタグを注入してやればいい、ということになる。
方法1. renderRouteを使う
HTML要素をレンダリングだけする(配信はしない)メソッド
Nuxt.renderRoute(path, context)
というのがあるので、
meta要素の注入が必要な部分だけrender
ではなくrenderRoute
でHTML文字列を書き出して、それに対してmeta埋め込み操作を行った後に配信する。
// ExpressにNuxtモジュールを使う
// 特定のルートならOGP改ざん
app.get('/detail/*', (req, res) => {
const result = nuxt.renderRoute('/', req);
/** result.htmlの中にHTML文書が文字列が入っているのでmetaタグを入れてやる **/
res.send(result.html);
});
// それ以外のルートを通常通り描画
app.use('/*', nuxt.render);
こんな感じ。
ただ問題は、飛んできたリクエストがHTMLかどうかを判断するルート分けをしておかなければならないこと。
例えば上記の例だと、/detail/
以下のパスに対して全てHTMLを配信する形になるため、
/detail/hoge.js
だったり/detail/fuga.png
だったりするHTML以外のリクエストにもそのままHTMLを返しに行ってしまう。
厳密にルーティング書いてやれば行けなくもないがめんどくさいので、もっと簡単にできる方法を探したところ、あった。
方法2. hookで割り込み処理をかける
requireで呼び出したNuxtインスタンスに対して、特定のライフサイクルイベントが発火した時に処理を捕まえて、処理を追加することができるhook
という仕組みが存在する。
使い方はこんな感じ。
// nuxtの配信準備ができたらコンソールログを吐く
nuxt.hook('ready', (nuxt) => {
console.log('ready for nuxt');
return;
});
要はイベントリスナに登録するみたいなものと考えてもらえばOK。
そのフックできるイベントの中にrender:route
という、
ルートがサーバレンダリングされる度に発火し、レンダリング結果を引数に取るイベントがあるので、これを使って割り込み処理を実装してやる。
nuxt.hook('render:route', (url, result, context) => {
// ルーティングの結果がhtmlでないなら早期return
if (!render.html) return result;
// 特定のルートならOGP改ざん
if (/^\/detail\/.*/.test(url)) {
/** result.htmlの中にHTML文書が文字列が入っているのでmetaタグを入れてやる **/
}
return result;
})
// ExpressにNuxtモジュールを使う
app.use('/*', nuxt.render);
こうしてやれば割とざっくりしたパス指定でも
NuxtのレンダラがHTMLを配信するか、それ以外のリソースなのかを判断してくれるので楽。
まとめ
最初からUniversalモードでSSR開発しよう!!!