LoginSignup
9

More than 1 year has passed since last update.

posted at

updated at

Organization

Flamelink + Nuxt 触ってみた

Flamelinkとは

Flamelinkとは、FirebaseをヘッドレスCMS化できるサービスです

Flamelink公式

参考

基本的には上記の内容に則って進めていきます
また、参考サイトではDatabaseにRealtime Databaseを使用していますが、今回はCloud Firestoreを使用しています

もくじ

バージョン

$ node --version
v10.22.0

$ npm --version
6.14.6

FlamelinkとFirebaseの連携

※ここではFirebaseとFlamelinkを行ったり来たりするので、
先頭に<Firebase>、<Flamelink>を入れています

1. <Firebase> Firebaseでプロジェクトを作成しておく

この記事では割愛します

2. <Flamelink>Flamelinkのアカウント作成

1.png

3. <Flamelink>Databaseの選択

今回はCloud Firestoreを選択
Firestore側のセキュリティルールが表示されているのでコピーしておきます

2.png

4. <Firebase> Firestoreのルールを設定

Firebase > Cloud Firestore > ルールに、
先ほどコピーしたルールを貼り付けて公開します

3.png

5. <Flamelink> Storageのルールを表示

Databaseのルールと同様にコピー

4.png

7. <Firestore> Storageのルールを設定

同様にFirebase > Storage > ルールに貼り付け

5.png

8. <Flamelink> 認証の設定

Email/Passwordでの認証を有効にしてと出るので

6.png

9. <Firebase> Authenticationの設定

Firebase > Authentication > Sign-in methodから、
メール/パスワードを有効にします

7.png

10. <Flamelink> 料金プランを選択してFINISH

今回はテストなので無料のFlameを選択、確認して完了!

8.png
9.png
10.png

Flamelinkで記事の作成

1. PROJECTにログイン

初回のみパスワード設定画面が開きます

11.png

2. スキーマを作成

「NEW SCHEMA」からスキーマを作成

12.png

任意のtitle, schema IDを設定
ここで指定されたschema IDが、後程Nuxtで記事を取得する際のIDになります

13.png

「ADD FIELDS」タブから、記事のフィールドをドラッグ&ドロップで自由にカスタマイズできます

14.png

完了したら「SAVE」からスキーマを保存

3. 記事の登録

左ナビのContentメニューをクリックすると登録したスキーマがでるので「VIEW」をクリック

15.png

「NEW ENTRY」から記事を投稿
ここでは3件ほど登録しました

16.png

Firebase > Cloud firestoreを確認すると、
Flamelinkから登録したデータが追加されていることが分かると思います

「fl_」の接頭辞がついているコレクションがFlamelinkから追加されたものです
「fl_content」コレクションが記事のデータになります

17.png

FlamelinkSDKを利用してNuxtで記事取得

1. Nuxtでプロジェクト作成

npx create-nuxt-app <project-name>

一度起動して動作を確認

npm run dev

2. 必要なライブラリのインストール

ライブラリをインストールするために、package.jsonに以下を追記します

package.json.diff
  "dependencies": {
    "@nuxt/typescript-runtime": "^1.0.0",
    "@nuxtjs/axios": "^5.12.0",
    "nuxt": "^2.14.0",
+   "firebase": "^7.19.1",
+   "firebase-admin": "^9.1.1",
+   "flamelink": "^1.0.0-alpha.24"
  },
npm i

でインストール

3. NuxtでFlamelinkを使う設定

nuxt.config.js に以下を追記

nuxt.config.js
  plugins: [
    '@/plugins/flamelink'
  ],

flamelink.jsを作成

公式のflamelink.jsをコピーしてプロジェクト直下に保存します
公式はDBにRealtimeDatabaseを使っていたり、Node8を使用していたり、今回の環境と異なる点があるので一部修正します

flamelink.js.diff
import flamelink from 'flamelink/app'
// This example uses RTDB (Realtime Database) - replace with `cf` for Cloud Firestore
- import 'flamelink/rtdb/content'
- import 'flamelink/rtdb/storage'
+ // 今回Cloud firestoreを使用するのでimportするパッケージを修正
+ import 'flamelink/cf/content'
+ import 'flamelink/cf/storage'
// import 'flamelink/rtdb/settings'
// import 'flamelink/rtdb/navigation'
// import 'flamelink/rtdb/users'

export default ({ app }) => {
  let firebaseApp

  if (process.server) {
    const admin = require ('firebase-admin')

    if (!admin.apps.length) {
-     const serviceAccount = require(process.env.FLAMELINK_PATH_TO_SERVICE_ACCOUNT)
+     // Node10でうまくいかないっぽかったので修正
+     const serviceAccount = require('../serviceAccountKey.json')

      firebaseApp = admin.initializeApp({
        credential: admin.credential.cert(serviceAccount),
        databaseURL: process.env.FLAMELINK_DB_URL,
        storageBucket: process.env.FLAMELINK_STORAGE_BUCKET
      });
    } else {
      firebaseApp = admin.app()
    }
  } else {
    const firebase = require('firebase/app')
    // require('firebase/auth')
    // require('firebase/firestore')
    require('firebase/database')
    require('firebase/storage')

    if (!firebase.apps.length) {
      firebaseApp = firebase.initializeApp({
        apiKey: process.env.FLAMELINK_API_KEY,
        authDomain: process.env.FLAMELINK_AUTH_DOMAIN,
        databaseURL: process.env.FLAMELINK_DB_URL,
        projectId: process.env.FLAMELINK_PROJECT_ID,
        storageBucket: process.env.FLAMELINK_STORAGE_BUCKET,
        messagingSenderId: process.env.FLAMELINK_MESSAGING_SENDER_ID
      })
    } else {
      firebaseApp = firebase.app()
    }
  }

+ //dbTypeをcf(Cloud firestore)に変更
- app.flamelink = flamelink({ firebaseApp, dbType: 'rtdb' })
+ app.flamelink = flamelink({ firebaseApp, dbType: 'cf' })
}

4. Firebase側の設定

サービスアカウントのjsonファイルを作成して設置

サービスアカウント情報を含むJsonファイルをFirebase側で生成、ダウンロードし、
serviceAccountKey.json にリネームしてNuxtのプロジェクト配下に設置します。
Jsonファイルは 設定 > サービスアカウント > 新しい秘密鍵の生成 で作成できます

18.png

.gitignoreにserviceAccountKey.jsonを追加

gitignore.diff
+ # Firebase service account key
+ serviceAccountKey.json

.envファイルの作成

プロジェクト直下に .env ファイルを作成し、以下の情報を埋めます
Firebase > 設定 > 全般から取得できます

FLAMELINK_API_KEY="<firebase-api-key>"
FLAMELINK_AUTH_DOMAIN="<firebase-auth-domain>"
FLAMELINK_DB_URL="<firebase-db-url>"
FLAMELINK_PROJECT_ID="<firebase-project-id>"
FLAMELINK_STORAGE_BUCKET="<firebase-storage-bucket>"

19.png

最終的なディレクトリ構造

20.png

5. コンポーネントから記事データを取得できるか確認

pages/index.vue で取得テスト

index.vue.diff
export default {
  components: {
    Logo,
    VuetifyLogo,
  },

+ async asyncData({ app }) {
+   try {
+     const myposts = await app.flamelink.content.get({
+       schemaKey: 'myposts', // Flamelinkで設定したschemeID
+       populate: true
+     })
+     console.log({ myposts })
+     return { myposts }
+   } catch (err) {
+     console.log(err)
+     return { myposts: [] }
+   }
+ }
+
npm run dev

ブラウザのコンソールで記事情報が取得できているかを確認(Vueの開発ツールでもOK)

21.png

無事取得できていました :tada:

取得した記事をVuexで管理する

Flamelinkから取得した記事のデータは一覧ページと詳細ページで共通なので、Vuexでストア管理することにした
先ほどの index.vue に記述した取得ロジックをstoreに移植する

storeの準備

store/index.ts を作成し、state, getters, mutations を用意

<参考>Nuxt-Vuexの型定義

store/index.ts
import { GetterTree, ActionTree, MutationTree } from 'vuex'

import { formatDate } from '~/utils/format' // *これは自前で用意した関数
import { PostData } from '~/types' // *これは自前で用意した型

export type RootState = ReturnType<typeof state>

export const state = (): {
  posts: PostData[] | null
} => ({
  posts: null
})

export const getters: GetterTree<RootState, RootState> = {
  posts: state => state.posts
}

export const mutations: MutationTree<RootState> = {
  FETCH_POST: (state, payload: PostData[]) => {
    state.posts = payload
  }
}

nuxtServerInitで記事の取得

<参考>nuxtServerInitアクション

nuxtServerInit というアクションがストア内に定義されて、かつ universal モードである場合は、Nuxt.js はそれをコンテキストとともに呼び出します(ただしサーバーサイドに限ります)。サーバーサイドからクライアントサイドに直接渡したいデータがあるときに便利です。

store/index.ts にアクションを追加

Flamelink JS SDKの理解が少し足りず力技の部分もあるかもなのだが、
以下のとおり取得データを加工している

  • 1. ソート用に記事作成日時データを追加
    今回、date にFlamelinkから作成日のデータが注入されるのだが、 "2020-09-09T00:00:00+09:00" のように、同日付の記事は時間の部分が固定になっていた
    そのため同日作成の記事をうまくソートできないので、 _fl_meta_.createdDate._seconds から日時を取得し、createdDate フィールドに設定
  • 2. フォーマット化した日付を用意
    1の日時データをもとにフォーマット化した日付文字列を formatedDate フィールドに設定
  • 3. 記事のソート
    1の日時データをもとに記事データを降順にソート
store/index.ts
export const actions: ActionTree<RootState, RootState> = {
  async nuxtServerInit ({ commit }, { app }) {
    const getData: {
      [key: string]: PostData
    } = await app.flamelink.content.get({
      schemaKey: 'myposts',
      populate: true
    })

    // データ加工
    const myposts = Object.entries(getData)
      .map(([k, v]) => {
        const createdDate = new Date(v._fl_meta_.createdDate._seconds * 1000)
        v.createdDate = createdDate // (1)
        v.formatedDate = formatDate(createdDate) // (2)
        return v
      })
      .sort((a: PostData, b: PostData) => a.createdDate > b.createdDate ? -1 : 1) // (3)

    commit('FETCH_POST', myposts)
  }
}

無事Vuexにぶち込まれました:tada:

22.png

画面の作成

記事一覧

pages/index.vue

  • データ myposts に、ストアで用意したゲッター経由でデータを取得して格納
  • mypostsv-for でループ
  • リンクパラメータには id (記事ID)をセット

BlogList.vue は日付と見出しを受け取って表示するだけのコンポーネント

pages/index.vue
<template lang="pug">
  v-layout(column justify-center align-center)
    v-row
      v-col(v-for="item in myposts" :key="item.id" cols="12")
        nuxt-link(:to="`/article/${item.id}`")
          blog-list(
            :title="item.title"
            :date="item.formatedDate"
          )
</template>

<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'
import BlogList from '~/components/BlogList.vue'

@Component

export default class PageIndex extends Vue {
  myposts = this.$store.getters.posts
}
</script>

記事詳細

pages/article/_id.vue

※ 動的ルーティングさせるのでファイル名をスネークケースに

  • index.vue 同様に記事データをストアから取得
  • this.$route.params.id にわたってくる記事IDで該当記事データをフィルター
pages/article/_id.vue
<template lang="pug">
  div
    h1
      | {{ entry.title }}
    div(v-html="entry.content")
</template>

<script lang="ts">
import {Component, Vue} from 'nuxt-property-decorator'
import { PostData } from '~/types'

@Component
export default class PageArticle extends Vue {
  myposts = this.$store.getters.posts

  get entry() {
    const id = this.$route.params.id
    const entry = this.myposts.find((e: PostData) => {
      return e.id === id
    })
    return entry
  }
}
</script>

GOOD JOB :tada:

25.gif

雑感

記事の投稿フォーマットからFirestoreへの登録までFlamelinkが担ってくれるので、かなり簡単にブログが実装できるのを感じました

とりあえず今回は記事の取得、表示までを実装してみましたが、現段階ではブログ更新の都度 nuxt generate して firebsae deploy でホストさせる仕組みにとどまっています
引き続きJamstackで静的配信できるよう勉強していきたいと思います


:christmas_tree: FORK Advent Calendar 2020
:arrow_left: 14日目 GCS へのファイルアップロードをトリガーにBigQueryにデータをインポートする。 @shuhei4009
:arrow_right: 16日目 Full Static Generationを試す @AsaToBan

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
9