はじめに
noteのフロントエンドをNuxt.jsへ刷新します はもう昨年のことかと思いながら、Nuxt.jsを習得してみる。Universal Application、Progressive Web Applicationなど、これからのWebにとって当たり前の技術を身につける。
Nuxt.jsビギナーズガイド Vue.jsベースのフレームワークによるシングルページアプリケーション開発 を簡単にまとめてみた。JavaScript初心者の私でも非常に理解しやすく、Qiitaにいいねをつけていただいた上で、ご購入をおすすめします。
1. Nuxt.jsの概要
01. 「モダンフロントエンド」のイマ
今日フロントエンドは非常に安定し停滞した状態が続いている。トラディショナルなサーバーがHTMLを返す時代から、SPAとAPIによるWebアプリケーションがデファクトスタンダードになった。当分はReact、Vue、Angularの寡占状態は変わることはなく、技術的にも枯れている。先進的な機能(Web Worker、Web Assembly)も追加されているが、実用段階まで民主化されてはいない。
モダンフロントエンドは、多種多様な選択肢があり柔軟な開発が可能。しかしそれは、アプリケーションの本質的な価値ではない領域と付き合い続ける必要があるともいえる。現在では、どの領域にも著名な技術がシェアを占めており、効率化することに妥当性を覚える段階では、開発者自身が技術を選定し続ける理由はない。
一部でしか使われていない技術(GraphQL)にも対応できるだけのアーキテクチャとエコシステム、コミュニティが求められている。Nuxt.jsは、その要件を満たしている。Vue.jsの記法や設計の資産をそのままに、負債となりやすい部分(webpack、モジュールの最適化、サーバーサイドレンダリング(SSR))までカバーしている。またサードパーティライブラリやVueプラグインによるアプリケーションのコア拡張も、ミドルウェアやプラグインといった独自システムによってメンタナリビリティの高い、統一的なアーキテクチャでの運用が可能。またVue.jsの範囲だけに限らず、Nuxt.js内に形成されたシンプルな拡張システムのもとにaxiosやJSON API、GraphQLサーバーへのリクエストなども可能。
02. Nuxt.jsとは
当初はVue.jsアプリケーション開発におけるSSRを支援することを目的として始まった。Next.jsはReactらしいUNIX文化を引き継ぎ、Nuxt.jsは規約をベースとしたAngularのようなフルスタックフレームワークの道を辿る。
2016年10月25日zeit.coのチームがReactアプリケーションをサーバーサイドレンダリングするためのフレームワークNext.jsを発表しました。その発表から数時間後、Next.jsと同じ方法で、Vue.jsアプリケーションをサーバーサイドレンダリングするアイディアが生まれました。すなわちNuxt.jsが誕生したのです。
※Zeit社は、ミニマルなNode.jsのサーバー構築ライブラリ microやElectron製のターミナル Hyper、Node.js/Dockerホスティングサービス Nowを開発、運用している企業である。
Nuxt.jsの特徴と機能
- ビルドプロセスの隠蔽
Vue.jsによるSPA開発は、設定すべきビルド環境が多くwebpackを中心として多くのローダーやプラグインを導入、メンテナンスする必要がある。Nuxt.jsは、基本的にはすべての設定を所持、最適化した形でメンテナンスされており、webpackを意識する必要があるのはカスタマイズしたローダーやプラグインを追加したい場合だけ。
- Vueのエコシステムとのインテグレーション
Vue.jsは、Vue Router、Vuex、Vue Server Renderer、vue-metaが密接に連携するように作られており、定型句をもとに個別設定する必要がある。Nuxt.jsは、すべてが設定されており、独自レイヤからも扱いやすいようにカスタマイズされている。
- 独自レイヤの実装
独自レイヤは、Vue.jsの機能不足な部分を、Nuxt.jsが追加で実装した概念。例えば、middleware(アクセス時のフックとして機能する)は、内部で任意の処理やルーティングを改ざんすることが可能。SSR時は認証情報などに応じて柔軟に301/302リダイレクトを行う機能を提供し、SPAモードでは適切な初期データの格納などで利用できる。またVue.jsでは独自で設けるプラグイン領域は、pluginsディレクトリ内で包括的に管理できる。
03. Nuxt.jsがもたらすもの
Ruby on Railsは、規約の代表として広く知られている。暗黙知を大量に作ることを許容する代わりに、多くのWeb開発における頻出パターンを効果的に実装できる。同様にNuxt.jsは、Vue.js開発においての定型パターンを規約と定めている。例えば、「pagesディレクトリに置かれる.vueファイルはページを表す」という規約を守ることにより、開発者はVue Routerの煩雑な設定の記述とメンテナンスから開放される。また「data関数の代わりにasyncDataを利用する」という規約を守ることにより、簡単にSSRやアプリケーションとの連携に強いVueコンポーネントを開発できる。
つまり納得感のある、統一的な記述が守られる結果、多くのコスト(どちらでも良い設計の議論など開発におけるノイズから、単純なコードの記述量まで)を省略でき、効率的な開発に大きく寄与する。この規約に対する考え方は、Angularと近いものがある。両者は、JavaScript/TypeScriptらしさが好みなのか。他のエコシステムの資産を十分に活用して柔軟に対処したいのか、1つの設計の中ですべてを閉じて堅牢な形で運用したいのか程度の違いしかない。Nuxt.jsは比較的攻めの規約、Angularは守りの規約といえる。
04. Nuxt.jsがマッチするプロジェクトやシチュエーション
Vue.jsの中級者以上が1人でもいるVue.jsのSPAプロジェクトにはおすすめ。Vue.jsの柔軟さゆえに汎用性のない独自のアーキテクチャを考案・運用する代わりに、Nuxt.jsという1つのルールを中心に据えることで秩序のある開発が可能。またNuxt.jsでカバーできないものは、モジュールシステムを利用することでNuxt.jsのレールから逸脱しない。
メンバー間でVue.jsのレベル差が激しい場合にもおすすめ。Vue.jsは柔軟性ゆえバッドノウハウをつかみやすい。ベストプラクティスが分からない状況では、短期的な生産性の高い開発手法を選択してしまいがちだが、Nuxt.jsを利用することで短期的な生産性を落とすことなく効果的な設計を適用することが可能。
メンバー全員が初学者である場合とSPAでない場合はおすすめしない。規約によって整備されていて誰でも扱いやすいように見えるが、ミドルウェアやプラグインなど独自機能やVue.jsを拡張していることによる独自のライフサイクルなど、学習コストが高い。このような場合は、 Vue CLI
を利用した開発をおすすめする。
2. Nuxt.jsによるシングルなアプリケーション開発
QiitaAPIを利用してNuxt.jsタグの投稿一覧を表示し、その投稿から投稿者のプロフィールと投稿一覧を表示するサービス。axiosを利用したHTTPリクエスト、動的なルーティングによるコンテンツの出し分け、SSR、SEO対策、Vuexストア(クラシックモード)を理解できる。
$ vue init nuxt-community/starter-template hoge
※サンプルアプリケーションの詳細な解説は、Nuxt.jsビギナーズガイドをご購入ください。
3. Nuxt.jsの機能の活用
01. layoutsディレクトリによるレイアウトの共通化
Vue.jsでは共通化して効率化したいモチベーションと、共通化した際の編集コストや管理コストの増加という問題を天秤にかける。それに対してNuxt.jsは、簡単な切り分けや宣言的な記述方法によって、複雑な管理なしにレイアウトの共通化が可能。
default.vue
をトップページ用に、個別ページを single.vue
にした場合、すべてのコンポーネントに対して layout: 'single'
を指定する必要があり適用忘れが生じる可能性が高い。トップページはLPやダッシュボードのサマリなど他のページとは大きく異なるデザインが多いため、トップページだけ layout: 'home'
を指定することがベストプラクティス。大規模プロジェクトだと、default.vue
を使わない方がわかりやすい場合もある。
※サンプルアプリケーションの詳細な解説は、Nuxt.jsビギナーズガイドをご購入ください。
02. Nuxt.jsのライフサイクル
Nuxt.jsはアクセスがあった際に、次の図のようなライフサイクルを形成する。これらの処理が完了後、Vue.jsのライフサイクルが呼び出される。プラグインはさらに前に呼ばれる。ライフサイクルを意識することで、それぞれの責務を理解した開発が可能。
03. middlewareによるグローバルなフックの登録
middlewareは、機能面の追加としてもっとも強力な機能。グローバルを含めた任意のルーティングへのアクセス時の最初に読み込まれるため、SSR処理などが行われる前に様々な処理を行うことが可能。また default export
によって、1つの関数を返すファイルにする必要がある。
今回は universal-cookie
を用いて、簡単なログインを必要とするページを作成する。認証には contextオブジェクト を用いて、リクエストデータとrouteオブジェクトとredirect関数を受け取り実装する。
エンドポイント | 認証の振り分け |
---|---|
http://localhost:3000/ | 誰でもアクセス可能・それぞれのページへのリンクが存在する |
http://localhost:3000/login | 未ログインユーザーのみ・ログインしている場合は「/」へ |
http://localhost:3000/authed-route | ログイン済ユーザーのみ・ログインしているしていない場合は「/login」へ |
※サンプルアプリケーションの詳細な解説は、Nuxt.jsビギナーズガイドをご購入ください。
04. プラグインによるVue.jsプラグイン資産の有効活用
プラグインは、npmパッケージやVueプラグイン、特定の処理をグローバルに登録し再利用性を高めるために利用する。主にUIフレームワークやFirebase SDK、momentなど、必ずアプリケーション全体で利用するライブラリの導入時に利用する場合と、ルーティングフックや初期段階での外部CDNコードの読み込みなどの共通処理を実装する場合に利用する。メリットは、Nuxt.jsの規約の上で開発が可能なことと、SSR時に呼び出すかをオプションで簡単に切り替えることができること。
今回は、VueRouterのbeforeEachフック利用して、ページ遷移ごとにルーティングのパスをロギングしてみる。plugins: ['~/plugins/logger']
は、 plugins: [{ src: '~/plugins/logger', ssr: true }]
と等価。実際の開発では、GoogleAnalyticsや mixpanel などの設定で利用する。
※サンプルアプリケーションの詳細な解説は、Nuxt.jsビギナーズガイドをご購入ください。
05. Vuexのモジュールモードを活用したオートローディング
- Nuxt.jsによるシングルなアプリケーション開発
で、Vuexストアのクラシックモードを利用した。自身でVuexを読み込み、ストアインスタンスを生成する形でVuexストアを構築する。本格的なアプリケーション開発ではモジュールモードを利用する。Vuexストア自体に関わるコードは一切記述せず、Vuexストア内で利用するモジュールのビジネスロジックのみ記述する。モジュールを規約に沿って記述しexportするだけで、自動的に名前空間付きのモジュールと解釈しVuexストアインスタンスを生成する。
モードと構造の判別は以下で行なっている。
- index.jsはルートモジュールである
- index.jsがオブジェクトをエクスポートしている、もしくは存在しない場合はモジュールモードで動作する。Vuexストアをインスタンスをエクスポートしている場合は、クラシックモードで動作する
- index以外の名前を持つJavaScriptファイルは、ファイル名のスコープによるモジュールとして作用する
最終的に出力されるVuexストア構造は完全に同じであり、モジュールモードの方が構造について強く意識せずモジュールの記述に集中できる。
クラシックモード
new Vuex.Store({
state: { isLoading: false },
mutations: {
setIsLoading(state, isLoading) {
state.isLoading = isLoading
}
},
modules: {
users: {
state: {
list: []
},
mutations: {
addUser(state, user) {
state.list.push(user)
}
},
actions: {
addUser({ commit }, { user }) {
commit('addUser', user)
}
}
}
}
})
モジュールモード
export const state = () => ({
list: []
})
export const mutations = {
addUser(state, user) {
state.list.push(user)
}
}
export const actions = {
addUser({ commit }, { user }) {
commit('addUser', user)
}
}
4. 中規模以上の開発を意識したNuxt.jsによるWebアプリケーション開発
ブログサービスを作成する。バックエンドに Firebase Realtime Database を利用してAPIサーバを用意して、ユーザ情報と投稿といいねデータをやりとりできるようにする。フロントエンドでは、ElementUIを利用して全体の投稿の一覧や、ユーザごとの投稿の一覧を閲覧できるようにする。
$ create-nuxt-app hoge
プロジェクトのディレクトリをappディレクトリ配下にいれているのは、アプリケーションのコアコードとそれ以外のコードを明確にするため。また各種ツールのパス指定を簡潔にするため。例えば、Lintやコードフォーマット形式外の箇所を1つずつignoreする必要がない。
※サンプルアプリケーションの詳細な解説は、Nuxt.jsビギナーズガイドをご購入ください。
5. Nuxt.jsアプリケーションのテスティング
01. フロントエンドにおけるテストの必要性
従来は、UI層と密接に関係しているためや外部のSDKを読み込むため、API接続があることためテストが難しかった。またSeleniumなどのe2eテストツールは存在したが、DOMやブラウザに強く依存するため、作成コストは高いがテストの寿命は短かった。
UTはVuexデータストアと挙動が複雑なVueコンポーネント、フレームワークに依存しないレイヤのコードをテストすべきである。Jestを利用すると良い。アサーションライブラリからテストランナー、カバレッジレポーター、スナップショットテストまで内包しており、特有の設定を覚える必要がない。
$ yarn add -D jest @vue/test-utils lodash.clonedeep babel-jest 'babel-core@^7.0.0-0' @babel/core babel-preset-vue-app vue-jest
6. アプリケーションのデプロイと運用
名称 | モード | 学習コスト | 運用コスト | メタタグ対応(SEO/OGP) | 拡張の柔軟性 |
---|---|---|---|---|---|
Universal(デフォルト) | SSR | 高い | 高い | 💮 | 💮 |
Generate | SPA | 低い | 低い | 🙆♂️ | 🙆♂️ |
SPA | SPA | 非常に低い | 低い | 🙅♀️ | 🙆♂️ |
デプロイ先はService Level Agreement(uptime)やレイテンシ、運用コストなど総合的な判断が必要。
SSR
静的サイト
(Netlify参考記事)
【爆速】静的ページを無料で独自ドメインでSSL(HTTPS)で公開する方法(Github => CircleCI => AWS S3 / Firebase Hosting / Netlify)
7. プラグインとモジュール、エコシステムの開発・貢献
- @nuxtjs/axios
- json-server
- @nuxtjs/pwa
(PWA参考記事)
[Nuxt/WebSpeechAPI]騒がしい居酒屋でもワンタップで店員さんを呼ぶサービス「親指ですみません」
サードパーティモジュールの探し方は、以下リポジトリから探すのが良い。また本書では自作プラグイン・モジュールの開発からnpmへの公開まで記載されています。