SPA開発未経験者によるNuxt.jsを使った自社サービス開発

嘉数侑起氏(以下、嘉数):みなさん、こんばんは。「SPA開発未経験者によるNuxt.jsを使った自社サービス開発」というタイトルで、プレゼンさせていただきます。株式会社Re:Buildの嘉数と申します。よろしくお願いします。

こちらがアジェンダです。

今回、Nuxt.jsをこれから導入しようと検討している方、またはNuxt.jsを導入したばかりの方向けに、弊社が自社サービスでNuxt.jsを導入したお話と、Nuxt.jsを使って実際に感じたメリットやデメリット、あとはNuxt.jsとなにかしらを組み合わせて作った機能について紹介していきたいと思います。

自己紹介をします。

嘉数侑起と申します。1990年に沖縄で生まれて、沖縄で育ちました。28年間、沖縄にずっと籠もって生きてきました。大学と大学院も沖縄で、琉球大学を卒業し、2016年に沖縄電力の子会社で、電力システムのITインフラの構築、あとは保守・運用、セキュリティとかにも従事してました。去年、2018年の5月に株式会社Re:Buildに入社して、Webエンジニアとして活動しています。

なので、Webエンジニアとしてはまだ1年経ってないぐらいです。それまでは基本的にバックエンドでPHPとか、フレームワークのLaravelとかを使っていて、フロントエンドに関してはJS、jQuery、Vue.jsを少し触った程度です。フロントエンドに関しては、まだ素人レベルだと個人的に思っています。そのレベル感を踏まえた上で、今回の発表を聞いていただければと思います。

今回、沖縄から東京に来た目的ですが、「スマブラSP」をやっている方はちょっと手を挙げてもらっていいですか?

(会場挙手)

いや、絶対もっといますよね。もしよければフレンドになりたいと思っています。

あと、「スマブラSP」をやっているかにかかわらず、TwitterやFacebookでつながりを持って、交流ができたらいいなと思っています。

では、会社の紹介をします。

株式会社Re:Buildという名前で、沖縄を拠点に活動しています。基本的には受託開発で、去年の12月から自社サービスの企画や開発をしています。社員は社長を含めて5名で、全員エンジニアです。社長の考え方が「エンジニアの働きやすい環境を作っていきたい」というものなので、リモートワークが推奨されていて、基本リモートワークで働いています。

Nuxt.jsを導入した自社サービス

では、本題にいきます。「Nuxt.jsを導入した自社サービス」について話していきたいと思います。

弊社は自社サービスとして、クラウドソーシングに近いものを作っています。その技術要件として、サーバサイドはPHPでLaravelフレームワーク、フロントエンドでJavaScriptやVue.js、Nuxt.jsだったりを使っています。

個人的にはインフラの構成がおもしろくて、AWSを使っているんですが、デプロイ時にDockerのコンテナでデプロイする方法を使っています。その点に関しては別のスライドで紹介したいと思います。

開発体制に関してですが、SPAのWebアプリ経験者が1人と、それ以外はサーバサイドが得意な人が多くて、Vue.jsは触ったことがある程度のメンバーです。なので、SPA経験者1人といっても、その人はプロダクトオーナーという感じで、開発メンバーとしては動いていないので、実質SPA開発経験者なしのメンバーでSPAを作っています。

こちらがサーバ構成になります。AWS上でDockerコンテナが動いてまして、その中でフロントエンドとサーバサイドがあって、フロントからサーバサイドに対しては、Ajaxを使った非同期通信でのデータの取得や更新を行っています。

本当にざっくりですが、これが先ほど話したDockerでデプロイする流れです。

ローカルPCでプログラムを書いて、それをGitlabのほうにプッシュして、そのあとにGitlab CIが動くようになっています。このGitlab CIの中でテストが実行されて、テストが通ったら、このDockerコンテナがビルドするようになっています。このDockerコンテナをビルドするタイミングで、その中でNuxt.jsもビルドされるようになっています。

ビルド後は、コンテナをAWSのECRというサービスにプッシュして、ECSのコンテナが起動して、ユーザーがサイトを閲覧できるようになるという流れになっています。

Nuxt.jsを採用した理由

今回弊社がNuxt.jsを選定した理由ですが、一応SPAという考え方はけっこう前からあったんですけど、最近SPA化するサービスが増えてきたように見えるので、「流行りに乗って僕らもSPA化してみよう」という感じです。

開発メンバーにVue.jsでの開発経験があったということもありましたが、開発経験はあるものの、SPAに関するノウハウがなくて、Vue.jsだけでSPAのサイトを作るというのはちょっと時間がかかるし、難しいかなと思って、今回いろいろ調べた結果、規約に沿うことで機能を実現できるNuxt.jsが最適だと考えて導入しました。

具体的にどういう規約があるのかというと、ディレクトリとかファイル構成からルーティングを自動生成してくれたりする機能があります。

例えば、「pages」というフォルダの下にindexとか、user……その中で「users」フォルダを切って、その中にいくつかファイルを置いてビルドをかけると、ルーティングが自動で生成されるようになっています。

あとはミドルウェアという考え方で、画面がレンダリングされる前に処理を挟めるという機能があります。

例えば、「middleware」というフォルダの中に、認証しているかどうかをチェックする「auth.js」というファイルを置いておいて、それをVueファイルの中で「middleware: [‘auth’]」としておくことで、ユーザーはこの画面にアクセスしたときに、この中でビルドが動いて、認証されてなかったらリダイレクトするみたいな処理を挟むことができます。

あとは、自前のライブラリや外部モジュールを合わせて使用できるプラグインもあります。

モジュールに対して独自に処理を加えたいときに、「plugins」フォルダにファイルを置いて、それを設定ファイルの中に入れ込んであげるだけで、Nuxt.jsのアプリ全体にこの設定が適用されます。

ここまで規約とかについて説明したんですけど、最後のプレゼンターの方がけっこう詳しく説明してくれると思うので、そこらへんはよろしくお願いします。

あと、「ここにこう置けばこういうことが実現できるよね」ということで、初心者にもすごくわかりやすいなと思って、Nuxt.jsを導入しました。

Nuxt.jsを使って感じたメリット

次に、Nuxt.jsを使って実際に感じたメリットやデメリットについて話します。

まずメリットからお話しします。

この規約のおかげで悩む時間が短縮できました。先ほど話したルーティングの自動生成だったり、あとミドルウェアとプラグインです。どこになにを置いて、どう書くかは決まっているので、あまり悩む必要がありません。

僕自身はクラス名やメソッド名、あとファイルの置き場所とかで、毎回20〜30分悩んでいるので、すごく無駄な時間を過ごしていたなと思ったんですけど、Nuxt.jsの規約に従って書くだけで、わりといい機能が実現できるので、だいぶ時間短縮になっています。

あと、もう1つのメリットとして、チームでの開発の効率化ができているなと思います。

「どこに書けばいいかわからないし、なんか便利そうなコードはUtilクラスにまとめておくとよさそうだよね」という初心者にありがちなパターンがあるんですが、あとからメンテが不可能になるぐらい膨らんじゃう可能性があって、そういうオレオレアーキテクチャ的な部分が排除されたかなと思います。

全体的にファイル構成やコードの書き方も統一されるので、チームメンバーの技術レベルが低くても、ある程度はコードの品質が保てるかなという感じです。

日本語の公式ドキュメントとか、書籍もいいです。Nuxt.jsを触ったことがない人は、いったん公式ページを見て、チュートリアルを通して、微妙にわからない部分に関しては『Nuxt.jsビギナーズガイド』を見るという流れがいいと思います。

Nuxt.jsビギナーズガイド

そもそもVue.jsがわからないという人は、『基礎から学ぶ Vue.js』という本がだいぶやさしく、丁寧に解説しているので、ぜひご覧ください。

基礎から学ぶ Vue.js

ブラックボックスがもたらすもの

次に、Nuxt.jsを使ってみて感じたデメリットをお話します。実際にNuxt.jsを触ってから、まだ2ヶ月ちょいしか経っていないので、デメリットを感じる場面にあまり遭遇していません。なので、気になる点について少しお話ししていきたいと思います。

1つ目が、Nuxt.jsはブラックボックスだなという話です。Nuxt.jsをビルドするといい感じにアプリが動くんですけど、そのビルドでなにをやっているのかがわかりません。ビルドをするときになにかしら問題が発生した場合に、その原因の特定が難しそうだなというのは感じました。

2つ目が、これはNuxt.jsに限った話じゃないんですが、ファイルサイズが肥大化していきます。

ビルドに時間がかかって、開発者としてはすごくストレスがかかっちゃいます。ビルドしたあとのファイルサイズが肥大化しちゃって、ユーザーが最初にページに訪問したときに、ロードする時間が長くなっちゃうとユーザビリティが悪いので、悩ましいところです。

今の機能が少ないうちは別になんともないんですけど、機能を追加していくにつれてどんどんファイルが大きくなっていくと思うので、そのときにまた考えないといけないかなという感じです。

でも昨日調べたら、「Nuxt.jsのビルド時間を100倍高速化した」みたいなタイトルの記事があって、「本当かよ?」と思ったんですけど、あとで検証してみたいと思います。

これもNuxt.jsに限った話じゃないんですけど、インフラ絡みのエラーが発生すると非常に面倒くさいです。例えばCORSで、異なるドメインや異なるプロトコルに対してリクエストを送信すると、怒られてしまいます。APIを実行できないというエラーが発生します。

この時はバックエンドとかインフラ側の設定をして、問題を解消したんですが、あとあと調べてみたら「@nuxtjs/proxy」というNuxt.jsのモジュールを使えば、CORSは回避できるらしいです。ちゃんとドキュメントを読んでおけばよかったなという反省をしています。

Nuxt.jsを使ってみて、規約から受ける恩恵は大きいです。ただ、ブラックボックスを触る必要が出てくるとキツイかなという感じです。あんまり触る機会ってないと思うんですけど、もしそうなった場合は、ただひたすら掘り下げていくしかないのかなと思っています。

自社サービスにおけるNuxt.js

次に、「自社サービスでNuxt.jsとなにを組み合わせて機能を実装しているか?」について、少し紹介したいと思います。

JWTを使ったユーザー認証と、APIの実行についてです。

JWT(JSON Web Token)と書いて「ジョット」と読むんですけど、これは電子署名付きのURL-safeなJSONを使った認証方式で、自社サービスの上では、このJWTを使ってユーザー認証を行っています。

JWTを使うメリットは、今回だとデバッグと管理が簡単だったり、あとはビルトイン式の有効期限を設定する機能がついていることと、個人的にいいなと思ったのは、Laravel側にJWTを組み込むのが楽だったので、そこが一番のメリットだと感じています。

大まかな認証のフローですが、クライアントからログインIDやパスワードという認証情報がサーバに対して送信されます。サーバはその認証情報からユーザー情報と有効期限を含むJSONを暗号化して、クライアントに対してJWTとして返却します。

それ以降、クライアントがなにかしらAPIを叩くときは、この受け取ったTokenを認証リクエストとして扱って、このサーバのリソースにアクセスするという流れになっています。

これはNuxt.jsと組み合わせた場合の承認フローです。Nuxt.js側から、Nuxt.jsのログイン画面からユーザーIDとパスワードを入力して、ログインボタンを押すと、Nuxt.js側のアクションとして、サーバサイドにこの認証情報が送信されます。

サーバサイドでそれを検証し、適切な場合はTokenを生成して、またフロント側に返します。フロント側は、そのTokenを受け取って、Vuex側のStateやローカルのストレージ、それかLocalStorageに保存しておきます。

これ以降、ユーザーがNuxt.jsの画面からなにかしらの機能を使う場合は、このTokenをミドルウェアで検証して、APIを叩くときとかも、このプラグインの中でAuthorizationヘッダーにTokenをセットしてあげて、リクエストを投げるという仕組みを作っています。

これはmiddlewareでのログインチェックの例です。ユーザーが認証されていない状態で、なにかしらの画面に入ってきたときに、このmiddlewareの中でユーザーがTokenを保持していることをチェックして、Tokenを保持していなければログイン画面にリダイレクトをします。保持していた場合は処理を継続します。

次にプラグインでのToken設定です。

こちらはpluginフォルダの中にaxiosでJS的なものを作って、その中でHTTPリクエストを送信するときにAuthorizationヘッダーにTokenを設定する処理だったり、Tokenの有効期限が切れているときにエラーが起きた場合、適切なページにリダイレクトする処理を書いています。これを書いてNuxt.jsの設定ファイル読み込むことで処理が実現できます。

これは自社サービスの画面です。ユーザーIDとパスワードを入力してログインボタンを押すと、管理画面に入ります。このときにLocalStorageを見ると、サーバ側から受け取ったJWT Tokenが保存されているのがわかります。

この状態で管理画面からユーザー一覧する機能をちょっと見てみます。

ユーザー一覧したあとにそのリクエストのヘッダー部分の内容を見ると、このAuthorization Headerに先ほど取得してきたヘッダーがセットされているのがわかります。

なので、Laravelに限ったことじゃないと思うんですけど、比較的簡単に導入できるし、管理が楽なので、認証はわりと使いやすいなと感じました。

Firebaseを使ったチャット機能

あと、これとは別で、Nuxt.jsとFirebaseを使ったチャット機能を実装しています。

自社サービスの中で、「チャットの機能をWebSocket、またはロングポーリング、またはFirebaseのどれを使うか?」という議論になったんです。

その時の判断基準として、「まぁ一応スタートアップ企業だし、スピード重視したいよね」ということで、実装難易度が高くないものを選びました。あとはメンテナンスコストが低いものです。さらにメッセージの ログ を解析する可能性もあるので、それを踏まえる必要があります。「採用のウリになるような技術だといいよね」という基準も含めて、結果としてFirebaseを採用することになりました。

選定理由としては、チャットのサンプルを作ってみたんですけど、これが半日ぐらいで実装できたんです。弊社は別の案件でロングポーリングのチャット機能を自前で用意したことがあるんですが、これが1人月ほどかかっての実装でした。それにもかかわらず、バグが多くて、メンテンナスコストがすごく上がったので、今回はメンテナンスコストを下げたいということで、「Firebaseいいな」と思い、採用しました。

あと、Firebaseを使っていく上での懸念点なんですけど、Firebase上のDatabaseの無料枠が1GBしか使えないので、この1GBがけっこう圧迫されてキツイなという印象です。

あとは、NoSQLなので、コレクション間で外部キーを持たせられません。なにかしら関連するデータを引っ張ってくる際は、ローカル側でがんばって取得してくるしかないです。

データを検索する際に、検索の条件に制約的なのがあって、うまく検索できない場合もあるという懸念点もあります。

こちらがFirebaseを使った機能の全体の構成です。まだ実装していないので想定になります。

フロントエンド側のNuxt.jsからサーバサイドに対してログイン処理を行って、先ほどのJWTのTokenを取ってきます。それ以降はNuxt.js側とFirebase側でリアルタイムのチャットのやりとりができるようになっています。

先ほど1GBを超えた場合に料金が発生していくと申したんですけど、「1GBを超えないように工夫を施したい」ということで、定期的にバッチを走らせて、Firebaseに保存されたデータをこのLaravel側のDBに保存するという処理を置こうかなと思っています。

テーブル的には、コレクションは基本的に1つにまとめて、内部でJSON形式のリレーションを持たせるようにしています。あまりテーブルを分散させすぎると、外部キーという概念がないので、組み合わせてデータを取ってくるのが面倒くさいです。一番手っ取り早いのは、1つのテーブルに全部のデータをまとめる手法だと聞いたので、そういうふうに実装してます。

SPAの開発経験がなくても

まとめに入ります。

SPAの開発経験がなくてもVue.jsを触ったことがあるなら、Nuxt.jsを使うことを検討してもいいのかなと感じています。今回で言うと、SSRを使ってなくてSPAモードで動かしているので、SPAモードだけであれば、Vue.js経験のある人は、Nuxt.jsいいんじゃないかなと思います。

下のほうに「Firebaseにはいろんな可能性がありそう!」と書いてあるんですけど、最近のインフラ系はクラウド化されてきて、どんどんバックエンドの処理を書かなくても、いろんな機能が実装できるようになってきてます。そういった中でFirebaseはNuxt.jsを使った開発とかで、今後旬になってくるかなと感じています。

このあと懇親会もあると思うので、僕とお話をしたい人がいたら絡んでください。よろしくお願いします。以上です。ありがとうございました。

(会場拍手)