Nuxt.jsとFirebaseで爆速でPWA対応Webアプリを開発した話

[株式会社diffeasy](https://diffeasy.com)の日本一魅力的なプログラマー集団を作るCTO[@_takeshi_24](https://twitter.com/_takeshi_24)です。

去る12/8(土)に福岡で開催された[Frontend Conference Fukuoka 2018](https://frontend-conf.fukuoka.jp/)素晴らしいカンファレンスでした！運営の皆様お疲れ様でした。

その中で登壇させていただいた「[Nuxt.jsとFirebaseで爆速でPWA対応Webアプリを開発した話](https://speakerdeck.com/takec24/nuxt-dot-jstofirebasetebao-su-tepwadui-ying-webahuriwokai-fa-sitahua)」について、少し詳しく記事に書かせていただきます！

## 今回開発したアプリについて
今回開発したアプリは「友達申請のないSNS WACHA!(仮)」です。
このアプリは私が4年前フリーランス時代に出場した「スタートアップウィークエンド福岡2014」で優勝したアイデアのアプリです。
<img width="420" alt="スタートアップウィークエンド福岡" src="https://qiita-image-store.s3.amazonaws.com/0/3003/d389af67-8a87-2092-2a46-9c5970dac000.jpeg">

Facebookやtwitterなど従来のSNSは、まず最初に友達申請やフォローして、誰かとつながる必要があります。
様々な人と繋がることで、本当に言いたいことを言えなくなっている人も多いと思います。
例えば、
「Nuxt.js好きだ！」と投稿しようと思ったところで・・・
「Angular、React勢の人が見たらどう思うかな。。」など余計なことを考えて投稿削除・・などよくある話です。

本当に話したい事について話せる場が欲しい、という思いでこのアプリを考えました。

そもそも通常の社会において最初に「友達申請」などやるわけなくて、最初は名前も顔もわからない人と出会って、話していくうちに共通の趣味などが見つかり、相手のことを徐々に知っていき、友達になるという流れが普通です。

そこでそれをインターネット上で実現させるためのアプリが「WACHA!(仮)」です。

主な機能としては

- ユーザー・プロフィール登録
- ログイン・ログアウト
- プロフィール画像アップロード
- リアルタイムチャット

です。

![wacha.gif](https://qiita-image-store.s3.amazonaws.com/0/3003/04fd25ac-a5bd-7ffb-48dc-fe9af359c330.gif)
本当はこの記事公開のタイミングでα版リリースしたかったのですが、少し遅れていているので、少々お待ちください。。
気になる方はtwitter[@_takeshi_24](https://twitter.com/_takeshi_24)をフォローいただければ、そちらでリリースお知らせします！

今後ネイティブアプリの開発も予定しています。

## Nuxt.jsとFirebaseによるPWA開発
Nuxt.jsとFirebaseによるWebアプリ開発については、Qiita内にも多数ありますね。

「[Nuxt.jsビギナーズガイド](https://www.amazon.co.jp/Nuxt-js%E3%83%93%E3%82%AE%E3%83%8A%E3%83%BC%E3%82%BA%E3%82%AC%E3%82%A4%E3%83%89-%E8%8A%B1%E8%B0%B7%E6%8B%93%E7%A3%A8-ebook/dp/B07J5434JB)」著者花谷 拓磨氏の記事

- [【v2対応しました】Nuxt.jsとFirebaseを組み合わせて爆速でWebアプリケーションを構築する](https://qiita.com/potato4d/items/cfddeb8732fec63cb29c)

をはじめ、[Nuxt.js Advent Calendar 2018](https://qiita.com/advent-calendar/2018/nuxt-js)にも

- [Nuxt, netlify, Firebase を使ったWebサービス開発と個人開発における技術選定のポイント](https://qiita.com/howdy39/items/ac4c19dd36cd182841bc)
- [結婚パーティーのため Nuxt と Firebase でサーバーレスなご祝儀を納品した話](https://yamitzky.hatenablog.com/entry/2018/12/10/232946)

など投稿され、人気の高さがうかがえます。

Nuxt.jsとFirebaseによるPWA開発の詳細な手順についての記事は
「[Nuxt v2とFirebase(CloudFirestore)でPWA対応Webアプリ開発](https://qiita.com/_takeshi_24/items/3ee051e1db1b3e8da106)」に記載していますので、そちらをご覧ください。

ここの記事では、実際に開発をやってみて困った点や、工夫した点を述べていければと思います。

## Nuxt.js(Vue.js)実際どうなの？
Nuxt.js(Vue.js)については、現時点で最高！としか言いようがないです。
弊社[diffeasy](https://diffeasy.com)では多数の受託案件、自社サービスでNuxt.js(Vue.js)を採用しています。

### なぜNuxt.jsを採用したのか？
弊社[diffeasy](https://diffeasy.com)は創業4期目(2018年12月現在)です。
Web系以外のレガシーな言語や開発環境出身のエンジニアが多く、フロントエンドの技術選定にはかなり悩みました。

創業当初はjQueryを使ったフロントエンド開発を行なっていましたが、jQueryを使って行く中でいくつか課題が出て来ました。

- コードの書き方が人によって様々になってしまう
- アプリ化など見据えてサーバーサイドをAPI化してフロントと分離したい

が主な課題です。

そこで、モダンなフロントエンド開発のための新しいフロントエンドフレームワークの選定に取りかかりました。
Nuxt.js(Vue.js)の他にもAngular、Reactも当然候補に上がりました。
しかし、以下の点を考慮してNuxt.js(Vue.js)以外のライブラリの採用は見送りました。

- Angular
一部案件でAngularJSを使っていて、それの後継ということでAngularは第一候補で考えていました。
しかし、リリースサイクルが短い点が導入における最大のネックでした。
フロントエンドのライブラリを選定している時点ですでに次のバージョンのリリースが発表されており、採用を見送りました。

- React
Web経験の浅いメンバーが多い中で、学習コストが高いと判断しました。
社内リソースの問題もあり、フロントのマークアップを外部のデザイナやマークアップエンジニアに出すケースが多かったのですが、JSXを利用することからプログラムとマークアップの分離が難ししい点も採用見送りのポイントになりました。

- Nuxt.js(Vue.js)
検討時点ではまだ実践採用されている事例が少ないという不安はありました。
ドキュメントが充実している点、jQueryなど既存の資産も利用できる点、そして学習コストが低い点を考慮し、採用を決めました。

今では入社半年〜1年ぐらいのフロントエンド開発未経験のエンジニアでもNuxt.jsを使って、モダンなフロントエンド開発をバリバリとやっています。
Nuxt.js、Vue.jsに感謝です！

ただ誤解が無いように言っておくと、Nuxt.js以外がダメだ！というわけではなく、この時点ではマッチしなかったというだけです。
今後フロントエンドエンジニアの層が厚くなってくれば将来的にまたReactやAngularを採用する可能性は大いにあります。

## Firebase実際どうなの？
今回個人開発のアプリで、業務時間外での作業がメインになるため、サーバーサイド開発に時間をそれほど投資できないということで、Firebaseを利用しました。
Firebaseを実際に使ってみての感想や、工夫した点について記載します。

### 基本的には最高！
サーバーサイド開発が基本的に不要なので、サービスの開発スピードが圧倒的に速いです。
<img width="420" alt="Firebase" src="https://qiita-image-store.s3.amazonaws.com/0/3003/97a0ed66-a531-3fb5-fba7-1791f4858bb9.png">

- サーバーのインフラ設計構築
- ミドルウェアのインストール/設定
- サーバーサイド開発
- サーバー運用/保守
- スケーラビリティの考慮/設計

などなど、通常バックエンドを提供するために必要なことがFirebaseを利用することで自動で提供されます。

複雑なデータを必要とするサービスでは辛い部分もあると思いますが、チャットやTODOアプリなど比較的データが単純なアプリケーションであれば十分な機能が揃っています。

また全てをFirebaseで実現しなくても、アプリケーションのユーザー認証部分をFirebaseのAuthenticationを利用する、チャット機能のみリアルタイムデータベースを利用するなどの選択肢もありだと思います。

### クエリが貧弱
リアルタイムデータベースのサービスとしてその名も「Realtime Database」というサービスと「Cloud Firestore(β)」という２つのサービスが用意されています。

β版ではありますが「Cloud Firestore」の方が、「Realtime Database」に比べてクエリやソート機能が柔軟です。
とは言え、まだまだ色々制約があり、辛い部分があります。

#### OR検索できない
OR検索ができません。
OR検索と同じことをしたければ、OR条件それぞれのクエリを実行し、その結果をアプリ側のロジックでマージして上げる必要があります。
リアルタイムで投稿時間でソートした結果を表示したい場合などを考えると、辛いです。

#### LIKE検索が弱い
例えばユーザー名"西"から始まるユーザーを検索したい場合、ソースは以下のようになります。

```js
ref.collection('user')
  .orderBy('name')
  .startAt('西')
  .endAt('西'+'\uf8ff')
```

startAtとendAtを利用して、orderByで並び替えた項目に対して、範囲指定して一覧検索する必要があります。
名前でLIKE検索したい場合は、名前で第一ソートする必要があるので、例えば、"西"から始まるユーザーを登録日順に表示したい場合でも名前でのソートが優先されます。

前方一致は上記の通りですが、部分一致、後方一致検索はできません。
名前に"西"が含まれているユーザー一覧の取得などはできません。

### データ設計重要
RDBの考え方に慣れている方は、リアルタイムデータベースのNoSQLのJSONによるデータ設計に戸惑うと思います。
リレーションができないので、重複したデータを持つなど、非正規化したデータ設計が必要になります。

例えば、今回のアプリのケースでも繋がったユーザー一覧を表示するためのデータと、ユーザー詳細のデータを別々に重複して持っています。
通常のRDBであれば、例えば以下のようなテーブルがあれば、ユーザー一覧にユーザー名を表示したいときは２つのテーブルをjoinすることで対応できます。

- users(ユーザーのデータテーブル)

|ID|ユーザー名|性別|
|:--|:-----------------|:-----------------|
|1|にし|男|
|2|たけし|男|
|3|ニッシー|女|

- pairs(ペアのデータテーブル)
 
|ID|ペアとなるユーザー1のID|ペアとなるユーザー2のID|
|:--|:-----------------|:-----------------|
|1|1|2|
|2|2|3|
|3|1|3|

しかし、リアルタイムデータベースでは、joinができないので、今回私は以下のようなデータの作りにしました。

```json:users
users: {
  1: {ユーザー名: 'にし', 性別: '男'},
  2: {ユーザー名: 'たけし', 性別: '男'},
  3: {ユーザー名: 'ニッシー', 性別: '女'}
}
```

```json:pairs
pairs: {
  1: {ペアとなるユーザー1のID: 1, ペアとなるユーザー1のユーザー名:'にし', ペアとなるユーザー2のID: 2, ペアとなるユーザー2のユーザー名:'たけし', },
  2: {ペアとなるユーザー1のID: 2, ペアとなるユーザー1のユーザー名:'たけし', ペアとなるユーザー2のID: 1, ペアとなるユーザー2のユーザー名:'にし', },
  3: {ペアとなるユーザー1のID: 2, ペアとなるユーザー1のユーザー名:'たけし', ペアとなるユーザー2のID: 3, ペアとなるユーザー2のユーザー名:'ニッシー', },
  4: {ペアとなるユーザー1のID: 3, ペアとなるユーザー1のユーザー名:'ニッシー', ペアとなるユーザー2のID: 2, ペアとなるユーザー2のユーザー名:'たけし', },
  5: {ペアとなるユーザー1のID: 1, ペアとなるユーザー1のユーザー名:'にし', ペアとなるユーザー2のID: 3, ペアとなるユーザー2のユーザー名:'ニッシー', },
  6: {ペアとなるユーザー1のID: 3, ペアとなるユーザー1のユーザー名:'ニッシー', ペアとなるユーザー2のID: 1, ペアとなるユーザー2のユーザー名:'にし', }
}
```

pairsのデータにユーザー名も持って、joinなしに一覧表示しています。
ここでもう一点、pairsのデータ側で、重複したデータがあるのにお気付きだと思います。
これは先ほど言ったOR検索ができないためにこのような作りにしています。

RDBであれば、ユーザーID:1のペア一覧を検索したい場合、```ペアとなるユーザー1のID=1 or ペアとなるユーザー2のID=1```で検索できますが、リアルタイムデータベースではOR検索ができないので、ユーザーID=1のユーザー用のペア一覧と、ユーザーID=2のユーザー用のペア一覧のデータをそれぞれ重複して持つようにしました。

#### データのマイグレーション大変
上記のような考慮が必要なため、Firebase公式サイトにも書いていますが、適切な構造のデータベースを構築するには、事前の綿密な計画が必要です。
例えば、Railsの場合、データベース設計を見直す場合、db migrateでデータベースのマイグレーションが可能です。
しかし、リアルタイムデータベースの場合マイグレーションツールなどがないので、手動でマイグレーションが必要で、これはなかなか大変な作業になります。

### オフラインで開発できない
Firebaseを使った開発をする場合、Firebaseとの接続が必須になるので、オフラインでの開発ができません。

## PWA実際どうなの？
アプリを開発してもそれがユーザーに受け入れられるかどうか、わかりません。
やはり新規サービスは早い段階で市場での検証が必要です。
（この辺の考えについては、[プロジェクトマネジメントとプロダクトマネジメントにおけるQCD](https://diffeasy.com/posts/study/3209)というタイトルで社内ブログに書きましたので、こちらも是非ご覧ください。）

利用されるかどうかわからないアプリに対して、個人でネイティブアプリを開発するまでの工数は掛けられません。
そこで今回はPWAを使ったクロスプラットフォームアプリの開発を進めました。

### 基本的には最高！
Webアプリをホーム画面に追加できて、ホーム画面から起動するとアドレスバーなどが消えてネイティブアプリと同じような状態で利用できます。
![PWAをインストール](https://qiita-image-store.s3.amazonaws.com/0/3003/2ed069ed-378e-14e4-d476-4571136cddf4.png)

バックエンドで動作するService WorkerがWebから最新のコンテンツをキャッシュに保存してくれるので、オフライン時でもネイティブアプリと同じように画面操作が可能です。
オフラインなので、サーバーとの通信はできませんが、コンテンツ投稿などの処理もサーバーとの通信再開時にServiceWorkerが行ってくれます。
<img width="324" alt="スクリーンショット 2018-12-12 15.04.37.png" src="https://qiita-image-store.s3.amazonaws.com/0/3003/5f90401c-72cb-b31a-f184-8ec32a9437c2.png">

また、Service Workerがプロキシのような役割を果たすので、キャッシュからコンテンツを高速に表示することができます。
[PWAで表示速度が2倍に！ スピード改善を妥協しない日経電子版に学ぶ、PWAのメリット＆デメリット](https://employment.en-japan.com/engineerhub/entry/2018/06/05/110000)

従来のWebアプリではユーザーへの通知ができませんでした。
メールで通知を送るしかなかったのですが、メールの場合開封率が低いのが問題でした。
PWAではメッセージ受信のお知らせなどプッシュ通知が可能です。
![PWAのプッシュ通知](https://qiita-image-store.s3.amazonaws.com/0/3003/e246a9fc-0835-76ba-235b-4e1580e412a4.png)

### PWA対応も簡単
今回Nuxt.jsでアプリを開発したので、PWA対応も@nuxt/pwaモジュールをインストールすることで簡単に対応できました。
（詳細は「[Nuxt v2とFirebase(CloudFirestore)でPWA対応Webアプリ開発](https://qiita.com/_takeshi_24/items/3ee051e1db1b3e8da106)」）

### ネイティブアプリに比べるとまだまだ
#### iOSの状況
ここまで、PWA最高！という話をして来ましたが、iOSはまだまだ対応が遅れています。
iOS版SafariのPWA対応iOS11.3/Safari11.1から強化されました。
https://medium.com/@firt/progressive-web-apps-on-ios-are-here-d00430dee3a7

しかし、現時点(2018年12月時点)iOS版Safariではプッシュ通知未対応です。

#### その他問題点
##### アイコンのバッジ表示できない
前回フロントエンドカンファレンスのセッションの中でバッジ表示ができないという話をしたのですが、ちょうどその後Googleの[@agektmr](https://twitter.com/agektmr)さんのセッションの中でChromeBookでの[Badging API](https://github.com/WICG/badging/blob/master/explainer.md)の話が。
https://www.aboutchromebooks.com/news/android-and-pwa-notification-badges-chromebooks/
バッジ表示できるようになりそうですね！

##### ブラウザの設定でServiceWorkerを無効にできる
「Chromeを使うなら、必ずServiceWorkersを無効化しよう」という記事がQiitaにも投稿されていましたが、ServiceWorkerはオフにできるので、この設定をされると、当然PWAの恩恵を受けることはできません。

### 今後に期待
ネイティブアプリに比べると、特に通知や端末機能へのアクセスの面でまだまだではあり、今後どうなるかも不透明ではあると思います。
しかし、従来のネイティブアプリに比べて、Webブラウザからそのままホームに追加してインストールできる手軽さなどはネイティブアプリにはない魅力です。

TwitterのPWA版[Twitter Lite](https://mobile.twitter.com)などもリリースされていますが、ネイティブアプリと比べても、一部機能に制限はありますが、起動も速く、遜色ありません。

今後iOSのPWA対応がどのように進むか？が1つの大きなポイントだと思いますが、バッジ表示など機能も充実してくれば、PWAを採用するプロダクトも増えるのでは、と思います。

## 最後に
Nuxt.jsのアドベントカレンダーなのに、Firebaseについての内容多めの記事になってしまいました・・

やっぱりPWAではなく、ネイティブアプリやりたい！という方は、「[Vue.js Advent Calendar 2018](https://qiita.com/advent-calendar/2018/vuejs)」に「[Vue Nativeでネイティブアプリ開発入門](https://qiita.com/_takeshi_24/items/091dead6fbd79d163250)」の記事も本日投稿していますので、そちらも是非ご覧ください！

最後に一言。
これだけ手軽にフロントエンド開発できるNuxt.js最高で〜す！！