Nuxt.js を使ってみました



最近 Vue.js を使うことが多いのですが、Vue.js ベースのフレームワークである Nuxt.js を使ったことがなかったのでチュートリアルを参考にして動きの確認をしてみました。




*Nuxt.js とは

Vue.js ベースのフレームワークで、SSR(サーバーサイドレンダリング)など Vue.js では手間のかかる実装を簡単にできるようになっています。
React.js ベースのフレームワークに Next.js がありますが、こちらよりも Nuxt.js のほうが機能が充実していて人気があるようです。
Vue.js の2系からはSSRの機能をサポートしていますが、Nuxt.js は webpack, Vuex, Vue router が標準で入っており、自動的なルーティングやプリコンパイルが可能になっています。
また、ミドルウェアやasyncDataといった拡張システムを使うことでコンポーネントのデータをセットする前に非同期の処理を行い、レンダリングすることができるようになっています。


*参考



*環境

  • MacOS
  • Nuxt 2.4.0
  • npm 6.3.0


*プロジェクト作成

任意の作業フォルダでnpx create-nuxt-app {プロジェクト名}を実行すると、Nuxt.js のプロジェクトが作成されます。コマンド実行後にいくつか質問されるので適当に答えます。
$ npx create-nuxt-app real-world-nuxt

? Project name real-world-nuxt
? Project description My super-excellent Nuxt.js project
? Use a custom server framework express
? Choose features to install Progressive Web App (PWA) Support, Linter / Formatter, Prettier, Axios
? Use a custom UI framework none
? Use a custom test framework jest
? Choose rendering mode Universal
? Author name XXXX
? Choose a package manager npm

プロジェクト作成後のディレクトリ構成は下記になります。
Vue.js のrouter.jsが必要なくなった代わりに、pagesディレクトリにルーティング用のVueファイルが必要になります。
real-world-nuxt
|
├── assets          --- SASS,font,image などコンパイルされないファイル(webpackの影響を受ける)
|
├── components      --- Vue.jsのコンポーネント(asyncData,fetchは使用不可)
|   └── logo.vue
|
├── layouts         --- ベースとなる共通レイアウトなどのvueファイル
|   └── default.vue
|
├── middleware      --- レンダリング前に実行する関数定義
|
├── pages           --- viewやルーティングファイル
|   └── index.vue
|
├── plugins         --- Vue.jsのアプリをインスタンス化する前に実行するJavascriptプラグイン
|
├── server          --- SSRの設定
|   └── index.js
|
├── static          --- 変更されないファイル(ファビコンなど)
|   ├── favicon.ico
|   └── icon.png
|
├── store           --- Vuexストアのファイル(index.jsを入れると有効になる)
|
├── nuxt.config.js  --- Nuxt.jsのカスタム設定
├── package.json    --- アプリが依存するパッケージやスクリプトを記述
├── jest.config.js  --- テストの設定
├── node_modules
└── test

プロジェクトディレクトリ直下で下記コマンドを実行して、アプリケーションを起動します。
$ npm run dev

下記URLにアクセスすると Nuxt.js のサンプル画面が表示されます。
http://localhost:3000/













*レイアウトを修正

ベースとなるレイアウトを変更します。
layouts/default.vueを下記に書き換えます。
<layouts/default.vue>
<template>
  <div id="app">
    <nuxt />
  </div>
</template>

<style>
html {
  -webkit-text-size-adjust: 100%;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
}
body {
  margin: 0;
  font-family: 'Open Sans', sans-serif;
  font-size: 16px;
  line-height: 1.5;
}
#app {
  box-sizing: border-box;
  width: 500px;
  padding: 0 20px 20px;
  margin: 0 auto;
}
hr {
  box-sizing: content-box;
  height: 0;
  overflow: visible;
}
a {
  color: #39b982;
  font-weight: 600;
  background-color: transparent;
}
img {
  border-style: none;
  width: 100%;
}
h1,
h2,
h3,
h4,
h5,
h6 {
  display: flex;
  align-items: center;
  font-family: 'Montserrat', sans-serif;
}
h1 {
  font-size: 50px;
  font-weight: 700;
}
h2 {
  font-size: 38px;
  font-weight: 700;
}
h3 {
  font-size: 28px;
  font-weight: 700;
}
h4 {
  font-size: 21px;
  font-weight: 700;
}
h5 {
  font-size: 16px;
  font-weight: 700;
}
h6 {
  font-size: 15px;
  font-weight: 700;
}
b,
strong {
  font-weight: bolder;
}
small {
  font-size: 80%;
}
.eyebrow {
  font-size: 20px;
}
.-text-primary {
  color: #39b982;
}
.-text-base {
  color: #000;
}
.-text-error {
  color: tomato;
}
.-text-gray {
  color: rgba(0, 0, 0, 0.5);
}
.-shadow {
  box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.13);
}
.badge {
  display: inline-flex;
  height: 26px;
  width: auto;
  padding: 0 7px;
  margin: 0 5px;
  background: transparent;
  border-radius: 13px;
  font-size: 13px;
  font-weight: 400;
  line-height: 26px;
}
.badge.-fill-gradient {
  background: linear-gradient(to right, #16c0b0, #84cf6a);
  color: #fff;
}
button,
label,
input,
optgroup,
select,
textarea {
  display: inline-flex;
  font-family: 'Open sans', sans-serif;
  font-size: 100%;
  line-height: 1.15;
  margin: 0;
}
button,
input {
  overflow: visible;
}
button,
select {
  text-transform: none;
}
button,
[type='button'],
[type='reset'],
[type='submit'] {
  -webkit-appearance: none;
}
button::-moz-focus-inner,
[type='button']::-moz-focus-inner,
[type='reset']::-moz-focus-inner,
[type='submit']::-moz-focus-inner {
  border-style: none;
  padding: 0;
}
button:-moz-focusring,
[type='button']:-moz-focusring,
[type='reset']:-moz-focusring,
[type='submit']:-moz-focusring {
  outline: 2px solid #39b982;
}
label {
  color: rgba(0, 0, 0, 0.5);
  font-weight: 700;
}
input,
textarea {
  box-sizing: border-box;
  border: solid 1px rgba(0, 0, 0, 0.4);
}
textarea {
  width: 100%;
  overflow: auto;
  font-size: 20px;
}
[type='checkbox'],
[type='radio'] {
  box-sizing: border-box;
  padding: 0;
}
[type='number']::-webkit-inner-spin-button,
[type='number']::-webkit-outer-spin-button {
  height: auto;
}
[type='search'] {
  -webkit-appearance: textfield;
  outline-offset: -2px;
}
[type='search']::-webkit-search-decoration {
  -webkit-appearance: none;
}
[type='text'],
[type='number'],
[type='search'],
[type='password'] {
  height: 52px;
  width: 100%;
  padding: 0 10px;
  font-size: 20px;
}
[type='text']:focus,
[type='number']:focus,
[type='search']:focus,
[type='password']:focus {
  border-color: #39b982;
}
::-webkit-file-upload-button {
  -webkit-appearance: button;
  font: inherit;
}
[hidden] {
  display: none;
}
.error {
  border: 1px solid red;
}
select {
  width: 100%;
  height: 52px;
  padding: 0 24px 0 10px;
  vertical-align: middle;
  background: #fff
    url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E")
    no-repeat right 12px center;
  background-size: 8px 10px;
  border: solid 1px rgba(0, 0, 0, 0.4);
  border-radius: 0;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
}
select:focus {
  border-color: #39b982;
  outline: 0;
}
select:focus::ms-value {
  color: #000;
  background: #fff;
}
select::ms-expand {
  opacity: 0;
}
.field {
  margin-bottom: 24px;
}
.error {
  border: 1px solid red;
}
.errorMessage {
  color: red;
}
</style>

pages/index.vueに書かれていた内容を削除し、下記だけに書き換えます。
<pages/index.vue>
<template>
  <div>
    <h1>Events</h1>
  </div>
</template>

アプリケーションを起動します。
$ npm run dev

下記URLにアクセスすると更新された内容が表示されます。
http://localhost:3000/










*コンポーネントを追加

画面を追加するためにpagesディレクトリ配下にコンポーネントを新規作成します。
<pages/create.vue>
<template>
  <div>
    <h1>Create An Event</h1>
  </div>
</template>

Nuxt.js はルーティングを記述しなくても自動でルーティングしてくれるので、create.vueを新規作成しただけでcreate.vueの内容を画面に表示することができます。
http://localhost:3000/create










*ナビゲーションバーを追加

画面上部に各画面共通のナビゲーションバーを追加します。
componentsディレクトリ配下にNavBar.vueを新規作成します。
Vue.js ではrouter-linkを使っていましたが、Nuxt.js ではnuxt-linkを使います。
<components/NavBar.vue>
<template>
  <div class="nav">
    <nuxt-link to="/" class="brand">Real World Events</nuxt-link>
    <nav>
      <nuxt-link to="/">List</nuxt-link>&nbsp;|
      <nuxt-link to="/create">Create<nuxt-link>
    </nav>
  </div>
</template>

<style scoped>
.brand {
  font-family: 'Montserrat', sans-serif;
  font-weight: 700;
  font-size: 1.5em;
  color: #39b982;
  text-decoration: none;
}
.nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  height: 60px;
}
.nav .nav-item {
  box-sizing: border-box;
  margin: 0 5px;
  color: rgba(0, 0, 0, 0.5);
  text-decoration: none;
}
.nav .nav-item.router-link-exact-active {
  color: #39b982;
  border-bottom: solid 2px #39b982;
}
.nav a {
  display: inline-block;
}
</style>

ベースのレイアウトに NavBar を追加します。
<layouts/default.vue>
<template>
  <div id="app">
    <nav-bar />     <--- 追加
    <nuxt />
  </div>
</template>
// ------ ↓追加ここから -------
<script>
import NavBar from '~/components/NavBar.vue'
export default {
  components: {
    NavBar
  }
};
// ------ ↑追加ここまで -------
</script>
<style>
...

画面にアクセスするとナビゲーションバーが表示され、ListCreateをクリックすることでそれぞれの画面を表示することができます。








*所感

Vue.js と比べると、router.jsの設定をしなくても自動的にルーティングされる点は非常に便利でした。
ディレクトリ構成についてはプロジェクト作成時に細かく分けてくれるので、人によって構成が変わるといったことがなくて良いですが少し学習コストがかかりそうです。
比較して良くない点では、クライアント側とサーバーサイド側の両方でビルドを行なっているせいか、ビルドの時間は Vue.js よりも長いと感じました。ただ、これは解消できるような記事もあったので、今回使っていない機能も含めてよく調べてみようと思います。

Previous
Next Post »

人気の投稿