2019年8月から実際のプロジェクトにNuxtを採用して継続的に開発を進めています。この記事はVueとNuxtを学び始めてから学習メモとしてTwitterに書いてきたことのまとめです。
以下は書籍や資料から学んだこと、同じチームのエンジニアからアドバイスいただいたこと、実際に試してみて気付いたことなどです。
まずNuxtとは何か
Nuxtの公式サイトには“ウェブ開発をシンプルかつ強力にするオープンソースフレームワークです”と書かれています。自分の解釈ですが、Vue.jsをベースとしたサイト開発・運用効率化のための仕組みや機能の集まり(ルールのまとまり)ととらえています。
Nuxtを採用するメリットとデメリット
Nuxt採用の検討時と採用後に感じたメリットとデメリットを挙げます。
Nuxtを採用するメリット
いくつかありますが次の点が主なメリットだと考えています。
- Webサイトの表示を高速化できる。
- SSR(サーバーサイドレンダリング)が可能でSEO対応ができる。
- コンポーネント管理によってサイト開発・運用を効率化できる。
- ディレクトリルールがあらかじめ備わっていることで複数人のチームで開発・運用する場合のルール運用コストが低く、開発・運用を効率化できる。
- サイトをより良くできそうなことや新しいことを学べるのが面白い(個人的な理由)。
- セキュリティ強化につながる場合もある(サイト構造や状況による)。
- JSONによる動的ルーティングが可能なため複数の外部サービスやシステムを運用している場合にデータを集約しやすく、デザインやフロントエンド実装の自由度が高まる(例えばWordPressの記事もJSON取得によって表示できる)。
Nuxtを採用するデメリット
- 学習コストがかかる。これから学び始める場合はVue.js、TypeScript(JavaScript)、Nuxtの作法なども学ぶ必要がある。一方でこれまでに何らかのコンポーネント設計の経験があると理解しやすい場面が増えそう。
- 開発環境を整えるまでの工程がやや多い。自分の場合はnpm、yarn、ターミナル、各種nodeのパッケージモジュールなど、始めてみるまで実体がよくわからないものが多いと感じた。未だに理解しきれていないものも多く、常に学び続けるための時間が必要。学ぶ楽しさや意志が持続できるとより良さそう。
- 開発環境依存のエラーやトラブルを解決するまでの時間コストはある程度かかる。初動の時間はかかりやすいが、長期的に見るとスピードアップにつながる期待が持てる。
デメリットはありますが、サイトを長期的に運用する際にはメリットが上回る場合が多いと感じます。今回のプロジェクトでは以下の理由メリットがデメリットを上回っていると判断して採用しました。
Nuxtの主な採用理由
- サイトの表示速度の向上が期待できるため。
- 拡張性が高く、長期的なサイト(サービス)の拡大に対応しやすいため(WordPressとの連携も可能)。
- 複数人での開発に対応しやすいため。
- SSR(サーバーサイドレンダリング)が利用できてSEO対応ができるため。
- セキュリティの向上につながると判断したため。
以下は各機能や気付いたことのメモです。
スロット機能(Vue.js)
- <slot>(スロット)はコンポーネントタグの中に記述されたコンテンツを配置するための特殊なタグ。
- コンポーネントのテンプレート内に配置する。
- 名前を指定してスロットの内容を書き出すこともできる。
コンポーネントの設置場所によって中身の内容(例えばテキスト内容)を変えることは多く、使用頻度は高めです。
renderメソッドとJSX記法(Vue.js)
- renderメソッドを使う場合は、JSX記法で書くと直感的にわかりやすいコードになる。
- return ();の中に直接HTMLを記述できる。
算出プロパティのGetterとSetter(Vue.js)
- メンテナンスしやすくするため、複雑なロジックには算出プロパティを利用したほうが良い。
- 値の読み書きをするための処理をGetter、Setterと呼ぶ。
- 値を取り出すだけではなく、設定もできる。
まだ使用頻度が少なく自分の理解は浅いです。ただ必要な状況は多いように感じるので今後理解を深めたいものの1つです。
トランジション(Vue.js)
- トランジションで状態を操作できる。
- <transition>タグはHTMLとしてレンダリングされない。
- <transition>タグに囲まれた内容に状態変化の過程が適用される。
- 独自の状態変化classが出力されるので、CSSでアニメーション制御ができる。
CSSのtransitionとjQueryのtoggleClassの組み合わせのようにイージングを制御しようとすると構造が複雑になりがちなので、よりシンプルに構造をわかりやすくできるのがメリットだと感じます。
Nuxtの各ページの共通要素の表示を一括管理できるlayoutsディレクトリについて
- layoutsディレクトリには画面全体の基本的なレイアウトを定義するvueファイルを配置する。
- 例えば/layouts/default.vueがある場合はこのvueファイルをベースとして、各ページのコンテンツが組み込まれる。
進行中のNuxtのサイトでlayout機能を活用して、各ページの共通要素の表示を一括管理できるように整理しました。layout機能を使うと次のような要素の出し分け管理がしやすくなります。
layout機能で表示管理する要素例
- ヘッダーやフッターのようにほぼ全ページ共通で表示する要素。
- 特定カテゴリーのみ表示する要素。
- 特定ページのみ表示する要素。
- 404エラーページ用の表示。
layoutという名前の機能ですが、画面上のレイアウトではなくページの共通要素を指定するテンプレートに近いイメージです。個々のページに直接コンポーネントを読み込むよりも、layoutであらかじめ表示パターンを定義することで、ページが増えたときの管理を効率化できるメリットがあります。
pagesディレクトリについて
- /pages/に配置したvueファイルがページ(パス)になる。ディレクトリも配置できる。
- ファイルやディレクトリの頭文字をアンダースコア付きにすると、パラメーターの機能を持たせられる。
- パラメーターはvalidateでチェックできる。例えばパラメーターがない場合にはエラーページを表示させるといった用途に使える。
- パラメーターは1つだけではなく複数用意できる。例えば/_yyyy/_mm/_dd/という構成にすることで、/2019/08/30/のようなパスにできる。
pagesディレクトリによって通常のWebサイト構築のディレクトリ分けと同じ感覚でページを制作できます。また動的ルーティング可能な点が魅力です。今後WordPressとの連携を試してみたいと思っています。
<nuxt-link>と<a>のリンクの使い分けについて
- <router-link>タグを使うと、HTMLの<a>タグのようにサイト内を遷移できるリンクをつなげられる。ブラウザのレンダリング時には<a>タグとして出力される。
- Nuxtには<nuxt-link>と呼ばれる<router-link>を拡張した機能が用意されている模様。
- <nuxt-link>を使うメリットは、<a>で遷移するよりもページ表示が早いこと。
- <nuxt-link>を使うデメリットは、Nuxt領域外へのページ遷移には使えないこと。
Webサイト全体が完全にNuxt内に収まっている場合のリンクは<nuxt-link>タグで良いのですが、1つのサイトがNuxt領域とそうではない領域に分かれている場合は通常の<a>タグでリンクをつないだほうが良いです(トラブル回避のため)。また外部サイトへのリンクの場合も<a>タグを使います。
状態(State)を一元管理するためのVuexについて
- Vuexは状態(State)を一元管理するために使う。
- 全てのコンポーネントで利用するデータをまとめる保管場所としての役割。
- storeディレクトリにスクリプトファイルを格納して使う。
- コンポーネントからVuexの変数や処理を利用するには$storeを用いる。
この機能は実際のプロジェクトではまだ使っていません。今後必要に応じて使ってみたいと思っています。
axiosによるデータ取得について
- axiosはHTTP通信のための機能を提供するパッケージ。
- const axios = require(‘axios’);でaxiosの機能を呼び出す。
- await axios.get(url);で対象のURLからデータを取得する(urlは変数)。
- awaitは「非同期処理を同期処理的に実行する」ためのもの。
- asyncDataは非同期で扱うデータをまとめて管理するためのもの。
- asyncDataは関数になっており、この中で非同期の値を用意してオブジェクトの形にして返す用になっている。
- asyncDataはコンポーネントがロードされる前に呼び出される。
- 必要に応じて任意のタイミングで非同期通信を実行したい場合は、thenというメソッドを使う。
- axios.get(url).then((res) => { アクセス後の処理 });のように記述する(ブログ記事をキーワード検索するときに、非同期で検索するために使えそう)。
- ネットワークアクセスの場合、アクセスが必ず成功するとは限らない(アドレスが間違っている、サーバーが止まっている、データが存在していないなど)。
- なのでエラー対策をしておく必要がある。
- エラー対策にはcatchというメソッドを使う。
Nuxt採用の主な理由の1つで重要だと思っているのがaxiosによるデータ取得です。この機能でWordPressからJSONデータ(WP REST API)を取得して、Nuxtのサイト内にWordPressの記事を表示できます。
応用として、例えば別サーバーに設置してあるWordPressから記事情報を取得してきてNuxtのサイトに表示する運用も可能です。
WordPressのWP REST APIのデータ取得と動的ルーティングによってコンテンツ管理と表示を分離できます。これによってさらにWordPressの活用範囲が広がると期待しています。WordPressに限らず、何らかのシステムを組み込む場合のデザインとフロントエンド実装の自由度が高まる点が魅力です。
axiosでJSONを取得して表示する
- axios.get()はデータを取り出す。
- axios.put()はデータを保存する。
- axios.delete()はデータを削除する。
ページ内アンカーリンクのアニメーション実装と効率化について
ページ内アンカーリンクをアニメーションさせる(イージングを指定する)ためのvue-scrolltoプラグインのメモです。
ページに固定ヘッダーがある場合はスクロール時の位置がヘッダーの高さ分ずれるので、オフセット値(offset)にヘッダーの高さを指定してスクロール位置を相殺させると心地良い動きになります。
- オフセットや遷移時間(duration)はデフォルト値の指定や要素ごとの指定もできる。
- 基本的にはデフォルト値よりも個別指定のほうが優先される。
注意点として、オフセットはなぜか個別要素の指定よりもデフォルト値が優先されるという問題が起きました。
この問題はオフセットのデフォルト値を0にして、個別要素のオフセットにヘッダーの高さを指定する方法で解決できました。個別要素の数値(ヘッダーの高さ)は後から変更する可能性があるため定数として登録しています。
// コード例を抜粋
v-scroll-to="{ el: '#exampleElementBox', offset: $constants.scrollToDefaultOffset }"
Nuxtのサイトでドロップダウンメニューを実装する方法
VueもしくはNuxt環境でドロップダウンメニューを実装するときには、vue-clickawayが便利だと実感しました。vue-clickawayを使うとドロップダウンメニューをクリック(タップ)したときにその要素以外をクリック(タップ)でメニューを閉じる実装がしやすく、インターフェイスが使いやすくなります。
vue-clickawayを用いて実装したインターフェイスはIE11環境でも期待通りの挙動になりました。もっと検証が必要ですが表示の安定感もありそうです。
コンポーネント間のデータ受け渡しについて
チームのエンジニアからNuxtのプロジェクトにおけるAtomic Designのコンポーネント管理について教わりました。次に挙げる例の主な目的は、データの流れをなるべくシンプルにして複雑化(スパゲッティ化)しない構造を維持するためだと自分は解釈しています。具体例に続きます。
具体例
- 商品リストのようなJSONのアイテムリストは主にPagesで受け取るのが良い(AtomsやMoleculesの粒度のコンポーネントで受け取らないほうが良い)。
- JSONはPagesで受け取り、propsでページごとの出力制御をするのが良い。
- propsの出力制御条件はPagesから見た子のコンポーネントに定義しておく。
今回手探りで進めてできたデータ受け渡しのコンポーネントは、Organismsを親として複数のMoleculesへデータを渡すという構造になりました。理想形ではなく改善点もかなり多いと思いますが、失敗を重ねながらどうにかデータの受け渡しができました。次のコンポーネント設計に活かしたいです。
要素のタグ記述の注意点
状況によりますが、Nuxtのプロジェクトであっても<i>タグは閉じタグありの形式(<i>内容</i>)でコーディングしておいたほうが、Nuxt領域外で部分的にコードを流用しやすくなるので良いかもしれません。理想はNuxtによるサイト全体の一元管理ですが、一時的に2重管理が必要な場合もあり得るためです。
Nuxtのサイトでメタタグ管理を効率化
NuxtでOGP画像を出し分けるための実装で迷っていたのですが、チームのエンジニアからアドバイスをいただいて解決できました。最初はif文を書いていましたが、よりシンプルに書けるコードにできたのでメモしておきます。
// メタタグのコード例を抜粋
{ hid: 'og:image', property: 'og:image', content: ogImage || this.$constants.defaultOgImage }
実現した仕様と補足のメモ
- 個別ページに独自のOGP画像がある場合は、個別ページ用のOGP画像を表示。
- 個別ページ用のOGP画像がない場合は、あらかじめ指定したデフォルト画像を自動で表示。
- デフォルト画像のパスは後から一括で変更できるように定数として定義。
この仕様にした意図とメリット
- ページごとに任意のOGP画像を反映することで、SNSなどで表示されたときにユーザーがページの内容を想像しやすくなる。
- 個別ページ用のOGP画像がない場合にも標準の代替画像を表示できる。
- 定数化によって代替画像のURLを後から変えたいときの工数を減らせる。
Nuxtのサイトで試しているCSS設計
- Scoped CSSを使う場合にもBEMの命名規則を用いる(自分はFLOCSSを使っています)。
- 効率を考慮して全体共通用のCSS(グローバルなCSS)とScoped CSSの両方を使う(状況に応じてリファクタリングは常におこなう)。
- 余白や配色、タイポグラフィなどのルールを定義した変数用のscssファイルを用意してデザインルールを統一する。さらなるメリットとして一括編集もしやすくする。
- Tailwind CSSの命名に沿ったユーティリティ用Classも使う。
最後に
Nuxtのこれまでの学習の振り返りをまとめました。まだまだわからないことも多くこれからも学ぶことはたくさんありますが、拡張性の高さに魅力を感じています。
また困ったことやわからないこと、疑問に思ったことに対して、いつも相談に乗ってくれたりアドバイスしてくれたりする同じチームのエンジニアに感謝しています。