RemixからmicroCMSのデータをフェッチする
Remixの素振りとして、microCMSのデータをフェッチして表示するところまでをやってみた。
microCMS側の操作説明は割愛する。ニュースのテンプレートを利用した。
Remixのインストール
ドキュメント通りにコマンドラインからインストールした。
npx create-remix@latest
ESLintや、TypeScriptのエイリアスの設定はデフォルトでやってくれていた。こちらでは何も設定せずにそのまま開発に入れる。
お知らせ一覧と詳細のページを作る
まずページを作ってみる。Remixにディレクトリ構造のルールは特に無いがドキュメントに記載してあった方法を採用する。
app
├── common
│ ├── libs
│ └── types.ts
├── entry.client.tsx
├── entry.server.tsx
├── root.tsx
├── routes
│ ├── _index.tsx
│ ├── news
│ ├── news.$id
│ └── news._index
└── tailwind.css
app/common
: 共通で利用するコンポーネントやその他のエトセトラのためのディレクトリ。コンポーネントやhooksなど。
routes以下の構造
routes
├── _index.tsx
├── news
│ ├── api.ts
│ └── route.tsx
├── news.$id
│ └── route.tsx
└── news._index
└── route.tsx
ディレクトリ内にroute.tsx
ファイルを置くと、そのファイルだけがページの表示のためのファイルとなる。その他のファイルはページ表示のためのファイルとならない。つまり、route.tsx
以外はコンポーネントファイルやhooksのファイルを置くことが可能となる(結構重要な話)。
URLが/news
, /news/hoge
などの場合は、まず news/route.tsx
が処理される。news/route.tsx
は以下のようになっている。
import { Outlet } from "@remix-run/react";
export default function News() {
return (
<div>
<h1>News</h1>
<Outlet />
</div>
);
}
ここでは「ガワ」としての機能しか持たない。
URLが/news
の場合の処理の実態は、news._index/route.tsx
に記述されている。
export default function NewsIndex() {
return (
<h2>News Index</h2>
);
}
詳細ページは news.$id/route.tsx
に書く。Remixの流儀として.
がURLの/
になる。.
の後ろに_
が先頭についた文字列はパス名からは除外される。
export default function NewsDetail() {
return (
<h2>News Index</h2>
);
}
microCMS SDKのインストールおよびクライアントの設定
microCMSからデータをフェッチするためにSDKを利用する。microcms-js-sdk
を package.json
に追加して、npm install
を実行する。
そしてクライアントを作る。microCMSのクライアントはnews以外にも利用するので、app/common/libs/microcmsClient.ts
として書く。
import { createClient } from "microcms-js-sdk";
const client = createClient({
serviceDomain: "xxx",
apiKey: "xxx",
});
export default client;
newsのapiを取得する処理は、/app/news/api.ts
に書いた。newsに関わることなのでファイルの設置場所はnewsにまとめるのだ。
Ruby on Rails方式で機能ごとにファイルをまとめるのはフロントエンド開発には向いていない。ドメイン別にまとめるのが良いと思うが、Remixのroute.tsxを利用すると、それが可能となる。
import microcmsClient from "~/common/libs/microcmsClient";
import { PagingResponse } from "~/common/types";
export interface News {
id: string;
createdAt: string;
updatedAt: string;
publishedAt: string;
revisedAt: string;
title: string;
content: string;
}
export type NewsResponse = PagingResponse<News>;
export async function getNews() {
return await microcmsClient.get<NewsResponse>({
endpoint: "news",
});
}
export async function getNewsById(id: string) {
return await microcmsClient.get<News>({
endpoint: `news/${id}`,
});
}
ページごとにデータの取得処理を記述する
ニュース一覧、ニュース詳細ページにデータをフェッチする処理を書いていく。
routes/news._index/route.tsx
import { json } from "@remix-run/node";
import { Link, useLoaderData } from "@remix-run/react";
import { getNews } from "~/routes/news/api";
export const loader = async () => {
const newsResponse = await getNews();
return json({ newsResponse });
};
export default function NewsIndex() {
const { newsResponse } = useLoaderData<typeof loader>();
const { contents: news } = newsResponse;
return (
<ul>
{news.map((post) => (
<li key={post.id}>
<Link to={`/news/${post.id}`}>{post.title}</Link>
</li>
))}
</ul>
);
}
routes/news.$id/route.tsx
import { LoaderFunctionArgs, json } from "@remix-run/node";
import invariant from "tiny-invariant";
import { getNewsById } from "../news/api";
import { useLoaderData } from "@remix-run/react";
export const loader = async ({ params }: LoaderFunctionArgs) => {
invariant(params.id, "Missing newsId param");
const news = await getNewsById(params.id);
if (!news) {
throw new Response("Not Found", { status: 404 });
}
return json(news);
};
export default function NewsDetail() {
const news = useLoaderData<typeof loader>();
return (
<div>
<h1>{news.title}</h1>
<div>
あいうえお
<div
className="prose"
dangerouslySetInnerHTML={{ __html: news.content }}
/>
</div>
</div>
);
}
tiny-invariant
は引数の値がfalsyの場合は例外をスローしてくれるライブラリ。
ハマったところ
const { newsResponse } = useLoaderData<typeof loader>();
当初、useLoaderDataの戻り値の型がanyになってしまう事象にかなりハマった。どうやら、バージョン2.9.0でのバグだと思われる。2.8.1にすると、ちゃんと指定した型が返された。
Remixは成長途中のライブラリなので、バージョンアップでこういうトラブルは多そう。安定したバージョンを利用するのが良いのか。
Discussion