💬

RemixからmicroCMSのデータをフェッチする

2024/04/24に公開

Remixの素振りとして、microCMSのデータをフェッチして表示するところまでをやってみた。
microCMS側の操作説明は割愛する。ニュースのテンプレートを利用した。

Remixのインストール

ドキュメント通りにコマンドラインからインストールした。

npx create-remix@latest

ESLintや、TypeScriptのエイリアスの設定はデフォルトでやってくれていた。こちらでは何も設定せずにそのまま開発に入れる。

お知らせ一覧と詳細のページを作る

まずページを作ってみる。Remixにディレクトリ構造のルールは特に無いがドキュメントに記載してあった方法を採用する。

Route File Naming | 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-sdkpackage.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