最終更新 : 2019/12/16

今年はコーポレートサイトやオウンドメディアの開発には ContentfulFirebaseを使ったサーバーレスな構成を多用させていただきました😇

2019年にContentfulを使って作ってきたものは、表に出せるもので以下のものがあります。

それらの開発の中で知見が溜まってきたな〜というところで、保活支援サービスEQGを運営されているフォーポイント株式会社様から以下のようなご相談をいただきました。

フォーポイント株式会社さま 「保活支援をする中で子育て向きの物件を探す方に出会う機会が増えてきて、それをサービス化してニーズを確かめたいんだけど、手軽にやる方法ないかな?」

これは丁度いい機会だと思い、

  • Nuxt.js
  • Contentful
  • Firebase Hosting

JAMstack ※1 な構成で、従来のオウンドメディアに不動産サイトを追加するという方法を提案し、実現しました。

リリースした不動産サイトはこちら → EQG不動産

子育て向き物件をお探しの方は、ぜひお気軽にご相談ください!(PR)

EQG不動産の開発

では、以下でどのような機能があり、それらをどのように実装したかの話をしたいと思います。

機能的要求仕様

  • 物件一覧ページ
    • 絞り込み検索
    • タグ検索
  • 物件詳細ページ

ContentfulのContent model設計

今回必要なContent modelを一部紹介すると

  • 物件基本情報
  • 路線
  • 間取り
  • タグ

などがあり、ER図で表すと以下のようになっています。

Content modelで1対1、1対多のデータをもたせたい場合は、FieldsのReferenceタイプを選択します。
そこで、One referenceMany referenceを選択すれば他のContent modelとの紐付きを管理可能となっています。
仕様次第かと思いますが、ValidationsAccept only specified entry typeに既存のContent modelを設定すれば、入力ミスを防いだ上で入力が可能になります。

実装

NuxtやFirebaseの設定などは他のブログで色々書かれているので、ContenfulのContent Delivery APIを呼び出すところを中心にここでは書いていきます。

◇ 一覧ページ

一覧ページでは、タグや金額による検索が可能となっています。
ContentfulのContent Delivery APIのオプションを使って行うのですが、以下のような実装をしています。

async fetchRooms ({ commit }, params) {
  try {
    const { limit, page } = params
    const skip = // page と limitから計算
    const query = {
      content_type: 'room',
      limit,
      skip,
      order: '-sys.createdAt',
    }
    if (params.tagId) {
      query['fields.tag.sys.id'] = params.tagId
    }
    if (params.monthlyFeeMax) {
      query['fields.monthlyFee[lte]'] = params.monthlyFeeMax
    }
    if (params.monthlyFeeMin) {
      query['fields.monthlyFee[gte]'] = params.monthlyFeeMin
    }
    /**
     * 一部省略
     */
    const rooms = await client.getEntries(query)
  } catch (e) {
    // error handlings
  }
}

上記のコードでqueryに渡している [lte], [gte] は、fieldの内容を絞りこむためのものです。

参照: Range

Four range operators are available that you can apply to date and number fields:
[lt]: Less than.
[lte]: Less than or equal to.
[gt]: Greater than.
[gte]: Greater than or equal to.
When applied to field values, you must specify the content type in the query.

あとは、こちらの記事 Contentfulを使い倒したくてContentful芸を磨く【検索・絞り込み編】 で紹介した全文検索なども使っています。

◇ 詳細ページ

詳細ページでは、Contentfulで吐き出されるランダムな文字列のIDではなく、URLに意味を持たせてページを表示したいので、fieldsにslugを設定し、それで詳細ページを表示できるようにしています。

  async fetchRoom ({ commit }, { slug }) {
    try {
      const client = contentful.createClient(config)
      const posts = await client.getEntries({
        content_type: 'room',
        'fields.slug': slug,
      })
      if (posts.items.length > 0) {
        commit(SET_ROOM, posts.items[0])
      } else {
        throw new Error('404 not found')
      }
    } catch (e) {
      // error handlings
    }
  },

あとは、物件画像が多いため、読み込みを早くするために、Images APIを利用して、表示する画像をリサイズしたりしています(Contentful本当になんでもあるな)。

リサイズと言ってもパラメーターを付与してあげるだけなので以下のように書いてあげるだけで

  • w → width : 1000px
  • q → quality : 95%

と、変換して配信することが可能です。

<img
  :src="`${image.fields.file.url}?w=1000&q=95`"
  :alt="image.fields.title"
>

◇ Firebase Hosting

Nuxtをunivarsal modeでgenerateすると、dist.nuxtが生成されるかと思います。
Hostingには、distのディレクトリをまるっとdeployします。

弊社環境では、CIからdeployされるようになっているので、Contentfulのwebhookとも連携し、コンテンツがPublishされるとCIが実行され、masterの内容が都度deployされるようになっています。

firebase.jsonの例

{
  "hosting": {
    "public": "dist",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

package.jsonの例

"script": {
  "deploy:dev": "firebase deploy --project $FIREBASE_DEV_PJ --token $FIREBASE_DEV_TOKEN"
}

package.jsonに記述しておくことで、CIで yarn deploy:dev を実行するだけとなります。
以下の環境変数はCircle CIの方に設定します。

  • $FIREBASE_DEV_PJ
  • $FIREBASE_DEV_TOKEN

まとめ

このような形でサーバーを用意したりすることなく、サービスを短期で立ち上げることができました。
デザイナーとフロントエンドエンジニアのみでサクッとサービスを立ち上げられるのは、メリットかと思います。
お客様が運用などあまりやりたくないという場合など、SLAなどの問題もあるにはありますが、サーバーレスな構成 & ContentfulのようなSaaSを利用をご提案するのも悪くないなと思っています。

と、いうことで、来年も引き続きJAMおじさんをやっていきたいと思います!

参考サイト

※1 : JAMstackってなに?実践に学ぶ高速表示を実現するアーキテクチャの構成