🌇

NuxtでスプレッドシートをDB代わりに使うぞ大作戦 Part3 - asyncDataでだって使いたい!

6 min read

こんにちは。@asatoです☀

シリーズ最終章(予定)です!

[前回の記事]

https://zenn.dev/at946/articles/bfd9d500359427

[前々回の記事]

https://zenn.dev/at946/articles/0c170df6a5a16b

Part2では、PCは表示できるけどモバイルはできない、というPart1の課題をJSON→JSONPに更新することで解決しました。🎉

これでOKかと思いきや、spaces.bzの開発では次なる課題が!

  • 動的OGPやりたいよね
  • Search Consoleでソフト404って出てるけどなんだろうね

うむ。いまのデータはcreatedで取得しているのですが、ロボットに情報を確認してもらうためにはasyncDataでデータを取得しないとですね。

たとえば、spaces.bzデイリーランキングページを見てください。
このページでは動的OGPやソフト404対策としてアクセス時はasyncDataを使ってスプレッドシートからデータを取得し、
日付の変更の場合はmethodsの関数を使ってスプレッドシートからデータを取得しています。

https://spaces.bz/daily_ranking/?rank=1

Part2の内容を踏まえると、asyncDataでも同じようにデータを取得しようとすれば実現できそうですよね。
(ボタンを押したらデータを更新する感じにします。)

pages/index.vue
  <template>
    <div>
+     <button @click="getUsers">更新</button>
      <table>
        <thead>
          <tr>
            <th>id</th>
            <th>name</th>
            <th>age</th>
            <th>email</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for='user in users' :key='user.id'>
            <td>{{ user.id }}</td>
            <td>{{ user.name }}</td>
            <td>{{ user.age }}</td>
            <td>{{ user.email }}</td>
          </tr>
        </tbody>
      </table>
    </div>
  </template>

  <script>
  const jsonpAdapter = require('axios-jsonp')

  export default {
+   async asyncData({ $axios }) {
+     const users = await $axios.$get('/api', { adapter: jsonpAdapter })
+     
+     return {
+       users: users
+     }
+   },
-   data() {
-     return {
-       users: []
-     }
-   },
-   created() {
-     this.getUsers()
-   },
    methods: {
      async getUsers() {
        this.users = await this.$axios.$get('/api', { adapter: jsonpAdapter })
      }
    }
  }

よっしゃ!これで行けるはず!

document is not found

まあ、これで大丈夫なら記事書かないんですよね。

ということで、今回はasyncDataでもスプレッドシートをDB代わりに使おう大作戦です。

何がだめなの?

なにが起きてるかというと、axios-jsonpでエラーが発生しています。
「document is not found」とか「window is not found」とか、このエラーはフロントエンドで動作する前提のパッケージがバックエンドで動作するときに起こります。
つまり、asyncDataはバックエンドでデータを取得するので、フロントエンドで動作する前提のaxios-jsonpは利用できないのです。

じゃあどうする?

こういうパッケージはnuxt.config.jsssr: falseすればいい、とか、<no-ssr><client-only>タグで囲めばいい、とか解決策があるのですが、今回はasyncDataで動作させたいので採用できません。
かといって、何も考えずにJSONPをやめてしまえばまたモバイルで使えなくなる...

ということでserverMiddlewareを使ってみましょう!
いままで「フロントエンド」→「GASのWebApp」と通信していた間にBFFライクにserverMiddlewareが入ります。
これによって、フロントエンドとしては同じドメインのサーバーと通信しているようになるのでCORS対策もバッチリになります。

あれ、じゃあモバイルで使えないってなったとき、JSONPじゃなくてserverMiddlewareでもよかったってこと?
そうですね。気づきませんでした...

NuxtアプリでserverMiddleware経由でGAS APIをコールする

まずはBFFでGAS APIをコールするコードを書いていきましょう。
ここではexpressを利用します。

$ yarn add express

新しくapiディレクトリを作成し、その中にBFFのコードを書いていきましょう。

$ mkdir api
$ touch api/index.js
api/index.js
const express = require('express')
const axios = require('axios')

const app = express()

app.get('/', async (req, res) => {
  const users = await axios.get(<ウェブアプリのURL(GAS API)>)
  res.send(users.data)
})

module.exports = {
  path: "/api/",
  handler: app
}

今までpages/index.vueでやっていたaxiosの処理を転記した感じですね。
これをnuxt.config.jsserverMiddlewareとして登録しましょう。(ついでにproxyも不要になったので消します)

nuxt.config.js
  export default {
    ...
    axios: {
      proxy: true
    },
-   proxy: {
-     '/api': {
-       target: <ウェブアプリ(GAS API)URL>,
-       pathRewrite: { '/api': '' }
-     }
-   },
+   serverMiddleware: [
+     '@/api/'
+   ],
    ...
  }

これでserverMiddlewareの準備は完了です。

フロントエンドもJSONPからJSONを取得するように更新しておきましょう。

pages/index.vue
  ...
  <script>
- const jsonpAdapter = require('axios-jsonp')
-
  export default {
    async asyncData({ $axios }) {
-     const users = await $axios.$get('/api', { adapter: jsonpAdapter })
+     const users = await $axios.$get('/api')

      return {
        users: users
      }
    },
    methods: {
      async getUsers() {
-       this.users = await this.$axios.$get('/api', { adapter: jsonpAdapter })
+       this.users = await this.$axios.$get('/api')
      }
    }
  }
  </script>

axios-jsonpも不要になったので、パッケージを削除しておきましょう。

$ yarn remove axios-jsonp

見ての通り、JSONPではなくJSONを期待してのコールですので、GASのコードを元(Part1)時点に戻していきます。

GAS APIをJSON返却に戻す

コード.js
  function doGet(e) {
    const users = getUsers()
-   const callback = e.parameter.callback

    return ContentService
-     .createTextOutput(`${callback}(${JSON.stringify(users)})`)
-     .setMimeType(ContentService.MimeType.JAVASCRIPT)
+     .createTextOutput(JSON.stringify(users))
+     .setMimeType(ContentService.MimeType.JSON)
  }
  ...

ここまででBFFの準備ができました!🎉

動作確認

最後に動作確認をしておきましょう。
期待動作としては

  1. http://localhost:3000/にアクセス → ユーザーA/B/Cのデータが表示される!
  2. SpreadsheetにユーザーDのデータを登録
  3. Nuxtの方で「更新」ボタンを押す → ユーザーDのデータも表示される!

です!ではやっていきましょう!

1 `http://localhost:3000/にアクセス

http://localhost:3000/にアクセスして、ユーザーA/B/Cのデータが表示されることを確認!
表示されてる!

2 スプレッドシートにユーザーDを追加

id name age email
1 Test A 20 test_a@sample.com
2 Test B 30 test_b@sample.com
3 Test C 40 test_c@sample.com
4 Test D 50 test_d@sample.com

3 更新ボタンを押下

http://localhost:3000/で更新ボタンを押して、ユーザーA/B/C/Dのデータが表示されることを確認!
ユーザーDの情報が新たに表示されてる!

テスト成功!🎊

まとめ

今回はserverMiddlewareを使って、スプレッドシートをDB代わりに使ってみました。
ここまで色々と試行錯誤をしてきましたが、今回がやりたいことができて一番手っ取り早い方法なんじゃないかと思います。
Google Sheet APIとかも試みてみましたが挫折しました...

スプレッドシートだとデータも見やすいですし、GASで色々操作できたりもするので、作りたいプロダクトによっては一つの選択肢になってくるのかなと思います。
この記事が、どなたかの役に立てば嬉しいです!

こんな仕組みを使っているspaces.bzも応援よろしくお願いします!

https://spaces.bz/

Discussion

ログインするとコメントできます