開発に慣れてきたら迷うことはないと思いますが、Vue.jsをさわるのが初めてでNuxt.jsを採用した場合、どっちのドキュメントを見ればよいかわからずに、さまよってしまう事があるかもしれません(自分がそうでした)。そんなときのための、長ったらしいメモです。
この記事を書いている時点では、Vue.jsが2.5
、Nuxt.jsが1.2
です。
ドキュメントの歩き方
まずはガイドで概要を把握して、細かいことが気になったらAPIをあたります。
Vue.jsでの開発がはじめてであれば、本格的に書き始める前に、スタイルガイドを見ておくと、手戻りが少なくなるかもしれません。
ガイド
フレームワークの使い方がわかりやすく書かれています。
はじめにから順に読んでいって、大体こんなことができるんだぁーと頭に入ったら、手を動かしてみてハマったらガイドを眺める、の繰り返しで体が慣れていくと思います。
API
プロパティやメソッドの具体的な仕様を確認できます。例えば、ガイドでは算出プロパティとウォッチャというページに、それぞれをどのように使えば良いかが書かれていますが、APIにはcomputedやwatchがどのような値をとって、どんな挙動をするかが書かれています。
例
コードのサンプルが色々と載っています。
スタイルガイド
Vue.jsのドキュメントには、コンポーネントの命名や、詳細なプロパティの定義など、開発するうえで従っておいた方がいい慣習がスタイルガイドにまとまっています。あとからぐちゃぐちゃになったコードを修正するのは大変なので、強いこだわりや異論がない限りは、最初からスタイルガイドに沿っていたほうが綺麗に書けると思います。
大切なこと
- ディレクトリ構造を把握する
- コンポーネントを理解する
- ライフサイクルと仲良くなる
この3つを乗り越えたら開発スピードが上がったので、これらを中心に機能を整理していきます。
ディレクトリ構造
├ assets/ # Sassや画像などのWebpackで扱うもの
├ components/ # コンポーネント
├ layouts/ # ヘッダーやフッターを含む共通のレイアウト
├ middleware/ # ページのレンダリング前に実行したい関数(認証など)
├ pages/ # 各ページを表すコンポーネント(このディレクトリをもとにルーティング)
├ plugins/ # プラグイン
├ static/ # assetsに含めたくない静的ファイル
├ store/ # Vuex
└ nuxt.config.js # Nuxt.jsの設定ファイル
ディレクトリ構造はNuxt.jsの流儀に従います。
assetsとstatic
assets
にはWebpackで扱いたいアセットを置きます。
具体的には、コンポーネントから読み込む画像や、Sassファイルなどが対象です。
static
には、favicon
やrobots.txt
など、
Webpackのローダーをかませずに、そのまま出力したいファイルを置きます。
参照する際は、static
がなくなってルートにいるので注意してください。
出力例 : /static/robots.txt → /robots.txt
components
<script>
import AppLogo from '@/components/AppLogo.vue'
export default {
components: {
AppLogo
}
}
</script>
pages
やlayouts
などの特別なコンポーネント以外は、すべてここに置きます。
コンポーネント内で別ファイルのコンポーネントを使いたいときは、import
したコンポーネントをcomponentsプロパティに指定することで、利用できるようになります。
layouts
<template>
<div class="app">
<header></header>
<main>
<nuxt/>
</main>
<footer></footer>
</div>
</template>
全ページの共通になるレイアウトはlayouts/default.vue
に置きます。
<nuxt/>
コンポーネントのところに、各ページのコンポーネントが挿入されます。
レイアウトが複数ある場合は、カスタムレイアウトを作成して、ページのコンポーネントで指定することで切り替える事ができます。また、エラーページが必要な場合もここに作成します。
middleware
認証などのレンダリングよりも前に実行したい関数をここに置きます。
pages
各ページのコンポーネントをここに置くと、ディレクトリ構造に従って自動的にVue Routerの設定を生成してくれます。
レンダリング前に非同期データの設定を行うasyncData/fetchや、ページごとにheadの設定をおこなうhead、レイアウトを指定するlayoutなど、ページコンポーネントでしか使えないプロパティやメソッドがあります。
他にもいくつかあるので、詳しくはドキュメントでご確認ください。
plugins
プラグインを追加したいときはこちらに置きます。
store
アプリケーション全体の状態を管理するVuexストアのファイルを置きます。
コンポーネントとはいったい
Vue.jsにおける単一ファイルコンポーネントとは、HTMLとそれに付随するスタイルやロジックをひとつの.vue
ファイルにまとめたもののことです。HTMLは<template>
タグ、スタイルは<style>
タグ、ロジックは<scripit>
タグに記述し、そのコンポーネントを組み合わせてWebサイトを構築していきます。
HTML - <template>
<template>
<div class="example">
<!-- テンプレート構文 -->
<p>Hello World!</p>
<p>{{ message }}</p>
<!-- 条件付きレンダリング -->
<p v-if="ok">Yes</p>
<!-- リストレンダリング -->
<ul>
<li v-for="item in items" :key="item.id">
{{ item.message }}
</li>
</ul>
<!-- クラスとスタイルのバインディング -->
<p :class="{ active: isActive }">hoge</p>
<!-- イベントハンドリング -->
<button @click="handleClick">button</button>
<!-- フォーム入力バインディング -->
<input v-model="text">
<!-- スロット -->
<slot />
<!-- nuxt-link -->
<nuxt-link to="/about">このサイトについて</nuxt-link>
</div>
</template>
HTMLは、コンポーネントの<template>
タグに記述していきます。
テンプレート構文や条件分岐など
テンプレート構文や条件分岐など、テンプレートにかかわるものは、Vue.jsのドキュメントを参照してください。
- Vue.js - テンプレート構文
- Vue.js - 条件付きレンダリング
- Vue.js - リストレンダリング
- Vue.js - クラスとスタイルのバインディング
- Vue.js - イベントハンドリング
- Vue.js - フォーム入力バインディング
- Vue.js - スロット
Nuxt.jsのコンポーネント
<nuxt>
や<nuxt-link>
などのコンポーネントは文字通りNuxt.jsのものです。
- Nuxt.js - nuxtコンポーネント
- Nuxt.js - nuxt-linkコンポーネント
- Nuxt.js - nuxt-childコンポーネント
- Nuxt.js - no-ssrコンポーネント
pugを使いたい
Vue.jsのテンプレートはデフォルトではHTMLとして扱われますが、lang
属性にプリプロセッサを指定することで、pugなどを使用できます。pugのコンパイラやローダーはNuxt.jsには含まれていないため、別途インストールする必要があります。(個人的にはpugのコードを読むのにHTMLの3倍ぐらい時間がかかってしまうので、あんまりですが・・・)
<template lang="pug">
h1.red Hello World!
</template>
$ npm install -D pug@2.0.3 pug-plain-loader
スタイル - <style>
<style lang="scss" scoped>
.example {
color: red
}
</style>
コンポーネントのスタイルは<style>
タグのなかに記述していきます。
特に属性の指定がない場合は、CSSがグローバルに適用されます。
スコープの限定
scoped
属性を追加することで、スタイルの影響範囲をコンポーネントにとどめられます。小規模な開発ではCSSがグローバルでも問題になることは少ないと思いますが、規模が大きくなると予期しないCSSの競合に悩まされるので、scoped
の恩恵が大きくなります。scoped
を付けた場合は、HTMLとCSSにdata
属性が追加された状態で生成されます。
<p class="example" data-v-f3f3eg9>hoge</p>
.example[data-v-f3f3eg9] {
color: red;
}
scssを使いたい
スタイルはデフォルトではCSSとして扱われますが、lang
属性にプリプロセッサを指定することで、scssなどを使用できます。SassのコンパイラやローダーはNuxt.jsには含まれていないため、別途インストールする必要があります。
$ npm install -D sass-loader node-sass
また、共通の変数やMixinをコンポーネントで使いたい場合は、nuxt-sass-resources-loaderが便利です。
パッケージをインストールした後で、nuxt.config.js
のmodules
にファイルへのパス追記するとコンポーネントから参照できるようになります。
$ npm install nuxt-sass-resources-loader
module.exports = {
modules: [
['nuxt-sass-resources-loader', [
'@/path/to/variables.scss',
'@/path/to/mixin.scss',
]],
]
}
トランジション
HTMLとスタイルの両方にかかわる機能として、要素にフェードインなどの効果を追加できるトランジションがあります。
要素のトランジション
コンポーネント内の要素へトランジションを適用したい場合は、Vue.jsのドキュメントを参考に実装します。あまり使うことはないかもですが、JavaScriptのフックを使用してアニメーションを実装することもできます。
ページ遷移のトランジション
ページ遷移時にトランジションを適用したい場合は、Nuxt.jsのドキュメントを参考に実装します。デフォルトではpage
というトランジション名が共通で使用されますが、ページごとにトランジションを変更することも可能です。
ロジック - <script>
<template>
<div class="example">
<p>{{ message }}</p>
<p>{{ count }}</p>
<p>{{ oddOrEven }}</p>
<button @click="handleClick">Click!</button>
</div>
</template>
<script>
import axios from 'axios'
export default {
// 親から受け取る状態
props: {
message: String
},
// コンポーネント内の状態
data() {
return {
count: 0
}
},
// レンダリング前に非同期でコンポーネントへ設定したい状態 - Nuxt.js
async asyncData ({ params }) {
const { data } = await axios.get('https://qiita.com/api/v2/tags/javascript')
return { tag: data }
},
// レンダリング前に非同期えVuexストアへデータを反映する - Nuxt.js
async fetch ({ store, params }) {
const { data } = await axios.get('https://qiita.com/api/v2/tags/css')
store.commit('setTag', data)
},
// 算出プロパティ
computed: {
oddOrEven() {
return this.count % 2 === 0 ? '偶数' : '奇数'
}
},
// プロパティの監視
watch: {
count(newCount, oldCount) {
console.log(`countが${oldCount}から${newCount}になりました。`)
}
},
// ライフサイクル
mounted() {
console.log('mounted!!')
},
// コンポーネント内のメソッド
methods: {
handleClick() {
this.count++
}
}
}
</script>
ロジックは、コンポーネントの<script>
タグに記述していきます。
アプリケーションの状態管理
Nuxt.jsでは、アプリケーションの状態管理にVuexというライブラリを使用しています。
Vuexとは
Vue.jsでは、コンポーネントごとに状態を持てますが、規模が大きくなっていくと、どこにどんな状態があるかわかりにくくなり、受け渡しのフローも秩序がなくなっていきます。そこで役に立つのが、状態を管理してくれるライブラリであるVuexです。Vuexを使用することで、状態を1ヶ所でまとめて管理でき、ルールに従って変更を行うことでフローがシンプルなります。
Reduxのようなライブラリを使用したことがある場合はすんなり理解できると思いますが、はじめての場合はデータを更新するための手順が煩雑に感じるかもしれません。
こればかりはフローを理解して慣れるしかないので、Nuxt.jsで本格的に開発を始める前に、Vuexのドキュメントに目を通して頭に入れておいたほうがスムーズに開発を進められると思います。
Nuxt.jsにおけるVuexの扱い
Nuxt.jsでは、store
ディレクトリをVuexのストアとして扱います。
export
の仕方によって、クラシックモードかモジュールモードのどちらで扱われるかが決まります。
Vuexを使わないという選択
Vuexの導入は必須ではないため、store
ディレクトリは使わず、Vue.jsのドキュメントにあるstoreパターンのような形でシンプルに実装することもできます。小さなアプリケーションでも、規模が大きくなる可能性がある場合は、最初からVuexを導入しておいたほうが良いですが、選択肢のひとつとしてはありだと思います。
以前、Nuxt.jsとstoreパターンで実装したことがあったので、参考までに。
https://github.com/noplan1989/aws-rough/blob/master/stores/index.js
fetch
<script>
import axios from 'axios'
export default {
async fetch ({ store, params }) {
const { data } = await axios.get('https://qiita.com/api/v2/tags/css')
store.commit('setTag', data)
}
}
</script>
SSRやページ遷移時のレンダリングが行われる前に、非同期にデータを取得してVuexストアへデータを設定したい場合は、fetchメソッドを使います。fetchメソッドは、Promiseを返却する必要があり、pages
内のコンポーネントでのみ使用できます。
コンポーネント内の状態
data
<script>
export default {
data() {
return {
count: 0
}
}
}
</script>
Vuexで管理せずに、コンポーネントのみで使用する状態は、dataに設定します。dataをコンポーネント内で使用する場合は、オブジェクトではなく、オブジェクトを返す関数を指定する必要があります。
Vue.js - データとメソッド
Vue.js - data
props
<script>
export default {
props: {
message: String
}
}
</script>
親のコンポーネントから受け取る状態はpropsで指定します。型の確認以外にも、デフォルト値の設定やバリデーションも行えます。
computed
<template>
<div class="example">
<p>{{ `${this.count % 2 === 0 ? '偶数' : '奇数'}` }}</p>
<p>{{ oddOrEven }}</p>
</div>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
computed: {
oddOrEven() {
return this.count % 2 === 0 ? '偶数' : '奇数'
}
}
}
</script>
data
やprops
の値に少し手を加えて表示したい場合、HTMLのテンプレート内で処理を行うことも可能ですが、テンプレートにロジックが入ってしまうのはあまり綺麗ではありません。そんなときには、computedプロパティで算出した値をテンプレートに表示するようにすれば、テンプレートとロジックが切り分けられてシンプルになります。
watch
<script>
export default {
data() {
return {
count: 0
}
},
watch: {
count(newCount, oldCount) {
console.log(`countが${oldCount}から${newCount}になりました。`)
}
}
}
</script>
状態の変化を監視してなにか処理を行いたい場合は、watchを使用します。
asyncData
<script>
import axios from 'axios'
export default {
async asyncData ({ params }) {
const { data } = await axios.get('https://qiita.com/api/v2/tags/javascript')
return { tag: data }
}
}
</script>
SSRやページ遷移時のレンダリングが行われる前に、コンポーネントへ状態を設定したい場合は、asyncDataメソッドを使います。asyncDataメソッドは、fetchメソッドと同様に、Promiseを返却する必要があり、pages
内のコンポーネントでのみ使用できます。
取得したデータは、data
と同じように扱われます。
Nuxt.js - 非同期なデータ
Nuxt.js - asyncData
ライフサイクル
<script>
export default {
// マウントされたときに呼び出されるメソッド
mounted() {
console.log('mounted!!')
}
}
</script>
Vue.jsとNuxt.jsを使う上で最も大事な概念のひとつにライフサイクルというものがあります。
ライフサイクルは、Vueインスタンスが生成されてから破棄されるまでの流れのことで、createdやmountedのような、区切りになるタイミングで、対応するメソッドが呼び出されます。
Vue.jsのライフサイクル
おそらく開発中に幾度となく見返すことになる図です。
インスタンスの生成から破棄までの流れのなかで、赤枠部分のメソッドが呼び出されます。それぞれのメソッドの詳しい説明は、APIドキュメントのオプション/ライフサイクルフックに記載があります。
Nuxt.jsの処理フロー
出典 : Nuxt.js - 図解
Nuxt.jsでは、Vue.jsのライフサイクルに加え、middlewareやasyncData/fetchなどのフローが加わります。
Vue インスタンスの ライフサイクル において、beforeCreate と created フックのみが クライアントサイドとサーバーサイドの両方で呼び出されることに注意してください。それ以外のすべてのフックはクライアントサイドでのみ呼び出されます。
プラグインのページに記述があるとおり、サーバーサイドではbeforeCreateとcreatedのみが呼び出され、それ以降はクライアントサイドで呼び出されます。
コンポーネント内のメソッド
<script>
export default {
methods: {
handleClick() {
this.count++
}
}
}
</script>
これについては特に補足することがありませんが、文字通りコンポーネント内から呼び出せるメソッドです。
ルーティング
Nuxt.jsでは、ルーティングにVue Routerというライブラリを使用しています。
Vue Routerを単体で使う場合は、ルートを自分で宣言しなければいけませんが、Nuxt.jsではpages
ディレクトリの構成に従って、自動的にVue Routerの設定を生成してくれます。自動生成時には、動的なルーティングや、ネストされたルートの設定などもできるので、詳しくはルーティングのドキュメントをご確認ください。
module.exports = {
router: {
linkActiveClass: 'is-active',
scrollBehavior(to, from, savedPosition) {
// ここにページ遷移時のスクロールの挙動
}
}
}
ルーターの設定はデフォルトで良い感じになっていますが、カスタマイズしたい場合は、nuxt.config.js
のrouter
プロパティに、さまざまな項目を設定できます。
Nuxt.jsのドキュメントに載っていない、プログラムによるナビゲーションなどの機能は、Vue Routerのドキュメントを参照してください。
titleやdescriptionなどの設定
module.exports = {
head: {
htmlAttrs: {
lang: 'ja'
},
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
]
}
}
<script>
export default {
head() {
return {
title: 'トップページ',
meta: [
{ hid: 'description', name: 'description', content: 'トップページの説明' }
]
}
}
}
</script>
Nuxt.jsでは、title
やdescription
をはじめとした<head>
の設定にvue-metaというライブラリを使用しています。
デフォルトになる設定はnuxt.config.js
のhead
プロパティに設定し、各ページの設定はpages
内のコンポーネントのheadメソッドに設定します。
開発環境
コマンド一覧
{
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate",
}
}
# 開発サーバーの起動
$ npm run dev
# ビルド
$ npm run build
# プロダクションモードでサーバーを起動(nuxt buildの後に実行する)
$ npm run start
# 静的ファイルの生成
$ npm run generate
package.json
のscripts
にコマンドがデフォルトで設定されているので、npm run 〇〇
で実行できます。--spa
などのオプションが必要な場合は、scripts
内のコマンドに追加する必要があります。
SSR・SPA・静的生成
Nuxt.jsでは、デフォルトでサーバーサイドレンダリング(SSR)をするように設定されています。
もしもSSRを望まない場合は、nuxt.config.js
のmodeプロパティにspa
を設定するか、コマンド実行時に--spa
を追加してください。
また、ルーティングに従って静的にファイルを生成してくれる強力な機能もあります。この機能のメリットについては別の記事にまとめているので参考までに。
ビルドの設定
デフォルトの設定でも良い感じにビルドしてくれますが、より細かい設定が必要な場合は、nuxt.config.js
のbuildプロパティで設定できます。設定が必要な項目はケースによって違うと思うので、よく使いそうなvendor
と、便利なanalyze
だけここでご紹介します。
vendor
module.exports = {
build: {
vendor: ['axios']
}
}
共通で使うaxios
などのパッケージは、何もせずに各ページで読み込んでしまうと無駄にファイルサイズが大きくなってしまいますが、vendor
プロパティに追加することで、vendor.〇〇〇.js
という別ファイルに書き出されるので、サイズを抑えられます。
analyze
analyze
プロパティにtrue
を設定するか、--analyze
オプションを追加してbuild
コマンドを実行すると、webpack-bundle-analyzerでバンドルファイルを視覚的に確認できます。ファイルサイズが気になったときに、ひと目で割合を占めているモジュールが何かわかるので便利です。
ビルドのたびに確認する必要はないと思うので、scripts
に追加して必要なときに実行できるようにしておくと楽です。
{
"scripts": {
"analyze": "nuxt build --analyze"
}
}
$ npm run analyze
テスト
Vue.jsにはコンポーネントのテスト、Nuxt.jsにはE2Eテストの例が記載されています。
デプロイ
SSR
$ npm run build
$ npm run start
Node.jsが動作するサーバーへアップした後で、パッケージをインストールし、build
してstart
したら、プロダクションモードでサーバーが起動します。ドキュメントのFAQにHerokuの例と、nginxをリバースプロキシとして使う例があったので参考までに。
SPA/静的生成
# SPAモード
# nuxt.config.jsでmodeを'spa'にしておく or コマンドのオプションに--spaをつけておく
$ npm run build
# 静的生成
$ npm run generate
上記のコマンドでdist
ディレクトリにファイルが生成されるので、あとはお好きなホスティングサービスへアップしてください。ドキュメントのFAQにNetlifyの例と、GitHub Pagesの例があったので参考までに。あと、自分の記事なのでなんかあれですが、CircleCI+S3でデプロイする記事があるので、AWSが好きな方はそちらもご確認ください。
その他
設定ファイルをもっと
nuxt.config.js
の設定はこれまでにもたくさん出てきましたが、他にもローディングや環境変数などさまざまな項目があるので、気になるかたはドキュメントをご確認ください。
リリースノートが見たい
仕事を探したい
Hire expert Vue.js developers & find Vue.js jobs
記事を書いていて知ったのですが、Nuxt.jsのエコシステムのメニュー中にVueJobsというVue.jsを利用する開発者のための求人サイトがありました。
おわり
ダラダラと長くなってしまいましたが、すべてを網羅できているわけではないので、詳しくは各ドキュメントをご確認ください。