.../articles/

Nuxt + Contentful + Firebase Hosting のJamstackを使ったサービス開発

今年はContentful芸のブログばかり書いていたら、Jamstackおじさん(通称Jamおじさん)になってしまいました。 今回は実際にリリースしたサービスでどのように実装したかを書いてみたいと思います。

最終更新 : 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ってなに?実践に学ぶ高速表示を実現するアーキテクチャの構成

.../articles/

Articles

記事

AWS AmplifyにmonorepoのNext.js(App Router)をデプロイする

AWS AmplifyにmonorepoのNext.js(App Router)をデプロイする

monorepo管理しているNext.jsをAmplifyにデプロイしようとした際にいくつか躓く内容があったのでまとめておきます。

リモートワーク・オンライン会議でも、スムーズに制作を進めるために大切なこと[資料編]

リモートワーク・オンライン会議でも、スムーズに制作を進めるために大切なこと[資料編]

コロナ禍の影響により、リモートワークの導入をおこなっている制作会社も多く、実際に弊社でも導入しています。

売れるECサイトデザインを作るために。参考にしたいおしゃれな事例の探し方。

売れるECサイトデザインを作るために。参考にしたいおしゃれな事例の探し方。

売れるECサイトのデザインは、「この形式」という決まりはありません。ECサイトで売り上げを上げるなら、しっかりとしたコンセプトと、コンセプトを決定するまでのリサーチが必要です。

制作会社の考える、業務効率化ツールのおすすめ。個人でも使いやすいサービスなど。

制作会社の考える、業務効率化ツールのおすすめ。個人でも使いやすいサービスなど。

新型コロナウイルス感染拡大の影響で、リモートワークが主流になり、弊社でも週のほとんどは各自宅で作業をしています。

Figmaでデザインのコミット履歴を残せるプラグイン【Thought Recorder】をリリースしました

Figmaでデザインのコミット履歴を残せるプラグイン【Thought Recorder】をリリースしました

Figmaを利用するWebデザイナーの助けになれると嬉しいです。使い方は本記事をご覧ください。

ECの構築方法、おすすめのECサービス。

ECの構築方法、おすすめのECサービス。

ファッションや家電、スーパーの買い物でさえもECサイトを利用することが当たり前になりました。加えて新型コロナウイルスの影響もあり、弊社にも「どんなプラットフォームを利用したら良いか」「どれくらいコストがかかるのか」などECに関するさまざまなご相談を頂きます。

FastAPIのスキーマクラスをOpenAPIから生成する方法

FastAPIのスキーマクラスをOpenAPIから生成する方法

PythonでAPIを構築する要件があり、フレームワークに比較的モダンなFastAPIを採用しました。FastAPIはバックエンドの開発を行えば自動でOepnApi定義を生成する機能が備わっていますが、今回はこれを使わず、事前に用意したOepnApi定義からFastAPIで利用するスキーマクラスを生成する方法を紹介します。

Laravel 日本一解りやすい全文検索のマイグレーション記載方法解説

Laravel 日本一解りやすい全文検索のマイグレーション記載方法解説

Laravel + MySQLで全文検索を実装する

GiFT1号目新卒デザイナーの2021年振り返り

GiFT1号目新卒デザイナーの2021年振り返り

いつの間に、年末ですね。入社してもう、9ヶ月も立っていたようです。2021年の振り返りを記事にしました。

TimesclaeDBのデータ圧縮に関して

TimesclaeDBのデータ圧縮に関して

TimescaleDBはデータベース内の一部のテーブルを時系列データとして扱えるPostgreSQLの拡張です。PostgreSQLの機能拡張なので非常に手軽に導入できます。今回はこのTimesaceDBの圧縮について調べたので備忘録として書き綴りました。

すべての記事

お問い合わせ