Remix + Contentfulのチュートリアルが公開されたので実際に触りつつ、Netlifyにデプロイしてみる。

2022.09.29

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

どうも、ベルリンオフィスの小西です。

ヘッドレスCMSのContentfulから、Remixのチュートリアルが公開されていたので試してみました。

今回は簡単なポートフォリオのウェブサイトを立ち上げてみます。

Remixって何?という方は弊社で記事がありますのでそちらをご参照ください↓

https://dev.classmethod.jp/articles/qucikstart-remix-framework/

1. Contentful側の準備

Contentful にアクセスし、アカウント作成 & 新規Space作成を終えたあと、

下記の情報をコピー&APIキーを作成しておきます。

  • Contentful Space ID
  • Delivery API Key … 公開された記事・アセットデータのみを取得する本番用API
  • Content Management Token … 書き込み権限を持つ最上位のAPI

注意点

Content Management Token を利用することからも分かる通り、本スターターではContentfulのスペースにコンテンツモデルや記事の追加を行います。

例えば Blog という被りそうな名称のコンテンツモデルを追加したりするため、既にContentfulでコンテンツを管理している方は、新規でspaceを立ち上げておいた方が無難です。Contentfulは無料プランで2つまでSpace作成ができます。

2. Remixアプリケーションの立ち上げ

ターミナルからContentfulのRemixスタックをクローンします。

% npx create-remix@latest --template https://github.com/contentful/starter-remix-portfolio

このスタックではデザインに Tailwind CSS、コンテンツの取得に Contentful GraphQL API を使用しています。

色々聞かれますので、順に入力していきます。

? Where would you like to create your app?
./contentful-sample

? TypeScript or JavaScript?
JavaScript (どっちでも可)

? Do you want me to run `npm install`?
Yes

? Enter you Contentful Space ID
(先ほど作成したSpace ID)

? Enter you Content Delivery API Key
(先ほど作成したDelivery API Key)

? Enter you Content Management Token
(先ほど作成したManagement Token = `CFPAT-`から始まる文字列)

無事Contentfulへのアップロードとアプリケーション作成が完了したら、Content Management Tokenはもう使わないので削除しても問題ありません。

3. development サーバーを立ち上げる

作成が完了したら、development サーバーを立ち上げます。

% cd contentful-sample
% npm run dev

localhost:3000にアクセスし、サイトが表示されていたらOKです。

Remix Tutorial

4. スタックの解説

上記を試してみました、だけではアレなので少し解説をしてみます。

Contentfulへの記事の追加

ContentfulではCLIからリソースのエクスポート&インポートが可能で、設定をjsonファイルで記述します。

例えばContentfulにシードデータを流し込みたい時に便利で、扱えるリソースは記事だけではなく、画像などのアセット、コンテンツモデル、Webhook、Role情報などもエクスポート&インポートできます。

本スターターでは実行と同時にContentfulへのデータインポート実行を行いますが、その設定がファイルが下記にあたります。

https://github.com/contentful/starter-remix-portfolio/blob/main/remix.init/contentful/export.json

上記は % npx create-remix した際に実行される初回処理(入力されたContentfulのトークンなどを .env に保存したりなど)の一部に組み込まれています。

Contentfulのデータ取得

Contentfulは REST と GraphQL の2つのAPIエンドポイントを持っています。

本スターターでは下記のファイルでapiCall()関数としてGraphQLエンドポイントにPOSTリクエストを送っています。

app/models/contentful.server.js

async function apiCall(query, variables) {
    const fetchUrl = `https://graphql.contentful.com/content/v1/spaces/${SPACE}/environments/master`;
    const options = {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            Authorization: `Bearer ${TOKEN}`,
        },
        body: JSON.stringify({ query, variables }),
    }
    return await fetch(fetchUrl, options)
}

上記ファイルで各コンテンツモデルに対するクエリを一括して記述し、 client 変数でexportしてます。

export const client = {getProjects, getTalks, getAllBlogs, getSingleBlog, getPage}

app/root.jsx

app/root.jsx はRemixにおけるページレンダリングの親(Remixいうところの"root route”)としてアプリケーション全体のレイアウトを行います。

Remixではディレクトリ/ファイル構成がそのままアプリケーションのURLになるため、例えば app/routes/about.jsxabout() の返り値は app/root.jsxOutlet 内でレンダリングされ、URL= /about としてアクセスできます。

各コンポーネントの役割は下記のようになります。

app/root.jsx

export default function App() {
  return (
    <html lang="en">
      <head>
        <Meta />{/* app/routes/ 内の MetaFunction の返り値をタグとして出力。なお meta はRoutesにて予約語のためシンプルに変数 meta としてexportもできる */}
        <Links />{/* app/routes/ 内の LinksFunction の返り値をタグとして出力 */}
      </head>
      <body>
        <NavBar />
        <main>
          <Outlet />{/* app/routes/ 内の各ページでexport defaultされた関数の返り値がレンダリングされる */}
        </main>
        <ScrollRestoration />
        <Scripts />{/* スクリプトがここに入る */}
        <LiveReload />{/* コード変更時に自動リロードを行うかどうかを定義。developmentのみで動作 */}
      </body>
    </html>
  );
}

詳細は → https://remix.run/docs/en/v1/api/remix#links-livereload-meta-scripts-scrollrestoration

以上の通り、ここでは全体をラップするレイアウトを担当するのみで直接Contentfulデータをレンダリングしません。次を見てみます。

app/routes 配下のファイルたち

Remixでは app/routes/ 配下に入ったファイルはRoutesとして、ファイル名をURLとしてそのままアクセスできるようになります。

例えばURL= /projects/ としてアクセスできる app/routes/projects.jsx を開いてみると、先ほどの app/models/contentful.server.js を通じて、Contentfulの projects モデルの記事全てと、ページ名 "Projects" をキーにページメタデータ(SEOメタデータなど)をContentfulから取得しているのがわかります。

import { client } from "../models/contentful.server";
...
export async function loader() {
    const projects = await client.getProjects();
    const page = await client.getPage("Projects")
    return json({projects, page});
}

なお、 loader()で取得したデータは useLoaderData がJSONデータとして返すようになります。

またリッチテキストの出力には @contentful/rich-text-types@contentful/rich-text-react-renderer を使っており、HTML変換時に自前の処理(HTMLタグの追加や置換)が可能です。GatsbyやNext.jsですでにContentfulに触っている人には馴染み深いと思います。

5. Netlifyにデプロイしてみる

Remix側でのデプロイ関連のファイルは下記2つになります。Remixではフロントエンドとバックエンドのコードを一箇所で記述できて楽ですね。

  • remix.config.js
  • server.js

(閑話休題)Remixプロジェクト作成時のデプロイ先選択について

Remixでは最初のプロジェクト作成時に選択したデプロイ先(eg. Netlify, Cloudflare Pages…)に従って上記サーバー関連ファイルの生成や関連パッケージのインストールを行います。

なお、本スターターはNetlify専用になっています。

(実はデプロイ先をNetlifyからCloduflare Pages用に変更しようとしたところ、Node.jsの基本ライブラリの置換やGraphQLへのPOSTリクエスト処理で詰まってしまい難儀なことになってしまったので、やはりRemixは最初からデプロイ先をある程度想定した開発が推奨なのだと思います)

Netlifyでプロジェクト作成

コードをリポジトリにPushiしたら、あとはNetlifyから新規プロジェクトを追加するのみです。

Netlifyでは自動的にRemixを認識してくれるため、基本はデフォルト設定のままで問題ありません。

Contentfulの環境変数のみ追加しておきます(ローカルに生成されている .env と同じもので大丈夫です)。

  • CONTENTFUL_SPACE_ID
  • CONTENTFUL_ACCESS_TOKEN

Netlifyにもデプロイできました! 非常に簡単でした。

Remix Tutorial

最後に

以上、Remix + ContentfulのWebアプリケーションを立ち上げ、Netlifyにデプロイしてみました。

次回はRemixスターターのクラスメソッド版(with Cloudflare Pages)を作ってみようかと思っています。

参考情報