🗂

【Next.js × microCMS】SDKを使わずfetch関数で記事取得してみた

に公開

「fetch関数でAPI通信を行ったことがない...そうだ、microCMSの記事取得を自分でやってみよう!(やったことないけど)」
そう思い立って実装してみましたので、記事として残してみます。

筆者のmicroCMSのスキル感は、React/Next.js/microCMSで基本的なWEBサイトコンテンツなら実装できるけど、応用的なコンテンツ作成は未経験といったところです。
なので、間違っていたりする場合もあるかと思います。お気づきの際はぜひ教えてください!

想定している読者

  • microCMSのAPIをfetch関数で直接呼びたい
  • SDKに頼らず、自分でデータ取得〜描画までやってみたい

環境

  • Next.js 15
  • TypeScript
  • microCMS(SDKは使用しない)
  • Tailwind.css

まずは公式ドキュメントを確認してみる

https://document.microcms.io/content-api/introduction

こちらを要約すると、コンテンツAPIのURLはサービスIDを使用していて決まりがあって、APIキーを使って認証をしてねってことです。
なので、サービスIDとAPIキーが必要ってことですね。

APIの種類もいろいろありますが、今回は取得と表示のみなので、GETを使用します。

https://document.microcms.io/content-api/get-list-contents

GET /api/v1/{endpoint}/{content_id}を使用してリクエストを送ることでデータが取得できるみたいです。
{endpoint}は、作成したAPIのものを使用します。
{content_id}は、1投稿文のIDを渡せば取得できそうです。
今回は一覧表示の実装のみのため、割愛しますがダイナミックルーティングで記事IDを取得してエンドポイントの後に追加すれば取得はできそうですね!

そして、リクエストヘッダーにはX-MICROCMS-API-KEY(認証キー)を載せないといけないようですね。
そのほかのパラメータの細かい解説は割愛させていただきます!

ひとまず必要なものは確認できたので、実装してみましょう!

型定義を行う

https://document.microcms.io/content-api/get-api-field-responses

こちらのドキュメントと合わせて、公式のSDKを参考にさせていただきました。
fields?: string | string[]とかfilter?: string | string[]とかがわかりづらかった...)
TypeScriptの型定義の仕方と合わせてとても勉強になったと実感しています。

typesフォルダを作成し、その中にindex.tsを作成しておきます。

type/index.ts
// microCMSの型定義
// @see https://document.microcms.io/content-api/get-api-field-responses
export type MicroCMSConnect = {
  apiDomain: string;
  apiKey: string;
};

export type MicroCMSGetOptions = {
  endpoint: string;
  queries?: MicroCMSQueries;
};

// コンテンツ取得時のクエリパラメータの型定義
// @see https://document.microcms.io/content-api/get-list-contents#h929d25d495
export type MicroCMSQueries = {
  draftKey?: string;
  limit?: number;
  offset?:number;
  orders?: string;
  q?: string;
  fields?: string | string[];
  ids?: string;
  filters?: string | string[];
  depth?:  number;
  richEditorFormat?: "html" | "object";
};

// コンテンツ取得時のレスポンスボディの型定義
// @see https://document.microcms.io/content-api/get-list-contents#h8a4c320d89
export type MicroCMSResponseBody = {
  contents: MicroCMSContent[];
  totalCount: number;
  offset: number;
  limit: number;
};

// コンテンツIDの型定義
// @see https://document.microcms.io/manual/automatic-grant-fields
export type MicroCMSContentId = {
  id: string;
};

// コンテンツの日付の型定義
// @see https://document.microcms.io/manual/automatic-grant-fields
export type MicroCMSDate = {
  createdAt: string;
  updatedAt: string;
  publishedAt?: string;
  revisedAt?: string;
};

// コンテンツの型定義
// @see https://document.microcms.io/manual/automatic-grant-fields
export type MicroCMSContent = MicroCMSContentId & MicroCMSDate;

// コンテンツの画像の型定義
// @see https://document.microcms.io/content-api/get-api-field-responses#he02e86da57
export type MicroCMSImage = {
  url: string;
  width: number;
  height: number;
  alt?: string;
};

// お知らせコンテンツの型定義
export type News = {
  title: string,
  description: string,
  content: string,
  thumbnail?: MicroCMSImage,
} & MicroCMSContent;

型定義はこちらで完了です。
最終的にSDKと同じ形になりました。
microCMSの記事はなんでもいいですが、お知らせやブログの形式にしておくとわかりやすいかと思います。

fetchして記事を取得する

では実際に、型定義を活かして fetch 関数でデータを取得する処理を書いてみましょう。
取得したデータをconsole logでデータを確認します。
MICROCMS_API_KEY,MICROCMS_SERVICE_DMAIN,ENDPOINTの3か所はご自身のものと合わせて変更してください。
自分用のテストならいいですが、公開予定があるもので使用する場合は.env.localなどで管理してください!

page.tsx

const getNewsList = async () => {
// MICROCMS_API_KEY,MICROCMS_SERVICE_DMAIN,ENDPOINTの3点は変更してください。
  const headers = {
    "Content-Type": "application/json",
    "X-MICROCMS-API-KEY": "MICROCMS_API_KEY",
  };
  const url = `https://MICROCMS_SERVICE_DMAIN.microcms.io/api/v1/ENDPOINT`;
  try {
    const response = await fetch(url, {
      headers: headers,
    });
    if (!response.ok) {
      throw new Error(`HTTPエラー: ${response.status}`);
    }
    const data = await response.json();
    return data;
  } catch (error: unknown) {
    if (error) {
      const message =
        error instanceof Error
          ? `APIリクエスト中にエラーが発生しました: ${error.message}`
          : "APIリクエスト中にエラーが発生しました。";
      throw new Error(message);
    }
  }
};
const articles = await getNewsList();
console.log(articles);

export default function Home() {}

記事の取得が確認できたと思います!
確認ができない場合は、MICROCMS_API_KEY,MICROCMS_SERVICE_DMAIN,ENDPOINTの3点が正しく設定されているか、確認してみてください!

記事を表示してスタイルしてみる

最後に取得した記事を表示させて、スタイルを当てたら完成です!
サンプルはこちらに置いておきます。

page.tsx
const articles = await getNewsList();
console.log(articles);

export default function Home() {
  return (
    <section className="py-16 md:py-24 lg:py-32">
      <div className="inner">
        <hgroup className="title mb-10 md:mb-16 lg:mb-20">
          <h2>お知らせ</h2>
          <p>News</p>
        </hgroup>
        <p className="text-base text-center leading-loose">
          最新情報をお届けします。
        </p>
        {articles.contents.length === 0 ? (
          <p>投稿がありません</p>
        ) : (
          <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-6 lg:gap-8 mt-10">
            {articles.contents.map((article: News) => (
              <article key={article.id}>
                <a
                  href={`/news/${article.id}`}
                  className="py-6 lg:py-8 px-5 bg-white rounded-xl shadow-lg mb-4 hover:bg-green-50 hover:shadow-sm transition-all duration-300 block"
                >
                  <header className="mb-2 md:mb-3 lg:mb-4">
                    <p className="text-xs text-right text-gray-500 mb-2 lg:mb-3">
                      <time dateTime={article.publishedAt ?? article.createdAt}>
                        {article.publishedAt ?? article.createdAt}
                      </time>
                    </p>
                    <figure className="mb-4 aspect-[3/2] rounded-lg overflow-hidden">
                      {article.thumbnail ? (
                        <Image
                          src={article.thumbnail.url}
                          width={article.thumbnail.width}
                          height={article.thumbnail.height}
                          alt={
                            article.thumbnail.alt ? article.thumbnail.alt : ""
                          }
                          className="w-full h-full object-cover rounded-lg"
                        />
                      ) : (
                        <Image
                          src="./images/no-image.jpg"
                          width={600}
                          height={400}
                          alt="noimage"
                          className="w-full h-full object-cover rounded-lg"
                        />
                      )}
                    </figure>
                  </header>
                  <h3 className="text-md font-bold mb-2 line-clamp-1">
                    {article.title}
                  </h3>
                  <p className="text-sm line-clamp-2">{article.description}</p>
                </a>
              </article>
            ))}
          </div>
        )}
      </div>
    </section>
  );
}

公開リポジトリ

今まで紹介してきたコードでも動作自体は確認できますが、このままでは扱いにくかったりAPIキーが露出してしまったりするので、コード整理したものを置いておきます。
クローン出来るので、よかったら参考までにご覧ください。
一部、関係ないコードが含まれていますが、その辺りはご了承ください...!

導入についてはREADMEを参照していただけると幸いです。

https://github.com/kaze-wind-dev/micro-motion-case

参考となるフォルダは以下の通りです。

-types
-libs
-components/ArticleCard
-components/NewsCardList

補足
一部の構造(型定義やAPI呼び出しの形)は、microCMS公式SDKやそのドキュメントを参考にしています。
特に理解が難しかった部分は、SDKのコードを読みながら再現を試みました。
自力実装を通じて、microCMSの設計やAPIの挙動を学ぶことを目的としています。

最後に

microCMS × fetch関数でAPIデータ取得から表示まで実装してみました。
私自身、完全に理解できたとは言えませんが、だいぶ理解が進んだ感覚が得られました。

公式のSDKは100件までしか取得できず、101件以上取得するとなるとfetchを使用する方法が紹介されていたりします。(101件以上取得したいケースはなかなか考えにくいとは思いますが...)
なので、fetchを使用することもきっとあるはずです。

少しばかりですがコンテンツAPIやfetch実装の理解の手助けになれば幸いです。
最後までお付き合いいただきありがとうございました!

Discussion