虎の穴開発室ブログ

虎の穴ラボ株式会社所属のエンジニアが書く技術ブログです

MENU

新規プロダクトにNuxtを選んでみて!

f:id:toranoana-lab:20210315124753p:plain

皆さんこんにちは、とらのあなラボのY.Fです。

最近のプロダクト開発ではReact.jsやVue.js、Angularを使うケースがとても多いかと思います。この記事を読んでいる皆様もいずれかのフレームワーク/ライブラリを利用したことがあるという方も多いのではないでしょうか?

ご多分に漏れず、とらのあなラボでも新しいプロダクトではどんどんVue.jsやReact.jsを採用していこうとしています。今回の記事では新規プロダクトに、数ある選択肢の中からNuxt.jsを選んだ理由、選んでどうだったかについて話してみたいと思います。

はじめに

とらのあなラボではいくつかのプロダクトを開発しています。有名なところだと

fantia.jp

ec.toranoana.jp

などがあります。

とらのあなラボでの利用技術は主に以下になります。

  • Ruby on Rails、jQuery
    • Fantia等
  • Java、Kotlin、Spring Framework、jQuery
    • 通販等

基本的には昔ながらのHTMLをレンダリングするフレームワークに、プラスアルファ程度でJavaScriptが乗っかっているような形です。

プラスアルファ程度とはいえ結構ガッツリとJavaScriptが使われている箇所もあり、既存のものをモダンフレームワークに置き換えるのはなかなか骨が折れそうです。 しかし、新規プロダクトなら使えるはず!ということでNuxt.jsの導入に踏み切りました。

また、タイミング的にVue.jsもしくはReact.jsの経験があるメンバーが多数入社していたことも追い風となりました。

Nuxt.jsを選んだ理由

Nuxt.jsを選択する際の他の候補としてはNext.jsがありました。実際にはNuxt.jsを選びましたが、以下の理由によるものです。

  • 社内アプリではVue.js+TypeScriptを使ったプロダクトがあったので実績がある
  • デザイナーが深く関わることがわかっていたので既存のHTMLに近いほうが良かった
    • jsxを避けたかった
  • 採用への訴求

懸念材料としては下記のようなものがありました。

  • Vue3への移行過渡期
    • 社内アプリではcomposition apiを使ったものがあるので移行する場合にも知見は問題ないと判断
    • ただし、class componentにしてしまうと移行が大変なのでoptions apiを利用することに

また、当然ながらTypeScriptも採用しています。

導入

詳しい人が数名いるとはいえ、全員が全員Vue.js,Nuxt.jsについて理解があるわけではありませんでした。また、デザイナーの方に作業をお願いする場合もあります。

そこで、ハンズオン形式で、導入用スライドを作り、事前に技術情報を共有することにしました。内容としては、

  • Vue.js、Nuxt.jsの環境作成方法
    • cliツールを作ったときのディレクトリ構成について
  • コンポーネントという概念について
  • propsやstateについて
  • Nuxt固有のルーティングについて

などを中心にまとめました。

Vue.jsに関してコンポーネントを使うフレームワークが初めての人をターゲットにしました。デザイナーの方にまで深く理解してもらうのは酷だと思ったので、実際に作業するときに困らないように、CSSやHTMLに手を加える場合のことを考えてディレクトリ構成などについても説明するようにしました。

Nuxt.js(+ TypeScript)を使ってみてよかったところ

実際に良かったところをあげてみようと思います。

ルーティングが直感的

pages配下に*.vueファイルを置くだけでルーティングされたページが出来上がります。ディレクトリ構造でページが一目瞭然なのでデザイナーに連携するときも簡単でした。

例として、pages/hello_world.vueを以下のように作ります

<template>
  <div class="container">
    HelloWorld
  </div>
</template>

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({})
</script>

そうすると、自動で http://localhost:3000/hello_world ページが出来上がります。

plugin/middlewareでの拡張が容易

ログインが必要な画面へのフィルタなどmiddlewareを利用して簡単に作ることが出来ます。

各コンポーネントのライフサイクルフックやmixinなどを作ることなく差し込みたい処理を記述することができます。

学習コストが低い

一例ですが、*.vue拡張子のシングルファイルコンポーネントでは、 template 部分はほぼ生のHTMLが記述出来ます。

そのため、デザイナーにちょっとしたタグ構造の修正をお願いしたり、クラス、cssを調整してもらうときに特に説明等は必要ありませんでした。ファイルの場所だけ共有し修正してもらうことも多々ありました。

当然、エンジニア側がしっかり使いこなす分にはちゃんとした理解が必要ですが、デザイナーの方に少し調整してもらう等のVue.jsの簡単さは大きな利点だったと思います。

インタラクティブなUIが簡単に作れる

例えば、http://localhost:3000/histories で一覧表示し、http://localhost:3000/histories/1 でダイアログ出して詳細表示するなどの画面が簡単に作れます。

(pages/histories.vue)

<template>
  <div>
    <nuxt-child />
    <ul>
      <li v-for="h in histories" :key="h.id">
        <nuxt-link :to="`/histories/${h.id}`">{{h.name}}</nuxt-link>
      </li>
    </ul>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'

const HISTORIES = [
  { id: 1, name: "test1" },
  { id: 2, name: "test2" },
  { id: 3, name: "test3" },
  { id: 4, name: "test4" },
  { id: 5, name: "test5" },
];

interface State {
  histories: { id: number, name: string }[]
}

export default Vue.extend({
  data(): State {
    return {
      histories: [...HISTORIES]
    }
  }
})
</script>

(pages/histories/_id.vue)

<template>
  <!-- ここをモーダルにすればルーティングとモーダルの表示をリンクできる -->
  <span>{{msg}}</span>
</template>

<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
  computed: {
    msg(): string {
      return `${this.$route.params.id}のHistoryです。`;
    }
  }
})
</script>

これも先述したように、ディレクトリの中に上記のようにファイルを配置することで簡単に実現可能です。

大変だったところ

逆に大変だったところをあげてみたいと思います。

  • Vuexに型を付けるのが面倒くさい
    • Nuxt.jsでVuexに型をつけるには、 nuxt-typed-vuex を利用する必要があります。
    • 主観もはいりますが、ライブラリから提供される関数を使う必要があり、あまり直感的ではないかなと思いました。
  • SSR特有な部分での引っ掛かり
    • 当たり前ですが、DOMがレンダリングされているとき前提の処理やライブラリなどは工夫しないと使えません。開発段階では結構ハマるところが多かったです。

(Vuexのサンプル)

// Vanilla

export const getters = {
  // Type-checked
  email: (state: RootState) => (state.emails.length ? state.emails[0] : ''),
  // NOT type-checked
  aDependentGetter: (_state: RootState, getters: any) => getters.email,
}

// Helper function

import { getterTree } from 'typed-vuex'

export const getters = getterTree(state, {
  // Type-checked
  email: state => (state.emails.length ? state.emails[0] : ''),
  // NOT type-checked
  aDependentGetter: (_state, getters) => getters.email,
})

まとめ

今回は、新規プロダクトでNuxt.jsを選んだときの考え方について書いてみました。 プロダクトや会社の方向性によって様々な意見があると思いますが、皆様の参考になれば幸いです。

P.S.

3/19(金)19:30~ 【オンライン】3/19 とらのあなエンジニア&マーケター採用説明会【地方勤務可能!!】 yumenosora.connpass.com

採用情報
募集職種
yumenosora.co.jp

カジュアル面談も随時開催中です
お申し込みはこちら!
news.toranoana.jp

ToraLab.fmスタートしました!
メンバーによるPodcastを配信中!是非スキマ時間に聞いて頂けると嬉しいです。

anchor.fm

Twitterもフォローしてくださいね!
ツイッターでも随時情報発信をしています
twitter.com