https://zenn.dev/sora_kumo/articles/46a87f1bde207c

SuspenseとSSR動作に関して

React18.3以降の新機能useを使うと、throw promiseとデータの取得部分の処理が簡略化出来ます。そしてuseを使ったコンポーネントは非同期中の状態を受け取るためSuspenseとセットで使われることが想定されます。しかしNext.jsで使う場合、必ずしもセットにしなくても動作します。その場合は異なった挙動をするので、違いを確認するためNext.js 13の新機能app/ Directory (beta)を使って出力結果を検証します。ちなみにReact 18.2やNext.js 12以前のバージョンでも同様の動作をします。最新版で固めたのは、新機能が非同期に最適化されているため、コードの記述量が減るからです。

Next.jsの初期設定

TypeScriptや@types/nodeは自動で入ります。

// @ts-check
/**
 * @type { import("next").NextConfig}
 */
const config = {
  reactStrictMode: true,
  experimental: {
    appDir: true
  },
};
module.exports = config;

ちなみにStreaming-SSRをするためにruntime: "experimental-edge"の設定は不要です。appフォルダの機能を使うと自動的にStreamingが有効になります。

サンプルソース

動作確認用リンクです。完全にページ遷移する必要があるのでaタグでリンクを張っています。

const Page = () => {
  return (
    <>
      <div>
        <a href="<https://github.com/SoraKumo001/next-layouts-test>">Source code</a>
      </div>
      <hr />
      <div>
        <a href="/suspense">Suspense有り</a>
      </div>
      <div>
        <a href="/nosuspense">Suspense無し</a>
      </div>
    </>
  );
};
export default Page;

1秒のウエイトが入った状態で東京の天気予報を取得します。 Streamingが有効になっているので初期状態でLoadingが表示され、その後、天気予報に切り替わります。

import React, { Suspense, use } from "react";

export interface WeatherType {
  publishingOffice: string;
  reportDatetime: string;
  targetArea: string;
  headlineText: string;
  text: string;
}

const fetchWeather = (id: number): Promise<WeatherType> =>
  fetch(`https://www.jma.go.jp/bosai/forecast/data/overview_forecast/${id}.json`)
    .then((r) => r.json())
    .then(
      //ウエイト追加
      (r) => new Promise((resolve) => setTimeout(() => resolve(r), 1000))
    );

const Weather = () => {
  //Reactの新機能useでデータを取り出す
  const weather = use(fetchWeather(130000));
  return (
    <div>
      <h1>{weather.targetArea}</h1>
      <div>
        {new Date(weather.reportDatetime).toLocaleString('ja-JP', {
          timeZone: 'JST',
          year: 'numeric',
          month: 'narrow',
          day: 'numeric',
          hour: 'numeric',
          minute: 'numeric',
        })}
      </div>
      <div>{weather.headlineText}</div>
      <pre>{weather.text}</pre>
    </div>
  );
};

const Page = () => {
  return (
    <Suspense fallback='Loading'>
      <Weather />
    </Suspense>
  );
};
export default Page;

1秒のウエイトが入った状態で東京の天気予報を取得します。 こちらはSuspenseを入れていません。 Suspenseが無い場合はStreamingが行われず、データが出そろうまで待機状態になります。