Nuxt + firebase Auth & Realtime Databaseで超基本Webアプリを作り、基本SEO対策までして公開する全容をざっくりと公開

はじめまして、ゆと(@utoc11)と申します。

普段はPdM（プロダクトマネージャー）が本業なのですが、自分でも創れるようになりたい！　そんな思いでプログラミングを学びはじめ、小さくWebアプリを作ったのでその超基本的事項をシェアできればと思います。

今回のVue、Nuxt、firebaseに関しては全くのど素人です。かつ、以下のように他のスキルも精通しているわけではないです。（だからこそ、初歩の初歩でつまずく方への参考になれば嬉しいです）

本記事は利用した技術の紹介から、多くの人に必要な知識/技術のところを中心に絞って解説していく流れになります。

1つ強調して伝えたいのは、Nuxt.js+firebaseでの開発はとても楽しいです。公式ドキュメントも、Qiitaやブログも充実しつつあります。Nuxt.jsとかfirebaseに興味を持ってるんだよねーと言うかたはぜひ触ってみることをお勧めします。

##利用した技術など
- Nuxt.js
- firebase auth
- firebase Realtime Database
- Vuetify
- pug

##完成したもの
URL：[ハッシュタグカウンター](https://hashtag-counter.net/)
![hashtag-counter.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284955/040ff772-6850-a45a-279a-5690017b2cdb.png)

##ソースコード
[Github](https://github.com/yutoc11/hashtag-counter)

##使った機能や実装した機能などざっくり全体像

1. pugを使いこなす
2. Vuetifyを使いこなす
3. Nuxt.jsのコンポーネント間での値受け渡し
4. firebase authによるSNSログイン
5. firebase authによるメールアドレスで新規登録/ログイン
6. Nuxt.jsのstore機能でログイン状態の保持
7. firebase Realtime Databaseへの保存
8. firebase Realtime Databaseからログインしているユーザーの投稿を読み取る
9. 「#~~ #×× #▲▲」のハッシュタグの数をリアルタイムに数えて表示する
10. 入力内容をコピーする
11. 入力内容をクリアする
12. 半角スペースを入力する
13. 退会
14. サイトマップ作成
15. @nuxtjs/google-tag-manager を利用したPV計測
16. 各種メタタグ、favicon、PWAなどの設定
17. 利用規約/プライバシーポリシーなど個人開発で漏れがちなところを作成
18. 独自ドメインの取得
19. firebase Hostingでホスティング

##共通で使えそうな部分を中心にご紹介
###1. Pugを使いこなす
こちらは以下の参考記事のおかげでものすごいスムーズに導入ができました。開発中も、その後はあまり参照することもなかったですね。強いて言うならば、コメントアウトくらいでした。

- [【pug導入編】今流行りの「Nuxt.js」を使ってサーバーサイドレンダリング(SSR)をしてみよう【Vue.js】](https://blogenist.jp/2019/06/13/8558/)
- [nuxt.js(v2)でpug/stylusを利用する](https://qiita.com/amishiro/items/38fe1b102d7e91a93ada)
- [Pug(Jade)の基本記法について](https://qiita.com/cotolier_risa/items/135d168eddd6ae6c3409)
- [Vue.js製のポートフォリオサイトをPugで書き換えました](https://becolomochi.hatenablog.com/entry/2017/11/05/234922)
- [Pugでコメントアウトをする](http://cly7796.net/wp/css/comment-out-by-pug/)

###2. Vuetifyを使いこなす
Vuetifyは、導入には苦戦してないですがひたすら参照はし続けましたね。　ただ最近知ったのですがVScodeにはプラグインでいい感じに予測を出してくれるものがあるらしいので、そちらを利用すると良いかもしれません。（ちなみに私はAtomです）

独自でCSSはほとんど書いておらず、マージンやレイアウト、ほぼ全てがVuetifyでサクッと書かれております。

- [Vuetify公式](https://vuetifyjs.com/ja/)
- [Vuetifyの使い方(UIフレームワークで手軽に構築)](https://www.wakuwakubank.com/posts/668-vuejs-vuetify/)

###3. Nuxt.jsのコンポーネント間での値受け渡し

Vue.js自体が初めてと言うこともあり、コンポーネント間の値受け渡しもここで初めて試しました。 親→子のイメージはすぐにできたのですが、子→親の感覚がなかなかしっくりこず、以下の記事を何度読み返したかわかりません。

- [Vue.jsでコンポーネント親子間の値の受け渡し](https://qiita.com/y_sasaki/items/5bbed5439fcfef9f8c40)

ただし私のコードでは結局動くことを優先して、子コンポーネントから親の変数を直接書き換えているので、正直あまりいいコードではありません・・・！（もっとスマートな書き方ができるようになりたいものです）

###4. firebase authによるSNSログイン／5. メールアドレスで新規登録/ログイン

firebaseのSNSログインは、本当に簡単です。以下の記事を参考にすれば、プログラミング初心者の鬼門でもあるログイン機能を、全てfirebaseに任せて実装することができます。

- [Nuxt.js + Firebase で google認証する](https://volpe.hatenablog.com/entry/2019/03/28/172057)
- [FirebaseとNuxt.jsを使ってユーザ認証関係を簡単に作ってみる+1ヶ月前の自分に教えたいリンク集](https://qiita.com/redshoga/items/da5c0e247e0df314a257)

ただし、考慮しなきゃいけなかったなあと後半に気づいて見送っているのが、パスワード変更やリセットなどの機能です。TwitterなどのSNS認証の場合にはパスワードが未設定で、メールアドレス認証の場合にはパスワードが設定済みと言う状況なので、場合分けしてコードを記述する必要があります。
私のハッシュタグカウンターではパスワード変更機能を実装した後にそのことに気づき、若干不自然な挙動になってしまっているのが反省です。

###6. Nuxt.jsのstore機能でログイン状態の保持

Vuexが実はロード（リロード）をするとデータが消えてしまうとのことで…
vuex-persistedstateというものを使い、消さないように、ユーザのlocalStorageを用いてよきに保存できるようにしておきました。

以下の記事を参考に、
1. npm install vuex-persistedstate --save　でインストール
2. persistedstate.js の作成、
3. nuxt.config.js内の設定変更
を行いました。

- [nuxt.jsを使う時にlocalStorageでstoreを永続化する - Qiita](https://qiita.com/sakapun/items/a0cf5698751ae70c8088)

本当にこの記事の通りにやればOKです。感謝。

###7. firebase Realtime Databaseへの保存

データベースはfirebaseのRealtime Databaseを利用してみました。以下の記事を参考に実装を進め、書き込みはなんなくできたのですが、どうしてもリアルタイム読み込みのコードがうまくいきませんでした。

- [［Nuxt］Firebase Realtime Databaseを使った基本的なCRUD](https://youj.work/post/firebase-3/)
- [Nuxt.jsとFirebase Realtime Databaseを使ってデータを保存する](https://www.non-standardworld.co.jp/20646/)

###8. firebase Realtime Databaseからログインしているユーザーの投稿を読み取る

そこで読み込むべきタイミングだけ更新するように、以下の記事を参考に調整してみることで問題を回避しました。

- [firebase realtime databaseのon()とonce()の違い](http://www.stockdog.work/entry/2019/02/03/030706)

firebase realtime databaseのon()（リアルタイム連携）

```javascript

const sampleRef = firebase.database().ref('sample')
sampleRef.on('value', function(snapshot){
	console.log('value', snapshot.val())
})
```

firebase realtime databaseのonce()（１度だけ読み込む）

```javascript
const sampleRef = firebase.database().ref('sample')
sampleRef.once('value', function(snapshot){
	console.log('value', snapshot.val())
})
```

上記参考記事でもあるように、リアルタイム同期って意外といらなかったりするんですよね。必要なタイミングを見極めて、once()を使うことがむしろ良い場合も多いということです。

私のハッシュタグカウンターも、once()の機能で十分だったので、結果オーライとなりました。


###9. 「#~~ #×× #▲▲」のハッシュタグの数をリアルタイムに数えて表示する

```Vue:vueファイル
<template lang="pug">
 v-textarea(
  id="hashtag_input"
  ref="r"
  v-model="content"
  @input="updateValue"
  auto-grow
  solo
 )
 div
  p {{ now_hashtag_count }}
</template>
<script>
export default {

  data () {
    return {
      now_hashtag_count:0,
      content:'',
      input_text:'',
    };
  },

  methods:{
    // 入力内容が1文字変わる度に、ハッシュタグの数を数える
    updateValue(){

      var input_text = this.content
      var hashtag_count = input_text.match(/#\S/g)
      if(hashtag_count){
        var now_hashtag_count = hashtag_count.length
        return this.now_hashtag_count = now_hashtag_count;
      }else{
        var now_hashtag_count = 0
        return this.now_hashtag_count = now_hashtag_count;
      },
    },
  },
</script>
<style>
//省略
</style>
```

textareaを`v-model='content'`として、ハッシュタグのカウントは`match(/#\S/g)`をしているだけのコードです。
厳密にはもう少し違う正規表現があり、
https://rubular.com/
ではうまく抽出で規定なのですが、JSでなんともうまく拾えず…
泣く泣く、`match(/#\S/g)`で抽出しています。

Instagramのハッシュタグは、「#」のみではハッシュタグにはならず、「#gragagag #garjgairga」のように
「#」の後になんらかの文字列が並ぶ必要があります。

「#が1文字あって、その後任意の文字が1文字以上続いて、空白または次の#がくるまでを1つのハッシュタグとする」
が正しいのですが、`match(/#\S/g)`では、
「#が1文字あって、任意の空白ではない文字が1文字以上続く」という抽出の仕方となっています。

この場合にはマッチしているのは「#テスト1 #テスト2#テスト3 #」
のような場合、
\#テ、#テ、#テの3つということになります。

使ったハッシュタグを保存していくまでを想定していく場合にはこれではNGですが、有効なハッシュタグの個数を数えるという今回の用途ではこれでOKだったということです。

###10. 入力内容をコピーする

`nuxt-clipboard2`というライブラリをインストールすると、以下のように書くだけでテキストをコピーすることができます。

```javascript

    // 入力内容をコピーする
    copyHashtag(content) {
      this.$copyText(content)
    }
```

###11. 入力内容をクリアする

`this.content = ""`
をするだけです。簡単ですね。


###12. #と半角スペースをワンタップで入力する

Instagramの####の流れでよく使うのがこの、「#」と半角スペースです。これをPCなら簡単ですが、スマホだと慣れていないと手間に感じる人も多いですよね。

なので、ボタンをワンタップすると、カーソルの位置に「#」や半角スペースを追加できるように試みました。

```Vue:「#」を追加するメソッド
<script>
addHashtag(content) {
 //入力内容をtext_valに
 var text_val = this.content
 //文字数抽出
 var all_len = text_val.length
 //カーソルの位置
 var select_len = this.$refs.r.$el.querySelector('textarea').selectionStart
 //元の入力内容のカーソルの前部分を抽出
 var first = text_val.substr(0, select_len)
 //「#」をいい感じの箇所に入れる
 var insert = '#'
 //元の入力内容のカーソルの後ろ部分を抽出
 var latter = text_val.substr(select_len, all_len)
 //#を間に入れた形で元のテキストと統合する
 text_val = first + insert + latter

 this.content = text_val
},
</script>
```
ただこれでは#を入力した後にフォーカスが外れてしまい、以下の記事を参考に$nextTickを利用してみました。

- [Vue.js で表示に切り替えたDOMにフォーカスできないときは $nextTick を使う](https://qiita.com/akicho8/items/b4d071ea7eae562f529d)

しかしそれでもうまくはいかず、半ば強制的にDOMを操作してフォーカスし直しました。
原因は厳密にはわかっていませんが、どうやらVuetifyが悪さを起こしているようでした。

```Vue:「#」を追加するメソッドにnextTickを追加
<script>
addHashtag(content) {
 //入力内容をtext_valに
 var text_val = this.content
 //文字数抽出
 var all_len = text_val.length
 //カーソルの位置
 var select_len = this.$refs.r.$el.querySelector('textarea').selectionStart
 //元の入力内容のカーソルの前部分を抽出
 var first = text_val.substr(0, select_len)
 //「#」をいい感じの箇所に入れる
 var insert = '#'
 //元の入力内容のカーソルの後ろ部分を抽出
 var latter = text_val.substr(select_len, all_len)
 //#を間に入れた形で元のテキストと統合する
 text_val = first + insert + latter

 this.content = text_val

 //ここを追加
 this.$nextTick(() => {
  this.$refs.r.focus();
  this.$refs.r.$el.querySelector('textarea').setSelectionRange(select_len + 1, select_len + 1)
 });
},
</script>
```
###13. 退会

firebaseの公式の通りに従うのみです。

```javascript
      var user = firebase.auth().currentUser;
      user.delete().then(function() {
        console.log('User deleted.')
      }).catch(function(error) {
        // An error happened.
        alert('ユーザーデータを削除できませんでした。再度お試しください。')
      });
```

###14. サイトマップ作成

`@nuxtjs/sitemap`というライブラリを用いて、一瞬で対応ができます。ありがたや。
サイトマップは簡単にいうとGoogleさんなどの機械にサイト構造を教えてあげるものでして、SEO対策の1つですね。

- [Nuxt.jsで静的ファイル生成時にサイトマップも自動生成する方法](https://qiita.com/bucchi49/items/d271c4010a3f6c900926)

###15. @nuxtjs/google-tag-manager を利用したPV計測

`@nuxtjs/google-tag-manager`を用いて、GoogleAnalyticsでページごとにPVを計測できるように設定しました。

- [Nuxt SPAのPVを @nuxtjs/google-tag-manager を利用して計測する](https://tech.actindi.net/2018/11/02/084447)
- [Nuxt.jsでGoogle Tag Manager+Google Analyticsを使ってみた](https://www.memory-lovers.blog/entry/2019/06/20/113000)

ぼくはディレクターとしてアナリティクスやタグマネをゴリゴリ使っていたこともありすんなりいけましたが、そちらが初めての場合は少しきついかもしれません。

とはいえ上記の参考記事に丁寧に沿って作業をすればできるはず、ではあります。

###16. 各種メタタグ、favicon、PWAなどの設定

各種設定については、以下の参考記事にスーパー感謝。

- [Nuxt.js(v2.6.x)やっておきたい設定：その2/3｜SEOに必要なmetaとogpの設定及びpwa(Manifest、Icon、Workbox)の設定](https://qiita.com/amishiro/items/e668be423a85c2b61696)

今回はOGPなど一部は見送りましたが、それらも含めて記事に沿えば実現が可能です。

###17. 利用規約/プライバシーポリシーの策定

利用規約やプライバシーポリシーなど、漏れがちなところは以下の記事を思いっきり参考にさせていただきました。

- [個人開発者がサービスリリースに際してやることリスト](https://qiita.com/you8/items/f943b324f966ff01214e)

###18. 独自ドメインの取得

僕は独自ドメインをいつも使っているエックスドメインから取得しましたが、基本的な流れはどのサイトでも同じのはずです。

参考にした以下の記事は、お名前.comの場合です。

- [Firebaseを使って独自ドメインWebサイトを公開する方法](https://qiita.com/yacchi1123/items/963bdf12c9c4a7a8f67c)

###19. firebase Hostingでホスティング

お疲れ様でした。firebase deployで完です。

##おわりに

ブックマークはしていたり、Qiitaでいいねやストックはしていましたが、意外と改めて読み返すことって少ないですよね。

本記事を書くに当たって、自分自身の復習にもなりました。

繰り返しにはなりますが、Nuxt.js+firebaseはとても楽しいです。ご興味ある方は是非、触ってみてください！

Twitter([@utoc11](https://twitter.com/utoc11))もやっているので、こちらからも絡んでいただけると嬉しいです。