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

《Nuxt.js》アトミックデザインの静的サイトを構築するために必要な小粒なTipsまとめ。

Nuxt.js は SSR(サーバーサイドレンダリング)や SPA の Vue.js サイトを簡単に作成するためのフレームワークです。Nuxt は非同期データ、ミドルウェア、ルーティングなどを管理するために必要な環境を提供してくれます。Angular Universal、Next.jsと同様の位置付けです。

Nuxt のいいところ

Nuxt はVue / Vue-Router / Vue-Meta / Vuex をインクルードしているので、SSR/SPA開発をするために必要なものをインクルード/設定する手間を省けます。

というような感じで、巷では高度なSSR/SPAを簡単に実装できる、という特徴が語られますが、nuxt は静的なウェブサイト構築にもすごく有能です。generateコマンドでの静的Webサイトの生成で、Jekyllなどの静的サイト生成ツールの機能があり、アトミックデザインで語られるようなコンポーネント指向のディレクトリ構造になっているところが素敵です。また、コードの分割や CSS の HTML へのインライン展開など、高速化のプラクティスも実装することができます。

Nuxt で静的なサイトを作る

上記ドキュメントを見れば、あらかたやりたいことが知れますが、ここでは「静的サイトを構築するための知識」ということを中心に、ぼくが必要だったことなどをまとめます 🍟

Nuxt のかんたんな環境構築

Nuxt をかんたんに始めるために、create-nuxt-app を使ってテンプレートを利用します。

/terminal.bath
npx create-nuxt-app your-title

Nuxt のコマンド

開発するためにコマンドを叩きます。

/terminal.bath
cd ./your-title
yarn run dev

http://localhost:3000 にアクセスすると Nuxt のテンプレートページが表示されます。

開発ファイルから静的なファイルを生成するには

/terminal.bath
yarn run generate

とコマンドを叩くと、 dist フォルダが生成されます。このフォルダをこのままFTPでアップするとOKです。

Nuxt のディレクトリ構造

Nuxt ではディレクトリ構造を把握しておく必要があります。

この中でコンポーネントをアトミックデザインで開発する要素としてのディレクトリを紹介しておきます。

  • sass variable -> atoms
  • components/*.vue -> molecules / organisms
  • layouts -> template
  • pages -> pages

layouts/*.vue -> template

ページのテンプレートになるレイアウトを layouts ディレクトリに置きます。ヘッダー/フッター/メインコンテンツなどのレイアウトをここで定義します。

どのレイアウトを使うか指定するのには、ページコンポーネント(pages ディレクトリの .vue ファイル)の layout プロパティに指定します。 default.vue ファイルが、デフォルトのテンプレートとして利用されるようになります。

pages/blog/index.vue
<script>
export default {
  layout: 'blog', // layouts/blog.vue を読み込む
}
</script>

のように指定します。

ここがアトミックデザインの template に相当します。

pages/*.vue -> pages

Nuxt はこのディレクトリ内のすべての *.vue ファイルを読み込み、vue-router の設定を生成します。 pages のツリー構造がサイトのディレクトリ構造に反映されます。pages ディレクトリ内のファイル変更は常に監視されていて、ページを追加したときなど、タスクの再起動は不要になっています。また、JSON などのデータを注入して動的にディレクトリを生成することもできます。

これがアトミックデザインの pages に相当します。

components/*.vue -> molecules / organisms

Vue のコンポーネントファイルを components ディレクトリに格納します。

ここが粒度によりアトミックデザインの molecules や organisms に相当します。

Nuxt での Atoms はコンポーネント共通の変数やmixinなどで定義するイメージです。読み込み方は後述しています。

nuxt.config.js

nuxt.config.js は nuxt の設定ファイルです。ここにいろいろ記述するのが nuxt の関門な気がします。webpack.config.js の代わりにこれを触る感じですね。

head への設定

titleTemplate オプションで、アプリケーションで使うタイトルのテンプレートを指定します。各ページやレイアウトでtitleオプションをセットすると、レンダリング前に%s がプレースホルダーとして機能し、タイトルが反映されます。

nuxt.config.js
module.exports = {
  head: {
    titleTemplate: '%s >> uto-usui',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
    ]
  },
}

Sassの共通の変数やmixinを読み込む

これについては以前まとめました。

ここがアトミックデザインの atoms に相当します。

CSS を外部化する

デフォルトでは CSS は Javascript 内のアセットとしてインクルードされてしまいますが、静的な CSS としても出力可能です。

nuxt.config.js
build: {
  extractCSS: true
}

ルートディレクトリの設定

プロジェクトがドメインルート直下でない場合や、開発環境と本番環境に差異がある場合は、routerにそのパスを指定します。nuxt のサイト内リンクはルート相対パスで張られるので、この設定が必要になります。

nuxt.config.js
router: {
  base: process.env.NODE_ENV === 'dev' ? '/' : '/test/'
}

autoprefixer の設定をカスタマイズ

これは以前まとめました。

エイリアスを設定する

Nuxt ではルートへのエイリアスが用意されていて、 ~ or @ でアクセスできます。

pages/index.vue
<script>
import Button from '~/components/Button.vue'
</script>

これを拡張して、エイリアスを追加するには、

nuxt.config.js
  build: {
    extend (config) {
      config.resolve.alias['Sass'] =  path.resolve(__dirname, './assets/sass/');
      config.resolve.alias['Js'] =  path.resolve(__dirname, './assets/js/');
      config.resolve.alias['Images'] =  path.resolve(__dirname, './assets/images/');
    },
  }

のようにします。
するとコンポーネントで @import "~Sass/object/component/_item"; のようにアクセスできるようになります。

重複するインポートをまとめる

複数の pages のコンポーネントで ライブラリを import している場合、各ページごとにライブラリを読み込んだ状態でビルドされるので、build.vendor に指定して共通のファイルにまとめるようにします。

nuxt.config.js
  build: {
    vendor: [
      'gsap'
    ]
  }

ただしこれは v2 で改善され、不要になるみたいです。

動的なディレクトリ(ルート)を作る

Nuxt では JSON などのデータから、動的なディレクトリを生成することができます。

次のような JSON を用意します。

blog.json

[
  {
    "id": "01",
      "title": "Hi",
  },
  {
    "id": "02",
      "title": "HiHi",
  }
]

nuxt.config.jsgenerate で、生成するためのルートを定義します。

nuxt.config.js
  generate: {
    routes(callback) {
      const blogData = require('./assets/json/blog.json')
      let routes = blogData.map(blog => `/blog/${blog.id}`)
      callback(null, routes)
    }
  }

これで、JSON の id を元に /blog/01/index.html/blog/02/index.html が生成されます。

開発のためのお役立ちな tips

実開発に便利だったり必要だった設定など。

https 環境でサーバを起動する

create-nuxt-app de プロジェクトを始めると http://localhost:3000 にアクセスして開発を進めることになりますが、https 環境で開発を進めたいときもあるとおもいます。

これは別記事でまとめました。

WebStorm のパスを解決

IDE の WebStorm で、~@ のパスがサジェストされるように、webpack.config.js を作成し、次のように記述します。

webpack.config.js
module.exports = {
  resolve: {
    extensions: ['.js', '.json', '.vue'],
    root: path.resolve(__dirname),
    // for WebStorm
    alias: {
      '@': path.resolve(__dirname),
      '~': path.resolve(__dirname),
    }
  }
};

また、先に挙げたようにエイリアスを追加している場合もここに記述しておくと、サジェストしてくれるようになります。

scrollToTop が効かない

Nuxt のページ遷移で、子ルートがあるときはスクロール位置をキープする、というデフォルトの挙動があります。子ルートをレンダリングするときスクロールポジションを初期化させたいときは page ごとに scrollToTop: true と設定しますが、これが効かない状況のときは、nuxt.config.js に次の内容を記述します。

nuxt.config.js
  router: {
    scrollBehavior(to, from, savedPosition) {
      if (savedPosition) {
        return savedPosition
      } else {
        let position = {};
        if (to.matched.length < 2) {
          position = { x: 0, y: 0 }
        } else if (to.matched.some(r => r.components.default.options.scrollToTop)) {
          position = { x: 0, y: 0 }
        }
        if (to.hash) {
          position = { selector: to.hash }
        }
        return position;
      }
    },
  },

ビュー全体のレンダリングを待つ

これは Vue の知見でもありますが、page/* のコンポーネントで、ページ遷移時にwindow.onload みたいな処理をしたいとき、次のようなコードを書きます。

pages/sample.vue
<script>
export default {
  mounted() {
    // 子コンポーネントのロードを無視して実行
    this.$nextTick(() => {
      // ビュー全体がレンダリングされた後に実行
    })
  }
}
</script>

mounted のフックで実行すると、子コンポーネントを無視して実行してしまうので、全てのレンダリングを待つときは、this.$nextTick を利用します。

IE11 へのポリフィル

頭を悩ましたくない IE のやつですが、プロダクションではまだまだ当たり前ですよね … polyfill.io を使うと柔軟に対応できるかと思います。 nuxt.config.js から head 内に読み込ませます。

nuxt.config.js
head: {
  script: [{ src: https://cdn.polyfill.io/v2/polyfill.min.js?features=default’ }],
},

gtag.jsのトラッキング

GAトラッキングの gtag.js のモジュールが公開されていなかったので(たぶん)、plugins ディレクトリに gtag.js というスクリプトを作りました。

plugins/gtag.js
export default ({app, store}) => {

  if (process.env.NODE_ENV !== 'production') return;

  (function (win, doc, script, url) {
    let a = doc.createElement(script);
    let m = doc.getElementsByTagName(script)[0];
    a.async = 1;z
    a.src = url;
    m.parentNode.insertBefore(a, m);
  })(window, document, 'script', 'https://www.googletagmanager.com/gtag/js?id=UA-xxxxxx-1');

  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'UA-xxxxxxx-1');

  app.router.afterEach((to, from) => {
    gtag('config', 'UA-xxxxxxx-1', {
      'page_path': router.history.base + to.fullPath,
      'page_title': store.state.title,
    });
  });

}

ここでは store.state.title で vuex の state にセットしている title にアクセスしています。省略しますが、page/*.vue でタイトルを State に渡すメソッドを叩いて受け渡すような処理をしています。

nuxt.config.js で読み込みます。

nuxt.config.js
  plugins: [
    { src: '~plugins/gtag.js', ssr: false }
  ]

ssr: false としているのは、windowdocumentundefined にならないようにするためです。

と、自作してみたんですが

その後 GitHub - nuxt-community/google-gtag: Enable google gtagjs for NuxtJs が作られていまして、これを利用するのがいいと思います 🎉

ただし、 router.base でルートディレクトリを変更している場合、送信先の path が
ずれてしまうので注意が必要です(pull request しました)。

port と host を変更する

アクセスする開発用サーバの port と host を変更します。
Host を設定すると、同一のネットワークのことなるデバイスからアクセスできるようになるので、 モバイルデバイスの実機デバッグや virtualbox 環境のデバッグなどが捗ります。ここを設定するのがおすすめです。

package.json

"scrips": {
  "dev:development": PORT=8080 HOST=0.0.0.0 nuxt"
}

Nuxt のビルドとデプロイをCIでやってみる

Nuxt を GitLab CI でビルドして GitLab Pages に自動デプロイするためのサンプルです。

プロジェクトルートに次のコードを設置して、master にコミットすると CI が動きます。

gitlab-ci.yml
image: node:latest
pages:
  stage: deploy
  only:
    - master
  before_script:
    - 'yarn config set cache-folder .yarn'
    - 'yarn install'
  script:
    - 'yarn generate'
    - 'cp -pr dist public'
  artifacts:
    paths:
      - public

ちょっと注意

なぜか、CI上で ~@ のエイリアスの解決ができなかったので、nuxt.config.jsで再定義します。

nuxt.config.js
  build: {
    extend() {
      config.resolve.alias['~'] =  path.resolve(__dirname);
      config.resolve.alias['@'] =  path.resolve(__dirname);
    }
  }

GitLab CI すごく便利です○

まとめ

というわけで、Nuxt で静的サイトを作るための小粒な知識のまとめでした。
Transition や Vuex などのことを省いてしまっていますが、強力なドキュメントがあるのでそちらを参考にすればよいかと思います*

Nuxt を使うようになって、ほんとうに静的サイト構築が楽になったので、各地でオススメしています!

ぜひとも😇

Nuxt を深めるための資料

Nuxt 関連書籍

Nuxt.jsビギナーズガイド―Vue.js ベースのフレームワークによるシングルページアプリケーション開発 | 花谷 拓磨 |本 | 通販 | Amazon
Vue.js入門 基礎から実践アプリケーション開発まで | 川口 和也, 喜多 啓介, 野田 陽平, 手島 拓也, 片山 真也 |本 | 通販 | Amazon
基礎から学ぶ Vue.js | MIO | 工学 | Kindleストア | Amazon

おわります。

Why do not you register as a user and use Qiita more conveniently?
  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
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