テクノロジー
Remix + microCMSでブログサイト構築してみた
2024-08-16T16:17:59.483Zに公開
2024-09-08T12:39:43.899Z
#Remix
#microCMS
<h2 id="h8d027c8ed3">はじめに</h2><p>普段はNext.jsで開発することが多かった私ですが、一休.comさんがRemixを採用しシステムリプレイスを進めているテックブログを読んでからはすっかりRemixに興味深々になってしまいました。</p><p>Next.jsを使っていて特に不満な点はなかったんですが、Remixを使い始めるとその思想が特に素晴らしく、もうRemix以外考えられないような身体になってしまいました…。</p><p>今回はRemixとはどんなフレームワークなのかを解説しながら、私の初Remix製プロダクトである当ブログをどのように構築したのかをご紹介します!</p><p>CMSにはヘッドレスCMSのmicroCMSを採用しておりますので、Remixとどう組み合わせて使えるか気になる方もぜひチェックしてください!</p><h2 id="hb0d2b0e65b">Remixとは</h2><p>Remixは、ReactベースのフルスタックWEBフレームワークです。</p><p><a href="https://remix.run/">https://remix.run/</a></p><p>ReactのみでWEBアプリケーションを構築するのと比べて設定やルーティングをフレームワークがよしなにやってくれたり、クライアントサイドレンダリング(CSR)のみでなくサーバーサイドレンダリング(SSR)も取り入れることができるのでアプリケーションの性能を向上させることができます。</p><p>似たような技術にNext.jsが存在しますが、Next.jsとの比較は以下のようになります。</p><ul><li>Next.jsはフレームワーク依存のノウハウが多いのに対し、RemixはWeb標準のAPIを使う場面が多いので学習コストが低い</li><li>Next.jsアプリケーションはVercelへのデプロイが前提になっているのに対し、RemixはJavaScript実行環境であればどこでも動作するためCloudflare等の選択肢も増える</li><li>Next.jsは便利である一方ブラックボックスな実装が多く、特にfetchAPIの自動キャッシュは分かりにくいが、Remixにそういったブラックボックスな箇所は少ない</li><li>Next.jsはバージョンアップのたびにアプリケーションコードの書き換えが必要になる場面が多いが、Remixはフレームワークに依存させない実装ができるため、アップデートにも追従しやすそう</li></ul><p>私がRemixに気持ちが傾いているため、主観的な内容が強めですが、RemixとNext.jsを比較するとこのようになります。</p><p>フロントエンド界隈の技術の流れは早いと言われますが、技術の移り変わりに耐えられるようなプロダクトを作る、という視点でも技術を選定するのが良いと思いますし、そうなると必然的にRemixが大きな選択肢として上がってくるのかなと思います。</p><h2 id="h7f0f3e295c">microCMSとは</h2><p>microCMSは、microCMS社が提供する日本製のヘッドレスCMSです。</p><p><a href="https://microcms.io/">https://microcms.io/</a></p><p>私が現在本業としてジョインしているプロジェクトでもtoB向けのCMSを作っていることもあり、日頃から様々なCMSを見たり触ったりしているんですが、個人的に最も推しているCMSです。</p><p>個人で使用する分にはほぼ無料で使えますし、リッチテキストエディタもすごく使いやすい。公式のSDKも使いやすく、APIのレスポンスもわかりやすいのでアプリケーションに組み込みやすいです。</p><p>日本製ということもあり謎の安心感もあります(笑)</p><p>他の選択肢としては<a href="https://www.contentful.com/" target="_blank" rel="noopener noreferrer nofollow">Contentful</a>や<a href="https://www.newt.so/" target="_blank" rel="noopener noreferrer nofollow">Newt</a>もあり、実際に組み込んでみて試したりしたんですが、最終的にはmicroCMSに落ち着きました。</p><h2 id="hcd52c12a97">Remix + microCMSでブログサイトを構築する</h2><h3 id="hfa16a89870">Remixプロジェクトを作成</h3><p>まずは下記コマンドでRemixプロジェクトを作成しましょう。</p><div data-filename="terminal"><pre><code class="language-shell">$ npx create-remix@latest</code></pre></div><p>細かい設定はRemixの公式ドキュメントのクイックスタート等を見ていただきながら進めてください。</p><p><a href="https://remix.run/docs/en/main/start/quickstart">https://remix.run/docs/en/main/start/quickstart</a></p><h3 id="hf1a4da0a35">microCMSでサービスとAPIを作成</h3><p>microCMSにアカウント登録し、サービス、APIをそれぞれ作成する必要があります。</p><p>詳細な手順はmicroCMSの公式ドキュメントを参照いただければと思います。</p><p><a href="https://document.microcms.io/manual/getting-started">https://document.microcms.io/manual/getting-started</a></p><h3 id="h9feda461a1">Remixアプリケーションからコンテンツを取得し表示する</h3><p>ここからは実際にRemixアプリケーションからmicroCMSのデータを取得し表示できるようにしていきます。</p><p>microCMSのデータを取得するには単純にfetchAPIを使用してもいいんですが、公式がSDKを用意してくれているので使っていきましょう。</p><div data-filename="terminal"><pre><code class="language-shell">$ npm i microcms-js-sdk</code></pre></div><p>microCMSの公式ドキュメントではmicroCMS Clientを<code>app/utils/client.server.ts</code>に配置するよう解説されています。</p><p>ホスティング先のJSランタイムがnode.jsの場合は公式ドキュメント通りで問題ありませんが、今回私はCloudflare Pagesにデプロイすることを想定しているため一工夫必要です。</p><p>ランタイムがCloudflareの場合、node.jsで使えるようなprocess.envが使えず、loader関数のcontextとして受け取り、<code>context.cloudflare.env.${ENV_NAME}</code>のような形式で環境変数を抽出しなければなりません。</p><p>なので今回は、各ページのloader関数ないでクライアントを生成し、microCMS APIにリクエストを送るような形で実装しました。</p><p>下記が一覧を表示するtsxファイルの例です。</p><div data-filename="app/routes/articles.tsx"><pre><code class="language-typescript">import { LoaderFunctionArgs, MetaFunction } from '@remix-run/cloudflare'
import { createClient } from 'microcms-js-sdk'
import { typedjson, useTypedLoaderData } from 'remix-typedjson'
import { ContentCard } from '~/components/content-card'
// メタデータを記述しています
export const meta: MetaFunction = () => {
return [
{ title: 'テクノロジー | キッサカタダ' },
{
name: 'description',
content:
'キッサカタダへようこそ。マスター兼ソフトウェアエンジニアのカタダが技術記事を綴ります。',
},
]
}
// ページ内でレンダリングするデータをfetchするloader関数です
export const loader = async ({ context }: LoaderFunctionArgs) => {
// microCMS Clientを生成
const client = createClient({
serviceDomain: context.cloudflare.env.MICROCMS_SERVICE_DOMAIN,
apiKey: context.cloudflare.env.MICROCMS_API_KEY,
})
// microCMS APIに一覧取得のリクエストを送信しています
// queriesオプションにorderを渡しており、古いデータから順番に取得できるようにしています
const response = await client.getList<Blog>({
endpoint: 'blogs',
queries: {
orders: '-createdAt',
},
})
// ビルトインのjsonメソッドではなく、typedjsonを使用しています
return typedjson({ response })
}
export default function Technology() {
// ビルトインのuseLoaderDataではなく、useTYpedLoaderDataを使用しています
const { response } = useTypedLoaderData<typeof loader>()
const { contents } = response
return (
<div className='container mx-auto w-full max-w-[1120px] py-8 md:py-10'>
<div className='mb-10 space-y-4'>
<h2 className='text-center text-md md:text-xl font-semibold'>
テクノロジー
</h2>
<p className='text-center text-sm md:text-md'>
フロントエンド成分おおめ
</p>
</div>
{contents.length > 0 ? (
<div className='grid lg:grid-cols-3 gap-8 justify-center'>
{contents.map((content) => (
<ContentCard key={content.id} content={content} />
))}
</div>
) : (
<div>
<p className='text-muted-foreground text-center'>
記事が見つかりません
</p>
</div>
)}
</div>
)
}</code></pre></div><p>microCMS APIのレスポンスの型定義も別ファイルで下記のように定義しています。</p><p>(そのうち型を自動生成できるような機能をプルリク投げたいな。。)</p><div data-filename="app/types.ts"><pre><code class="language-typescript">interface Blog {
id: string
createdAt: string
updatedAt: string
publishedAt: string
revisedAt: string
title: string
content: string
eyecatch: {
url: string
height: number
width: number
}
category: Category
tags: Tag[]
}
interface Category {
id: string
createdAt: string
updatedAt: string
publishedAt: string
revisedAt: string
name: string
slug: string
}
interface Tag {
id: string
createdAt: string
updatedAt: string
publishedAt: string
revisedAt: string
name: string
}</code></pre></div><p>ここで定義したinterfaceを、SDKの呼び出し部分に渡してあげることでレスポンスの型情報が補完されます。</p><div data-filename="app/routes/articles.tsx"><pre><code class="language-typescript">// await client.getList()だとレスポンスの型補完がされない
const response = await client.getList<Blog>({
endpoint: 'blogs',
queries: {
orders: '-createdAt',
filters: 'category[equals]7m4n94d190',
},
})</code></pre></div><p>余談ですが、loader関数で取得したデータをクライアントに渡す際、Remixビルトインのjson関数ではなく<a href="https://github.com/kiliman/remix-typedjson" target="_blank" rel="noopener noreferrer nofollow">remix-typedjson</a>というライブラリを使用しています。</p><p>ビルトインのjson関数だと、クライアントに渡される型情報がBlog型ではなく、JsonifyObject型となってしまうため、せっかく定義した型定義を使うことができませんでした。</p><p>この辺りの挙動は詳しく調べられていないので、また後日記事にするとして、一旦はremix-tyedjsonライブラリを使うことで解決できたのでよしとします。</p><p>ただ、公式のライブラリではないため、なるべく外部依存しない形で解決できないかは模索しています。</p><h2 id="h9be0c3393d">おわりに</h2><p>他にもGoogle Fontの最適化や、Google Analytictの設定など、実運用させるために必要な実装も行ったので、それはまた別記事にまとめられたらと思います。</p><p>ずっと自分のブログサイトを持ちたいと思っていたので、Remixのキャッチアップがてらいい成果物ができて大満足です(笑)</p><p>最近はエンジニアリングだけでなくSEOにも興味が出ているので、このブログで色々と試しつつ運用できていけたらなと思います。</p><p>こういったフロントエンド寄りの技術記事だけでなく、元バリスタの経験を活かしたコーヒー系の記事や、ガジェオタとして書くガジェット紹介記事も更新していければと思いますので、興味のある方は覗いていってください。</p><h2 id="h44e51f96ce">参考資料</h2><p><a href="https://user-first.ikyu.co.jp/entry/2023/12/15/093427">https://user-first.ikyu.co.jp/entry/2023/12/15/093427</a></p>