【Next.js】Suspenseが効かない事象と解決方法

# はじめに
Next.jsにてデモサイトを作成中に、Suspenseが効かない問題が起き、
前に同様な現象があったのですが備忘録で残しておきたいと思います。

`Qiita`の記事をAPIを作成して取得しています。「もっとみる」ボタンを押下したら全記事取得するのですが、
Loading.tsxが出てきませんでした。`useState`を使えばすぐに終わるのですが、クライアントコンポーネントにするとNext.jsの良さが消えてしまうのではないかと言う気持ちと、Next.jsではpageコンポーネントはサーバーコンポーネントとして使用することがベストプラクティスとされていることから、できる限り`Suspense`を使い倒したかったのです。

実際に、Next.jsの公式チュートリアルで学習を行った時にはできる限り`use client`を使っていなかった様が気がします。

# 問題
では問題のコードを見てみましょう。

```typescript
export default async function Home({ searchParams }: { searchParams: { showAll?: string } }) {
  const API_URL = process.env.NEXT_PUBLIC_API_URL;
  const params = await Promise.resolve(searchParams);
  const showAllParam = params?.showAll;
  const shouldShowAll = showAllParam === "true";
  const limit = shouldShowAll ? 100 : 4;
  const response = await fetch(`${API_URL}/api/qiita?limit=${limit}`, { cache: "no-store" });
  const data = await response.json();

  return (
    <div className="p-4 bg-blue-100 min-h-screen pt-10 pb-20">
      <h2 className="text-2xl font-bold text-gray-800 mb-2">Qiitaの記事一覧</h2>
      <Suspense key={String(params)} fallback={<Loading />}>
        <ArticlesList articles={data} />
      </Suspense>
      {!shouldShowAll && <ShowMoreButton />}
    </div>
  );
}

```

まず、書いたコードの一部を解説します。
```typescript
<Suspense key={String(params)} fallback={<Loading />}>
```
keyに入れる値は、「この値が変わったら、fallbackのLoadingを呼びたい」っていう時に使うものっぽいです。

Reactのドキュメント上にも、
>トランジション中、React は既に表示されているコンテンツを隠さないようにします。しかし、異なるパラメータを持つルートに移動する場合、React にそれが異なるコンテンツであると伝えたいことがあります。これを表現するために、key が使えます。

と書いてありますね。

では`params`の値が変わったのに、なぜかわらなかったのか。
それは結構簡単なことでした。

# 解決方法
`ArticlesList`コンポーネントにデータを呼び出し、非同期にしてあげる。
たったこれだけです。

理由ですが、
React（Next.js）は、コンポーネントの中で Promise が発生したら一時停止（＝suspend）するという仕組みを持っています。

これを使って、

「データ読み込み中だな → fallback 表示しとこ！」

とやってくれるのが `Suspense`

つまり、
1.コンポーネントが同期で即座に描画できるなら → ローディング不要なので、fallback出す理由がない
2.コンポーネントが 非同期でまだ中身が来てない（Promise中） → 今表示できないから fallback 出す

ということですね。
今回の事象はこの1に当てはまっていたみたいです。

てなわけで、今回のコードはこちら。
```typescript:page.tsx
export default async function Home({ searchParams }: { searchParams: { showAll?: string } }) {
  const API_URL = process.env.NEXT_PUBLIC_API_URL;
  const params = await searchParams;
  const showAllParam = params?.showAll;
  const shouldShowAll = showAllParam === "true";

  return (
    <div className="p-4 pt-10 pb-20">
      <h2 className="text-2xl font-bold text-gray-800 mb-2">Qiitaの記事一覧</h2>
      <Suspense key={shouldShowAll ? "all" : "limited"} fallback={<Loading />}>
        <FeatureArticleList showAll={shouldShowAll} />
      </Suspense>
      {!shouldShowAll && <ShowMoreButton />}
    </div>
  );
}


```

```typescript:FeatureArticleList.tsx

import { ArticlesList } from "@/app/components/common/articlesList/ArticlesList";
export const FeatureArticleList = async ({ showAll }: { showAll: boolean }) => {
  const API_URL = process.env.NEXT_PUBLIC_API_URL;
  const limit = showAll ? 100 : 4;
  const response = await fetch(`${API_URL}/api/qiita?limit=${limit}`, { cache: "no-store" });
  const data = await response.json();
  return (
    <>
      <ArticlesList data={data} />
    </>
  );
};


```

`FeatureArticleList`を新たに作ってますが、別に前のコンポーネントでもいけます。

また、こういったSuspenseで呼び出す時にはスケルトンUIを使うことがUX上でいいと言われています。

# おわりに
`Suspense`の技術について改めて復習になりました

# 参考
https://ja.react.dev/reference/react/Suspense