React18のStreaming SSRを使ってNextJSのアプリケーションを部分的にHydrateさせてUXを向上させる

jpnykw

Haruto Hirakawa

Posted on November 2, 2022

React18のStreaming SSRを使ってNextJSのアプリケーションを部分的にHydrateさせてUXを向上させる

React16.6から追加され、いまだにexperimentalではある<Suspense />というコンポーネントがあります。React18から Streaming SSR が実装され、コンポーネントを非同期にレンダリングできるようになりました。それによってSSRを効かせたアプリケーションを部分的に読み込ませたままユーザーに返すことで実時間を短縮してユーザー体験を向上させよう!という話です。

SSRについて

そもそもSSRのデータフローについて軽く話しておきます。ここでは、レンダリングの際に何らかのWebAPIからデータをFetchするアプリケーションを想定します。

まずクライアントがURLにアクセスしてリクエストを飛ばします。リクエストを受け取ったサーバーはgetServerSidePropsを通してWebAPIからデータをFetchしてきます。データのFetchが完了するのを待った上でpropsが定義され、コンポーネントがレンダリングされます。

export async function getServerSideProps(context) {
  const data = await fetch('...')
  return {
    props: {
      // ...
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

レンダリングされたHTMLをクライアントに送り、JSを読み込み、最終的にHTMLにHydrateします。Hydrateが完了するとユーザーがWebページを操作できる状態になります。

さて、この段階で「待たなければいけない瞬間」が存在しています。データのFetch、JSの読み込み、Hydrate、これらの処理が実行されている間ユーザーは待つしかありません。フロント側でいくらチューニングされていても、呼び出したWebAPIが遅ければ、それだけでページのレスポンスが悪くなってしまいます。

Streaming SSR

目玉機能の登場です。こいつの登場によって、これらの「待たなければいけない瞬間」を有効活用できるようになります。

例えば、以下のようなコンポーネントが存在するとします。非同期にデータをfetchするコンポーネントCがある場合、fetchが完了するまで関係のないコンポーネント(A、B)も待つ必要がありました。しかしReact18からは以下のように<Suspense />コンポーネントでラップしたコンポーネントを除いたコンポーネントのHTMLが先にクライアントに返却されるようになります。代わりにfallbackpropsに渡したコンポーネントが描画されます。

import { Suspense } from 'react'

const Component = (props: Props) => {
  return (
    <Container>
      <A />
      <B />
      <Suspense fallback={<Loading />}>
        <C />
      </Suspense>
    </Container>
  )
}
Enter fullscreen mode Exit fullscreen mode

クライアントサイド側でコンポーネントCのデータFetchを行い、完了した時点でレンダリングされます。これによってAPIによるページ全体のレンダリングの遅延は解決できるようになりました。

Hydrate

しかしこの状態、表示はされますがページが操作可能な状態にはなっていないので、周りのUIはハリボテ状態になっています。しかし、React.lazyを組み合わせることで、Fetchが完了する前にコンポーネントCを待たずにHydrateさせることができます。(<Suspense />と組み合わせることでSSRでも効くように)

import { Suspense, lazy } from 'react'

const C = lazy(() => import('./C.tsx'))

const Component = (props: Props) => {
  return (
    <Container>
      <A />
      <B />
      <Suspense fallback={<Loading />}>
        <C />
      </Suspense>
    </Container>
  )
}
Enter fullscreen mode Exit fullscreen mode

うれしい!

💖 💪 🙅 🚩
jpnykw
Haruto Hirakawa

Posted on November 2, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related