nuxt generate
の機能は、静的サイト生成とか、単に静的生成といわれることが多いですが、文中で多用するのがツラいので、より短く「SSG(Static Site Generation)」で統一しています。
Nuxt.jsにおけるビルドの選択肢
Nuxt.jsでは、SPA・SSR・SSGの中から、好きなものを選んで開発できます。
初期設定ではSSRで動作するようになっていますが、流れとして自然なのでSPAから順に利点と欠点をまとめました。それぞれのビルドを行うため設定は、ドキュメントにわかりやすく書いてあるので、そちらでご確認ください。
SPA(Single Page Application)
利点
- 実装しやすい
- サーバーの準備が楽
欠点
- 初期表示が遅い
- SEOに不安がある
- OGをページごとに設定できない
SPAは、最もシンプルに実装でき、ホスティング先の選択肢が多いのが魅力です。
ドキュメントで、NetlifyやGitHub Pagesへのデプロイ方法が紹介されていますが、単にファイルをアップすればいいだけなので、ロリポップのようなレンタルサーバーでも動きます。
フロントエンドでルーティングするため、ページ遷移は快適ですが、最初の見た目もJavaScriptで組み立てることになるので、初期表示の遅れが気になるかもしれません。
we decided to try to understand pages by executing JavaScript. It’s hard to do that at the scale of the current web, but we decided that it’s worth it.
また、GoogleのクローラーはJSの実行を待ってページを解釈してくれますが、完全に意図したとおりにインデックスしてくれるかは不安が残るのと、TwitterやFacebookは・・・という懸念もあります。
ユニバーサルをうたってるNuxt.jsなのにSSRしないの?それならNuxt.jsいらなくない?という疑問があるかもですが、個人的には、環境構築の手軽さや規約によって生まれる秩序に魅力を感じるので、Nuxt.jsでSPAという選択もありだと思います。
SSR(Server Side Rendering)
利点
- SPAの欠点を解消できる
欠点
- 実装が少し面倒
- サーバーの準備が面倒
初回のリクエストをサーバーサイドでレンダリングして返すため、SPAの欠点を補うことができます。一方で、レンダリングするためのサーバーと、SSRを考慮した実装が必要になります。
ドキュメントで、HerokuやNowへのデプロイ方法が紹介されていますが、Node.jsが動作するサーバーを用意できればなんでも大丈夫です。インフラに弱い人にはこのハードルが高い気がしますが、インフラに強い人や、好きなPaaSがある人にとっては、SSRが魅力的な選択肢になると思います。
SSG(Static Site Generation)
利点
- SPAの欠点を解消できる
- サーバーの準備が楽
- SSRよりも速い
欠点
- 実装が少し面倒
- 用途が限られる
各ルートのHTMLをあらかじめ生成するため、SSRよりもレスポンスが速いのが魅力です。また、静的ファイルを生成するおかげで、SPAと同じようにホスティング先の選択肢が広がります。
生成時にはサーバーサイドでレンダリングすることになるので、SSRを考慮した実装が必要になるのと、SSGには向き不向きがあるので、Webサイトの要件を考えて慎重に選択する必要があります。
SSGの向き不向き
HTMLを事前に生成するため、向いているサイトと、そうでないサイトがあります。
向いているサイト
- 更新が少ないWebサイト
- ドキュメントやブログ
漠然とした表現になりますが、Webサイトっぽいものは向いています。
具体的には、ユーザーごとの最適化がされておらず、みんなが同じ内容のページを見るような、HTMLを事前に生成しても問題がないWebサイトに適しています。
更新のたびに再生成が必要になるので、あまりにも更新が多い場合は再生成が渋滞します。
最も良い例はこのウェブサイト自体です。このサイトは生成され GitHub Pages でホストされています
Nuxt.jsのドキュメントもSSGのようです。
向いていないサイト
- ログインが必要
- コンテンツが動的に変わる
一方で、ログインが必要だったり、ユーザーごとに最適化されたタイムラインが表示されるような場合は、あらかじめHTMLを生成する利点が少なく、実装が面倒になるので、SSGには向いていないと思います。
気合で解決できるケースもありますが、労力が割に合わないことが多いと思うので、後悔することになるかもしれません。(しました)
迷ったときのフロー
初期表示の速度やSEOがあまり気にならない場合には、SSRやSSGは不要だと思います。Nuxt.jsのおかげで、SSRのことはあまり意識せずに開発できますが、ブラウザに依存しているライブラリが~とか、サーバーの設定が〜とかで、なんやかんや時間をとられるので。
手軽に実装できるSPA
高速だが用途が限られるSSG
もっともバランスが良いのがSSR
という印象です。
個人的には、SSGの利点が大きいと思っているので、少し頑張ってSSGできるように実装することが多いです。
ページ遷移時の振る舞い
それぞれ初回リクエスト時の振る舞いは異なりますが、リンクにnuxt-linkを使用した場合は、遷移時にクライアントサイドでルーティングを行うため、SSRやSSGの場合でもSPAのように振る舞います。
より細かい場合分け
説明をシンプルにするために、ドキュメントに記載のあるSPA/SSR/静的生成の3つに分けてざっくり書いてきましたが、本来は2つの軸を組合わせて考える必要があります。
- モード -
universal
/spa
- 実行環境 - Node.jsサーバー / 静的サイト向けのサーバー
モードの違いは文字どおりですが、実行環境の違いは、Node.jsのプロセスで処理するか否かです。
▼ Node.jsのサーバーを立てる
nuxt build && nuxt start
▼ 静的サイトを生成してホスティング
# Nuxt <= v2.12
nuxt generate
# Nuxt >= v2.13
nuxt build && nuxt export
Universal & Node.js
Universalモード(SSR)でビルドして、Node.jsのサーバーで受ける。(デフォルト)
この記事で SSR と書いてあるのはこれです。
Universal & 静的サイト
Universalモード(SSR)で事前にレンダリングして、静的サイトとしてホスティング。
この記事で SSG と書いてあるのはこれです。
SPA & Node.js
SPAモードでビルドして、Node.jsのサーバで受ける。
せっかくSPAにするなら、静的サイトとして運用した方が楽だと思うので、利用シーンは限られそう。
SPA & 静的サイト
SPAモードで事前にレンダリングして、静的サイトとしてホスティング。
この記事で SPA と書いてあるのはこれです。
Full Static!! >= v2.13
Nuxt.js v2.13
で Going Full Static な変更があったので追記。
Full Staticとは
nuxt generate
でも大部分を事前にレンダリングしますが、クライアントサイドのルーティング時に asyncData
や fetch
が実行されるとAPIのリクエストが発生します。それを改善して、ビルド時にAPIのレスポンスを事前に生成するようにしたのが Full Static であり、 nuxt export
コマンドである。みたいな感じだと思う。
小規模なサイトの場合は恩恵がイメージしにくいですが、100万PV/月を超えるぐらいのサイトになると、ページ遷移時のAPIリクエスト数が結構な数(料金)になります。それでなくとも、更新されていないエンドポイントを何回も叩くのは単なるオーバーヘッドです。
そこをまるごと事前に生成して、ブラウザでpreloadすればパフォーマンスの向上も見込めます。
nuxt generate is being deprecated (but still working without any breaking change now) and will be removed for Nuxt 3
後方互換性のために nuxt generate
も残してるけど、Nuxt 3で削除すると思うから nuxt export
を使ってね、とのこと。
{ target: 'static' }
SSGするか否かを明示するプロパティが nuxt.config.js
に追加されています。
https://nuxtjs.org/api/configuration-target
何が生成されるのか
試しにQiitaのAPIを asyncData
で叩いて、 nuxt build && nuxt export
を実行したところ、ルートごとに payload.js
という名前で asyncData
の返り値が出力されてました。
__NUXT_JSONP__("/detail", (function(a,b,c,d,e,f){return {data:[{item:{rendered_body:"本文",coediting:c,comments_count:d,created_at:e,group:b,id:"fa0790dbaf5b31e9f4c2",likes_count:d,private:c,reactions_count:d,tags:[{name:"Java",versions:[]},{name:"gradle",versions:[]},{name:"初心者",versions:[]}],title:"JavaとGradleでHello world",updated_at:e,url:"https:\u002F\u002Fqiita.com\u002FSilverCafe\u002Fitems\u002Ffa0790dbaf5b31e9f4c2",user:{description:a,facebook_id:a,followees_count:f,followers_count:f,github_login_name:b,id:"SilverCafe",items_count:3,linkedin_id:a,location:a,name:a,organization:a,permanent_id:273742,profile_image_url:"https:\u002F\u002Fs3-ap-northeast-1.amazonaws.com\u002Fqiita-image-store\u002F0\u002F273742\u002Fc063ebcb43753291db40c9338379516149a07d96\u002Fx_large.png?1583715996",team_only:c,twitter_screen_name:b,website_url:"https:\u002F\u002Fkudanshita.work\u002F"},page_views_count:b}}],fetch:[],mutations:void 0}}("",null,false,0,"2020-07-28T01:06:05+09:00",1)));
ルートをキーにして data
を返す関数を保持してるっぽい。JSONPという単語を久々に見た。
// 抜粋
const chunks = {}
function importChunk(chunkId, src) {
if (chunks[chunkId]) {
return Promise.resolve(chunks[chunkId])
}
// 以下、payloadをロードする処理などいろいろ
}
window.__NUXT_JSONP__ = function (chunkId, exports) { chunks[chunkId] = exports }
window.__NUXT_JSONP_CACHE__ = chunks
window.__NUXT_IMPORT__ = importChunk
// 抜粋
try {
const payload = await window.__NUXT_IMPORT__(decodeURI(route), encodeURI(src))
this.setPagePayload(payload)
return payload
} catch (err) {
this.setPagePayload(false)
throw err
}
好きなものを選ぶ
SSGの良さを前面に押し出す内容になっていますが、手軽に済ませたい場合はSPAがいいですし、SSGが難しい場合はSSRが適しています。どれを選ぶにしても、あまり意識せずに実装できるのがNuxt.jsのいいところなので、適当に好きなものを選んでみては。