Help us understand the problem. What is going on with this article?

Vue.jsとNuxt.jsを使っていて、どっちのドキュメントを見ればいいんだ?ってなったときのために機能を整理する。

More than 1 year has passed since last update.

開発に慣れてきたら迷うことはないと思いますが、Vue.jsをさわるのが初めてでNuxt.jsを採用した場合、どっちのドキュメントを見ればよいかわからずに、さまよってしまう事があるかもしれません(自分がそうでした)。そんなときのための、長ったらしいメモです。

この記事を書いている時点では、Vue.jsが2.5、Nuxt.jsが1.2です。

ドキュメントの歩き方

まずはガイドで概要を把握して、細かいことが気になったらAPIをあたります。
Vue.jsでの開発がはじめてであれば、本格的に書き始める前に、スタイルガイドを見ておくと、手戻りが少なくなるかもしれません。

ガイド

フレームワークの使い方がわかりやすく書かれています。
はじめにから順に読んでいって、大体こんなことができるんだぁーと頭に入ったら、手を動かしてみてハマったらガイドを眺める、の繰り返しで体が慣れていくと思います。

Vue.js - はじめに
Nuxt.js - はじめに

API

プロパティやメソッドの具体的な仕様を確認できます。例えば、ガイドでは算出プロパティとウォッチャというページに、それぞれをどのように使えば良いかが書かれていますが、APIにはcomputedwatchがどのような値をとって、どんな挙動をするかが書かれています。

コードのサンプルが色々と載っています。

スタイルガイド

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には、faviconrobots.txtなど、
Webpackのローダーをかませずに、そのまま出力したいファイルを置きます。
参照する際は、staticがなくなってルートにいるので注意してください。

出力例 : /static/robots.txt → /robots.txt

components

<script>
import AppLogo from '@/components/AppLogo.vue'

export default {
  components: {
    AppLogo
  }
}
</script>

pageslayoutsなどの特別なコンポーネント以外は、すべてここに置きます。

コンポーネント内で別ファイルのコンポーネントを使いたいときは、importしたコンポーネントをcomponentsプロパティに指定することで、利用できるようになります。

Vue.js - モジュールシステム

layouts

layouts/default.vue
<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など、ページコンポーネントでしか使えないプロパティやメソッドがあります。

他にもいくつかあるので、詳しくはドキュメントでご確認ください。

Nuxt.js - ページ

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のドキュメントを参照してください。

Nuxt.jsのコンポーネント

<nuxt><nuxt-link>などのコンポーネントは文字通りNuxt.jsのものです。

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.jsmodulesにファイルへのパス追記するとコンポーネントから参照できるようになります。

$ npm install nuxt-sass-resources-loader
nuxt.config.js
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で指定します。型の確認以外にも、デフォルト値の設定やバリデーションも行えます。

Vue.js - プロパティ
Vue.js - 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>

datapropsの値に少し手を加えて表示したい場合、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インスタンスが生成されてから破棄されるまでの流れのことで、createdmountedのような、区切りになるタイミングで、対応するメソッドが呼び出されます。

Vue.jsのライフサイクル


出典 : Vue.js - ライフサイクルダイアグラム

おそらく開発中に幾度となく見返すことになる図です。
インスタンスの生成から破棄までの流れのなかで、赤枠部分のメソッドが呼び出されます。それぞれのメソッドの詳しい説明は、APIドキュメントのオプション/ライフサイクルフックに記載があります。

Nuxt.jsの処理フロー


出典 : Nuxt.js - 図解

Nuxt.jsでは、Vue.jsのライフサイクルに加え、middlewareasyncData/fetchなどのフローが加わります。

Vue インスタンスの ライフサイクル において、beforeCreate と created フックのみが クライアントサイドとサーバーサイドの両方で呼び出されることに注意してください。それ以外のすべてのフックはクライアントサイドでのみ呼び出されます。

Nuxt.js - プラグイン

プラグインのページに記述があるとおり、サーバーサイドではbeforeCreatecreatedのみが呼び出され、それ以降はクライアントサイドで呼び出されます。

コンポーネント内のメソッド

<script>
export default {
  methods: {
    handleClick() {
      this.count++
    }
  }
}
</script>

これについては特に補足することがありませんが、文字通りコンポーネント内から呼び出せるメソッドです。

Vue.js - methods

ルーティング

Nuxt.jsでは、ルーティングにVue Routerというライブラリを使用しています。

Vue Routerを単体で使う場合は、ルートを自分で宣言しなければいけませんが、Nuxt.jsではpagesディレクトリの構成に従って、自動的にVue Routerの設定を生成してくれます。自動生成時には、動的なルーティングや、ネストされたルートの設定などもできるので、詳しくはルーティングのドキュメントをご確認ください。

nuxt.config.js
module.exports = {
  router: {
    linkActiveClass: 'is-active',
    scrollBehavior(to, from, savedPosition) {
      // ここにページ遷移時のスクロールの挙動
    }
  }
}

ルーターの設定はデフォルトで良い感じになっていますが、カスタマイズしたい場合は、nuxt.config.jsrouterプロパティに、さまざまな項目を設定できます。

Nuxt.jsのドキュメントに載っていない、プログラムによるナビゲーションなどの機能は、Vue Routerのドキュメントを参照してください。

titleやdescriptionなどの設定

nuxt.config.js
module.exports = {
  head: {
    htmlAttrs: {
      lang: 'ja'
    },
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' }
    ]
  }
}
pages/index.vue
<script>
export default {
  head() {
    return {
      title: 'トップページ',
      meta: [
        { hid: 'description', name: 'description', content: 'トップページの説明' }
      ]
    }
  }
}
</script>

Nuxt.jsでは、titledescriptionをはじめとした<head>の設定にvue-metaというライブラリを使用しています。

デフォルトになる設定はnuxt.config.jsheadプロパティに設定し、各ページの設定はpages内のコンポーネントのheadメソッドに設定します。

開発環境

コマンド一覧

package.json
{
  "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.jsonscriptsにコマンドがデフォルトで設定されているので、npm run 〇〇で実行できます。--spaなどのオプションが必要な場合は、scripts内のコマンドに追加する必要があります。

Nuxt.js - コマンド
npm-scripts

SSR・SPA・静的生成

Nuxt.jsでは、デフォルトでサーバーサイドレンダリング(SSR)をするように設定されています。
もしもSSRを望まない場合は、nuxt.config.jsmodeプロパティにspaを設定するか、コマンド実行時に--spaを追加してください。

また、ルーティングに従って静的にファイルを生成してくれる強力な機能もあります。この機能のメリットについては別の記事にまとめているので参考までに。

ビルドの設定

デフォルトの設定でも良い感じにビルドしてくれますが、より細かい設定が必要な場合は、nuxt.config.jsbuildプロパティで設定できます。設定が必要な項目はケースによって違うと思うので、よく使いそうなvendorと、便利なanalyzeだけここでご紹介します。

vendor

nuxt.config.js
module.exports = {
  build: {
    vendor: ['axios']
  }
}

共通で使うaxiosなどのパッケージは、何もせずに各ページで読み込んでしまうと無駄にファイルサイズが大きくなってしまいますが、vendorプロパティに追加することで、vendor.〇〇〇.jsという別ファイルに書き出されるので、サイズを抑えられます。

analyze

analyzeプロパティにtrueを設定するか、--analyzeオプションを追加してbuildコマンドを実行すると、webpack-bundle-analyzerでバンドルファイルを視覚的に確認できます。ファイルサイズが気になったときに、ひと目で割合を占めているモジュールが何かわかるので便利です。

ビルドのたびに確認する必要はないと思うので、scriptsに追加して必要なときに実行できるようにしておくと楽です。

package.json
{
  "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

VueJobs

記事を書いていて知ったのですが、Nuxt.jsのエコシステムのメニュー中にVueJobsというVue.jsを利用する開発者のための求人サイトがありました。

おわり

ダラダラと長くなってしまいましたが、すべてを網羅できているわけではないので、詳しくは各ドキュメントをご確認ください。

nishinoshake
AWSと映画のサイトを趣味で作ってます。
https://noplan.cc
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした