Ｎuxt ＋Firebase のメモ

# 内容

Nuxt + firebase で web アプリを作る方法についての内部向けメモです。基本、下記あたりの記事を見てそのままやっただけです。

- [Nuxt 公式](https://nuxtjs.org/ja/docs/get-started/installation)
- [Nuxt/Firebase 公式](https://firebase.nuxtjs.org/)
- [Nuxt.jsでfirebaseを使う方法](https://devsakaso.com/nuxt-firebase-installation/)
- [Nuxt.js + Firebase Authentication + FireStoreでwebアプリケーションハンズオン](https://qiita.com/ririli/items/d0d3a6ae78c1b6e827fc)

nuxt2 + firebase v8 です。nuxt3 + firebase9 は下記に書いてます

- [Nuxt3 で Firebase を使うときの忘備録](https://qiita.com/mml/items/5fcc43b5d45591566441)



# 手順

## 準備

Ubuntu 20.04LTS でやります。

```
$ npm -v
6.14.15
$ node -v
v14.18.1
$ yarn -v
1.22.11
$ firebase --version
9.20.0
```

インストールの手順は下記を参照のこと。

- [Firebase + Vue.js を WSL2 (Windows10) + Ubuntu 20.04LTS でやる](https://qiita.com/mml/items/862fdab2809fd9b1226a)

## Nuxt

Javascript, Vuetify, ESLint を選択してます。

```
$ yarn create nuxt-app first
...
✨  Generating Nuxt.js project in first
? Project name: first
? Programming language: JavaScript
? Package manager: Yarn
? UI framework: Vuetify.js
? Nuxt.js modules: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Linting tools: ESLint
? Testing framework: None
? Rendering mode: Universal (SSR / SSG)
? Deployment target: Static (Static/Jamstack hosting)
? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Continuous integration: None
? Version control system: Git
...
```

firebase の hosting を使う場合、Deployment target は Static/Jamstack hosting にする必要がある。

作成できたらテスト。

```
$ cd first
$ yarn dev
```

http://localhost:3000 にブラウザでアクセスして、下記のようになればOK。vuetify を入れてるので、ない場合とは少し表示が違う（多分）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/5594e287-b718-467b-24e5-afe1afe9f457.png)


問題なく動いたら、build と start してみる。

```
$ yarn build
$ yarn start
```

## firebase の設定

基本は[Nuxt Firebase:Getting Start](https://firebase.nuxtjs.org/guide/getting-started)の通り。

```
$ yarn add firebase@8.10.0
$ yarn add @nuxtjs/firebase@7.6.1
```

firebase のモジュールはバージョンを指定しないと 9.0.0 が入る。9.0.0 系は 8.0.0 系と使い方が微妙に違っていて、ネットに転がってる記事がそのまま通用しないことがある。

v9 でも compat を使えば v8 のように使えるけど、それなら普通に v8 を使うでいいような気もするんですが、どうなんでしょうね？

とりぜず、以下では firebase v8 でやってます。

@nuxtjs/firebase は 8.0.0 以上だと firebase v9 対応になってしまい、firebase v8 以下との併用ができないぽい。firebase v8 を使う場合は @nuxjs/firebase もバージョンを指定する必要あり。

### 初期化

```
$ yarn generate
$ firebase init hosting
? Please select an option: Use an existing project
? Select a default Firebase project for this directory: myproject (myproject)
? What do you want to use as your public directory? dist
? Configure as a single-page app (rewrite all urls to /index.html)? No
? Set up automatic builds and deploys with GitHub? No
✔  Wrote dist/404.html
? File dist/index.html already exists. Overwrite? No
```

public directory に dist を指定する。あらかじめ `yarn build` か `yarn generate` してないと生成されていないので、先にしておく。

### テスト

```
$ yarn generate
$ firebase serve --only hosting
```

firebase コマンドでテストやデプロイする場合は、`yarn build` ではなくて `yarn generate` する必要がある。これ重要。

これで http://localhost:5000 にアクセスして `yarn dev` のときと同じ結果になっていればOK。


### デプロイ


```
$ firebase deploy --only hosting
```

これで `https://myproject.web.app` にアクセスして、ローカルのときと同じ表示になっていればOK。

### apiKey とかの設定

[Nuxt Firebase:Getting Start](https://firebase.nuxtjs.org/guide/getting-started) にあるように、nuxt.config.js に apiKey などを設定する。

```nuxt.config.js
    modules: ['@nuxtjs/firebase'],
    firebase: 
      {
        config: {
          apiKey: '<apiKey>',
          authDomain: '<authDomain>',
          projectId: '<projectId>',
          storageBucket: '<storageBucket>',
          messagingSenderId: '<messagingSenderId>',
          appId: '<appId>',
          measurementId: '<measurementId>'
        },
        services: {
          auth: true // Just as example. Can be any other service.
        }
      },
```

使いたいサービスにあわせて [Nuxt Firebase:Options](https://firebase.nuxtjs.org/guide/options) を見て true を設定する。ローカルエミュレータを使う場合はもう少し設定が必要。上の麗では auth だけ有効になっている。

# Nuxt のファイル構造の基本的な理解

とりあえず、機能を試すのに必要なファイル以外を削除したのが下記の構成。これで、ルートのページには `index.vue` の中身が表示され、inspire ボタンを押すと `inspire.vue` の中身が表示される。

```
assets
└── variables.scss
layouts
├── default.vue
└── error.vue
static
└── favicon.ico
pages
├── index.vue
└── inspire.vue
components
nuxt.config.js
package.json
```

index.vue の中身は / を指定したときに表示される。

```pages/index.vue
<template>
  <v-container>
    Hello World
    <v-btn to="/inspire">
      inspire
    </v-btn>
  </v-container>
</template>
```

pages/ 以下の index.vue 以外のファイルは、`pages/xxxx.vue` というファイル名にすることで `/xxxx` というパスでアクセスできるようになる。vue-router に相当するものは、build したときに .nuxt/router.js に自動的に生成される。

```pages/inspire.vue
<template>
  <v-container>
    inspire
    <v-btn to="/">
      Home
    </v-btn>
  </v-container>
</template>
```

layouts/default.vue には全ページに共通するヘッダやフッダのような、ページの外枠になるUIを書いておく。ファイル中に `<Nuxt />` タグを書くと、そこにパスと一致する `pages/xxxx.vue` の中身を配置してくれる。

```layouts/default.vue
<template>
  <v-app dark>
    <v-main>
      <v-container>
        <Nuxt />
      </v-container>
    </v-main>
  </v-app>
</template>

<script>
export default {
  data () {
    return {}
  }
}
</script>
```

ページによって外観を変えたい場合は、下記のように `layout: 'hogex'` とかしておいて、

```layouts/hoge.vue
<script>
export default {
  layout: 'hogex',
  ...
}
</script>
```

pages のほうのファイルで、たとえば、

```pages/hoge.vue
<template>
  <v-container>
    Hogex
  </v-container>
</template>
<script>
<script>
export default {
  name: 'hoge',
  layout: 'hogex'
}
</script>
```

みたいなかんじで layout を指定すると、hogex という layout を持つ vue ファイルが外観として使用される。ぽい。

エラーページを出すには `layouts/error.vue` というファイルを作る。layout に empty が指定されているけど、empty という特別なレイアウトの名前があるわけではないらしい。あと、layouts/ 以下にあるけど、error.vue 自体はページ扱いされる（らしい）。

```layouts/error.vue
<template>
  <v-app dark>
    <h1 v-if="error.statusCode === 404">
      {{ pageNotFound }}
    </h1>
    <h1 v-else>
      {{ otherError }}
    </h1>
    <NuxtLink to="/">
      Home page
    </NuxtLink>
  </v-app>
</template>

<script>
export default {
  layout: 'empty',
  props: {
    error: {
      type: Object,
      default: null
    }
  },
  data () {
    return {
      pageNotFound: '404 Not Found',
      otherError: 'An error occurred'
    }
  },
  head () {
    const title =
      this.error.statusCode === 404 ? this.pageNotFound : this.otherError
    return {
      title
    }
  }
}
</script>

<style scoped>
h1 {
  font-size: 20px;
}
</style>
```

## assets ディレクトリ

`assets/variables.scss` は Vuetify のカスタマイズ用のグローバル設定を書くところ。空でもいいけど、存在しないとエラーになる。nuxt.config.js の下記の設定を消すと、エラーにならなくなる。

```nuxt.config.js
 vuetify: {
    customVariables: ['~/assets/variables.scss'],
    ...
 }
```

Vuetify 用の設定なので、Nuxt には直接関係はない（はず）。

- [SCSS/CSSで基本カラーを設定して効率よくまとめる](https://qiita.com/shohei_f/items/d8981908c89a9831a769)

## static ディレクトリ

static/favicon.ico は、置いておかないとブラウザが勝手に探しに行って 404 エラーが発生しまくるので、それの回避のためだけに置いてあるだけ。

static 以下にファイルを置くと、直接そのファイルを参照できる。たとえば `static/hoge.png` みたいなファイルがあるとき、`<template>` タグの中で、下記のような感じで参照できる。

```sample.vue
<template>
   <img src="/hoge.png"/>
</template>
```

このとき、パスに `static` は含めない。

ローカルの javascript も static に置ける（asset に置くこともできる）。たとえば `static/local.js` を参照したい場合は、nuxt.config.js の中に、次のような記述を書くことで読み込める。

```nuxt.config.js
  head: {
    script: [
      {
        src: "/local.js",
      }
    ],
  }
```

画像などの固定リソースは、`assets` 以下に置く方法もある。

- [【Nuxt】テンプレートでassetsとstatic配下のリソースを参照するときの書き方](https://public-constructor.com/nuxt-assets-static-resource/)


## layouts の適用ルール

- [他のページでデフォルトのレイアウトを無効にする方法](https://stackoverflow.com/questions/58134718/how-disable-default-layout-on-other-page)

上記によれば、layout の適用はこのようになるらしい。

- すべてのページはレイアウトdefault.vueでロードされます
- ページのレイアウトを指定した場合は、指定したレイアウトで読み込まれます。
- 指定されたレイアウトが利用できない場合、ページはdefault.vueでロードされます
- nuxtがlayoutsフォルダーにdefault.vueという名前のファイルを表示しない場合、ページは空のレイアウトで読み込まれます

empty を指定したからといって、レイアウトがなしになるわけではなくて、`layout: 'empty'` と書かれている layouts/ 以下のファイルを探しに行くだけだそうな。

参考：
- [【Nuxt】エラーページ作成](https://qiita.com/smkhkc/items/6d5b29fd8f514e3e0b60)
- [【Nuxt.js】layoutsに設定をまとめつつ、タイトルはpagesから設定したい](https://qiita.com/yama-t/items/b051cd9ea802f69da118)

## テスト再び

ここまでの状態で yarn dev すると、下記のような画面になるはず。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/9aae5dc1-32fc-fe63-20de-033f310d2c2f.png)

inspire を押すとこうなる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/e5c29bb4-0f54-7a7e-e09f-0f5405192122.png)

存在しないパスを指定するとこうなる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/4af307cd-350e-906c-60d0-d27e396e8265.png)

URL を直接指定して http://localhost:3000/inspire にアクセスしてもエラーになる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/039546a3-304f-e513-a05a-598a1a1545d2.png)

あくまで、home から遷移してしか inspire には行けない。このあたりは vue-router のときと同じ。

# firebaseui + firebase authentication でログインする

## 準備

バージョンを指定して firebaseui モジュールをインストールしておく。バージョンを指定しないと 9.0.0 対応のモジュールが入ってしまって、うまく動かないことがある。(2021/10現在)

```
$ yarn add firebaseui@5.0.0
```

apiKey を取得しておく。上のほうで nuxt.config.js に指定済なら問題なし。

## ログインページの作成

```pages/login.vue
<template>
  <v-container>
    <div>
        <div id="firebaseui-auth-container"></div>
    </div>
    <v-btn to="/">
        home
    </v-btn>     
  </v-container>   
</template>
<script>
export default {
  mounted() {        

    const firebaseui = require('firebaseui');
    require("firebaseui/dist/firebaseui.css");
    const ui = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(this.$fire.auth);
    const tmp = this;

    const config = {
      signInOptions: [
        //firebase.auth.EmailAuthProvider.PROVIDER_ID,
        this.$fireModule.auth.EmailAuthProvider.PROVIDER_ID,
        // firebase.auth.GoogleAuthProvider.PROVIDER_ID
        //this.$fireModule.auth.GoogleAuthProvider.PROVIDER_ID
      ],
      signInSuccessUrl: '/',
      callbacks: {
        signInSuccessWithAuthResult() {
          console.log('ok');
          tmp.$router.push('/');
        },
        uiShown: function () {
          console.log('ui');
        }
      }
    };

    ui.start('#firebaseui-auth-container', config);
  }
};
</script>
```

`$fireModule` をつかうのがみそ。`signInSuccessUrl` が機能してないかんじがするので、直接とばしている。要調査。

オプションとか細かいところは、下記と同じ。

- [Firebase Authentication + FirebaseUI + Vuetify でメール認証でログインする](https://qiita.com/mml/items/ffdf7c228fb8d0868525)

## SSR に対応してないプラグインを使うときの注意

Vue とか Vuetify 用で、SSR に対応していないモジュールを使うときは、`<client-only>` タグを使う

[vuetify-markdown-editor](https://www.npmjs.com/package/vuetify-markdown-editor) を使おうとしたら、document is not found というエラーが出たので、原因を調べたらこれだった。 `<no-ssr>` タグを使うように書かれている記事が見つかるけど、このタグは nuxt3 で削除されるので、今後は `<client-only>` を使いなさいということらしい。

```editor.vue
<template>
  <v-app>
    <v-main>
      <client-only>
        <Editor
            mode="preview"
            ref="editor"
            hint="Hint"
            :outline="false"
            :render-config="renderConfig"
            v-model="text"
        />
      </client-only>
    </v-main>
  </v-app>
</template>
<script>
import { Editor } from "vuetify-markdown-editor";
export default {
    name: "App",
    components: {
        Editor,
    },
    data: () =>({
      renderConfig: {
        mermaid: {
            
        }
      }
    }),
    mounted: function(){
        this.$vuetify.theme.dark = false;
        console.log("editor module");
    },
    methods: {
    }
}
</script>
```

こんな感じ。もちろん使うには下記が必要。

```
$ yarn add vuetify-markdown-editor
```