最近の砂場活動その11: Nuxt.jsでサーバーサードレンダリング

タイトルの通りやりましたというお話です。以前は$http_user_agentを見て、googlebotだったらrendertronを経由させてレンダリングした結果を見せることでお茶を濁して凌いでいた。

この方法、SPA自体は全く変更する必要なくbotにレンダリングされた結果を見せることができるというメリットがある一方で、以下のようなデメリットもあること分かってきた(分かってはいたものもある)。

  • レイテンシーが大きい: rendertronが結果を返すまでに3-4秒程度かかる
    • rendertron自体に手は入れたくないため、お手軽な改善方法がない
    • botにならある程度レイテンシーかかっても問題ないという話はありそう
  • rendertronがそこそこメモリを食う
    • めちゃくちゃ食うというわけでもないが、メインのAPIサーバーよりメモリを食っていて何だかな感がある
  • 人間とbotにhtmlを出し分けている
    • 描画されるものが決定的に異なるわけではないが、若干気持ち悪い
    • 両方のテストが必要になる

rendertronで対応するのはそもそも邪道だなとは思っていたので、Nuxt.jsで真面目にサーバーサイドレンダリングを行なうようにした。変更に対応するPull Requestは以下。

package-lock.jsonを入れていなかったのでdiffがやたら大きいですが、本質的にはhtmlテンプレートファイルの置き場所が変わったくらいです。

対応方法

元々はVue.jsを使ったSPAだったので、そこまで大きな変更点はないです。覚えている範囲でNuxt.js用に変えた点としては以下の通り。

  • テンプレートファイルの置き場所変更
    • Nuxt.jsはlayoutpagescomponentsなどある程度ディレクトリに関して規約があるため、それに従うようにします
    • 特に整理できていなかったので見通しはよくなった
    • routingも基本的にpagesのディレクトリに従う形
  • クライアントサイドでデータ取得している箇所をサーバーサイドで取得するように
    • メインの変更点。asyncDataを使うようにする
    • Vue.jsのときはrouterに必要な情報が入っていたが、Nuxt.jsではcontextに必要な情報が入っている。context.route.params.MyParamという感じ。あまりドキュメントに書かれていないので、console.logで出力しながら必要なものを探した
  • htmlのmeta情報の置き換え
    • vue-headfulを使っていたが、Nuxtが面倒を見てくれるようになったのでそちらを使う
  • 設定をnuxt.config.jsに書く
    • webpackであれこれ書いていたこと(minifyとか)をnuxtがある程度面倒を見てくれるようだった
    • そもそもwebpackも雰囲気でしか書けていなかった...
  • Node.jsで動かない記法やライブラリの置き換え
    • developer toolに出力させているつもりが、terminalに出ていたというのがちょくちょくあったので、クライアントサイドで動くものかサーバーサイドで動くものかはきっちり意識しながら書く必要がある

これで大体動いた、Fetch as Googleで確認。慣れは必要だけど、めっちゃ大変という感じではない。Vue.jsをある程度触ったことがあれば大丈夫。初回のhtmlはサーバーサイドで生成して、それ以降の画面遷移はSPAのときと同じような感じで動くので、SPAで書いていたものもほとんど無駄にはならなかった。

rendertronはなくせたものの、サーバーサイドでレンダリングするNode.jsは相変わらず必要。AWS ECSで動かしているので、専用のDockerfileを用意する。といっても必要なファイルをコピーする、くらいしかやっていない。ECSのタスク定義にもよしなに修正を入れる。

FROM node:11.6.0-slim
ENV NODE_ENV production
WORKDIR /app
COPY .nuxt /app/.nuxt
COPY ./nuxt.config.js /app/nuxt.config.js
COPY ./package.json /app/package.json
COPY ./package-lock.json /app/package-lock.json
RUN npm install 

ENV HOST 0.0.0.0
EXPOSE 3000

ENTRYPOINT ["npm","run-script","start"]

静的ファイル(今回の場合はjsしかないけど)はnginxで配信させるようにしたかったので、nginxも適当に書き換える。

upstream local-nuxt {
    server localhost:3000;
}

server {
    location / {
        proxy_pass http://local-nuxt;
    }
    location /_nuxt/ {
        root /www/app;
        proxy_set_header Host $http_host;
        expires 1d;
        gzip on;
        gzip_types text/css application/javascript;
    }
}

docker-componentsを頑張って書いて手元でNode.js/APIサーバー/Nginxがそれぞれ連携して動いているかを確かめられるようにすべきと思いつつ、面倒くさくなって本番に導入しながら確認した。

導入後の所感

  • Nuxt.jsなかなかよい、規約などに従ったほうがカオスにならないのでサーバーサードレンダリングせずにSPAするだけでも導入して損はあまりなさそう
  • 人間とbotに出し分けしなくてよくなったので、見ないといけないポイントが減った
  • rendertronと比較してレイテンシーは改善された(そりゃそうだ)
  • とはいえ、短時間にアクセスが集中すると200を返せなくなるときが増える

Nuxt.jsビギナーズガイド―Vue.js ベースのフレームワークによるシングルページアプリケーション開発

Nuxt.jsビギナーズガイド―Vue.js ベースのフレームワークによるシングルページアプリケーション開発

Vue.js入門 基礎から実践アプリケーション開発まで

Vue.js入門 基礎から実践アプリケーション開発まで

  • 作者: 川口和也,喜多啓介,野田陽平,手島拓也,片山真也
  • 出版社/メーカー: 技術評論社
  • 発売日: 2018/09/22
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る