Lesson 6

各レンダリング方式ごとのデータ取得方法

Lesson 6 Chapter 1
CSRでのデータ取得

Next.jsでのデータ取得

本レッスンではNext.jsでの外部データ取得の方法について学んでいきます。 Next.jsでの外部データ取得方法についてはレッスン1で簡単に学びましたが、もう少し深堀して学んでいきます。

SWR を使用したクライアント側のデータ取得

本チャプターではCSRの中でもSWRというライブラリを使用した際のクライアント側のデータ取得の流れについて見ていきます。 SWRという名前はstale-while-revalidateというデータ取得の方針の名前に由来しています。 stale-while-revalidateの方針でデータ取得を行う場合、まずは既存のキャッシュ(一度取得したデータを保存したもの)を返却して、 その後、バックエンドと通信を行って、既存のキャッシュと取得したデータに違いがないかを検証します。 キャッシュを利用して可能な限り素早くレスポンスを返すことと、可能な限りキャッシュを新しい状態に保つことを両立した方針と言えます。 ライブラリとしてのSWRは、このstale-while-revalidateの方針による通信をより簡単に利用するために作られました。 nextjs-6-1 stale-while-revalidateの流れ

useEffectとSWR

それではuseEffectを使用した際とSWRライブラリを使用した際のデータ取得のコードを見ていきましょう。

useEffectを使用したサンプルコード
const App = () => {
  const [user, setUser] = useState(null); // (2)
  const [doRefetch, setDoRefetch] = useState(false); // (1)

  useEffect(() => {
    // レンダリング後にdoRefetchがtrueだったら通信する
    if (doRefetch) {
      fetch('/api/user')
        .then(res => res.json())
        .then(data => {
          setDoRefetch(false);
          setUser(data); // 読み込みが完了したらデータをセットする
        });
    }
  }, [doRefetch]);

  if (user === null) {
    // データがまだない場合は読み込み中のUIを表示する
    return <Loading />;
  }

  return (
    <div>
      <Header user={user} />
      <Content user={user} />
    </div>
  );
}

キャッシュを更新したくなったら(1)で定義した関数でsetDoRefetch(true)を実行すればOKです。 これで、ブラウザがページをリロードするまでは、(2)に保持したユーザー情報が維持されます。 上記はサンプルコードでしたが、実用する場合は次のような課題を解決する必要がありそうです。

  • 通信先が増えると実装量が大幅に増えるので簡素にしたい
  • キャッシュの更新タイミングを適切に制御したい
  • 通信エラーをハンドリングしたい
  • こういった問題を解決できるのが、SWRライブラリになります。それではSWRライブラリを使用したコードを見ていきましょう。

    SWRを利用した通信処理のサンプルコード
    import useSWR from 'swr';
    
    const fetcher = (...args) => fetch(...args).then(res => res.json()); // (2)
    
    function App() {
      const { data, error } = useSWR('/api/user', fetcher); // (1)
    
      if (data === null) {
        // データがまだない場合は読み込み中のUIを表示する
        return <Loading />;
      }
    
      return (
        <div>
          <Header user={data} />
          <Content user={data} />
        </div>
      );
    }

    非同期処理や通信結果の保持に関する処理が(1)にまとまって、見通しがよくなりました。 最終的な通信処理はSWRの機能ではなく(2)で設定してあるようにfetch関数を使っているので、 通信方法自体のカスタムもとてもしやすくなっています。 SWRの役割は、あくまでも非同期処理の状態管理やキャッシュの管理なのです。 このようにSWRライブラリはCSRでデータ取得を実装する際にとても便利なライブラリとなっているので 是非活用してみてください。

    Lesson 6 Chapter 2
    SSRでのデータ取得

    Pre-rendering

    前チャプターではCSRでSWRライブラリを使用したデータ取得方法を見ていきました。
    少しCSRのデメリットとレンダリングについておさらいします。 CSRで作成したSPAページをロードする時、まず空のhtmlを読み込んでJSファイルも読み込んでそのJSが画面をレンダリングします。 この方法では SEO に弱くなったり初期表示が遅くなったりする問題があります。 この問題を解決するため、Next.jsでは基本的にすべてのページをサーバ側でレンダリングします。(Pre-renderingと言います) このPre-renderingには大きくわけて下記の3種類があります。

  • SSR(Server-side Rendering)
  • SSG(Static Site Generation)
  • ISR(Incremental Static Regeneration)
  • 本チャプターではSSRのデータ取得方法について学びます。

    getServerSidePropsとは

    SSRではgetServerSidePropsという仕組みを使用してサーバ側で外部のデータを取得し、
    Pre-renderingします。 CSRでいうSWRライブラリの様なものの認識で結構です。SSRはCSRと同様にクライアントからのリクエスト毎にレンダリングされるのが特徴です。 ただ異なるのがCSRはクライアント側、SSRはサーバ側でレンダリングされます。厳密にはもう少し異なる点はありますが、今はこの様な考え方で大丈夫です。 またSSRはリクエスト毎にレンダリングされるのでページが表示させるまでに時間はかかりますが最新の情報を表示することができます。
    本チャプターではgetServerSidePropsを用いて外部から取得したデータを利用してブラウザ上に表示させる方法を確認していきます。 データの取得には、「JSONPlaceholder」というサービスを利用します。 「JSONPslaceholder」を利用するとhttps://jsonplaceholder.typicode.com/postsにアクセスするだけで100件のデータを取得することができます。

    JSONPlaceholder

    JSONPlaceholderへはブラウザから直接アクセスしてもデータを確認することができるので どのようなデータが取得できるのか確認したい場合は先ほど記述したURLを利用してブラウザでアクセスを行ってみてください。

    getServerSidePropsを用いたデータ取得

    getServerSidePropsを利用してデータを取得しページに表示させるために新たにpostsディレクトリを作成してindex.jsxファイルを作成します。 (以降本レッスンではビルド時のtypescript型定義エラー回避のため拡張子を.jsxにして作成していきます。)

    作成したindex.jsxファイルではgetServerSideProps関数の中でJSONPlaceholderからfetch関数を利用してデータの取得を行っています。 まずは正しくデータの取得ができているか確認するためにconsole.logを利用します。

    posts/index.jsx
    const index = ({ posts }) => {
      return (
        <>
          <h1>POST一覧</h1>
        </>
      );
    }
    
    export const getServerSideProps = async() => {
      const res = await fetch(`https://jsonplaceholder.typicode.com/posts`);
      const posts = await res.json();
      console.log(posts);
      return { props: { posts } };
    }
    
    export default index;

    通常のJavaScriptであればコード中にconsole.logを記述するとブラウザのデベロッパーツールのコンソールログに情報が出力されます。 しかし、getServerSidePropsはサーバ側(Next.js)で実行されるため、 npm run devを実行したターミナルに下記の様にpostsの100件分のデータが表示されることが確認できます。

    データが取得できることが確認できたのであとはindex関数に渡したpropsをmap関数で展開します。

    posts/index.jsx
    const index = ({ posts }) => {
      return (
        <>
          <h1>POST一覧</h1>
          <ul>
            {posts.map((post) => {
              return <li key={post.id}>{post.title}</li>;
            })}
          </ul>
        </>
      );
    }
    
    export const getServerSideProps = async() => {
      const res = await fetch(`https://jsonplaceholder.typicode.com/posts`);
      const posts = await res.json();
      //console.log(posts);
      return { props: { posts } };
    }
    
    export default index;

    ブラウザで確認するとgetServerSidePropsを利用して取得したデータがブラウザ上に表示されます。

    nextjs-6-3

    getServerSidePropsの実行場所

    getServerSidePropsはページコンポーネントでは実行することができますがページコンポーネント以外の通常のコンポーネント内では実行することができません。 取得データを使用したい場合ページコンポーネントでデータの取得を行い、propsで他のコンポーネントに取得したデータを渡すことになります。

    SSRを使用する場面

    SSRはユーザーのリクエストなしでは Pre-rendering できないページ、最新状態を維持する必要があるページに向いてます。 SNS のタイムラインページを思い出してみましょう。タイムラインはユーザーによってデータが代わり画面も変わるのでレンダリングの前にユーザー情報を読み込む必要があります。 ですので、リクエストなしではレンダリングできません。 つまり、タイムラインやマイページなど接続するユーザーによって変わる画面は SSR が適切です。 そしてTwitterを考えてみると何か呟いた時にすぐタイムラインに反映されますね。 ページを常に最新状態に維持してるということです。 SSRは常にデータを更新できるので、こういうページに向いてます。

    本チャプターではgetServerSidePropsを用いてリクエスト毎に外部から取得したデータを利用してブラウザ上に表示させる方法を学びました。 次のチャプターではビルド時に一度のみデータを取得するSSGのgetStaticPropsというしくみについて学んでいきます。

    Lesson 6 Chapter 3
    SSGでのデータ取得

    SSGのデータ取得

    前チャプターではSSRでgetServerSidePropsを使用したデータ取得方法を見ていきました。 本チャプターではSSG(Static Site Generation)でのデータ取得方法について学び、SSRとの違いを見ていきましょう。

    getStaticPropsとは

    SSRではgetStaticPropsという仕組みを使用してサーバ側で外部のデータを取得し、Pre-renderingします。 SSRのgetServerSidePropsとサーバ側で外部データ取得処理が実行されるという点は同じですが、 異なるのがPre-renderingが実行されるタイミングです。 SSRはクライアント側からのリクエスト毎にレンダリングされますが、SSGはサーバ側でビルド時に一度のみレンダリングされます。 その為初回ビルド後レンダリング処理が行われないのでページ表示速度が非常に速いことが特徴です。 それではgetStaticPropsを用いて「JSONPslaceholder」から取得したデータを利用してブラウザ上に表示させる方法を確認していきます。

    getStaticPropsを用いたデータ取得

    getStaticPropsを用いたデータ取得方法はとても簡単でデータ取得処理関数名をgetServerSidePropsからgetStaticPropsに変更するのみです。

    pages/posts/index.jsxをコピーしてssg.jsx というファイル名に変更し、下記のコードに変更して見ましょう。

    posts/ssg.jsx
    const index = ({ posts }) => {
      return (
        <>
          <h1>POST一覧</h1>
          <ul>
            {posts.map((post) => {
              return <li key={post.id}>{post.title}</li>;
            })}
          </ul>
        </>
      );
    }
    
    // export const getServerSideProps = async() => {
    export const getStaticProps = async() => {
      const res = await fetch(`https://jsonplaceholder.typicode.com/posts`);
      const posts = await res.json();
      return { props: { posts } };
    }
    
    export default index;

    getStaticPropsではビルド時に静的なページが作成されるので実際に npm run buildを実行します。
    nextjs-6-4 上記の様にメッセージログが出力されinfo - Generating static pages (10/10) の行で静的ページが生成されていることがリアルタイムで確認することができます。
    再度npm run devを実行しデータを表示させたページを見てみましょう。 ビルド時に生成された静的ページは.next/server/pages/postsの中に作成されています。 nextjs-6-5
    また同ディレクトリ内にssg.jsonファイルがありますが、こちらが「JsonPlaceHolder」から取得したデータになります。
    実際にクライアント側に表示するのは.next/server/pages/postsの中のファイルになります。 この事からSSGがビルド時にレンダリングしてページを作成・表示しリクエスト毎にレンダリングしないことがわかりますね。

    SSGを使用する場面

    前チャプターでSSG はビルド時に1回だけデータを読み込むと説明しました。これはビルド時1回だけデータを取得して、 ビルドされた後に取得元のデータが変更されても次にビルドをするまで変更を反映できないという意味でもあります。 ですので、SSGはデータの1分1秒迄のリアルタイム性を問われない以下の様なページに向いてます。

  • ブログ記事
  • ECサイト商品リスト
  • ヘルプページ・ドキュメント
  • 速度の面でもSSRはすべてのリクエストの度に処理が走るので、ビルド時に生成したページをキャッシングして再利用するSSGより遅いことは当然です。 この様にSSR、SSG共に一長一短なデータ取得方法と言えます。この2つのデータ取得方法を関数名の変更のみで実装できるのがNext.jsの強みです。

    本チャプターではgetStaticPropsを用いてビルド時に1回のみ外部から取得したデータを利用してブラウザ上に表示させる方法を学びました。 次のチャプターではSSGの短所を補うISRというレンダリングの仕組みについて学んでいきます。

    Lesson 6 Chapter 4
    ISRでのデータ取得

    SSGのメリット・デメリット

    チャプター3ではビルド以降レンダリングを実施しないSSGという仕組みを学びました。 これによりSSG使用しているページでは、アクセス毎にサーバサイドでレンダリングを実施するSSRに比べ高速な描画が実現できます。 ですが、SSGはのレンダリングはアクセスよりもはるか前、デプロイ前のビルド時に返却するファイルを生成します。 その為リアルタイムで最新情報を取得して表示することを苦手としています。 記述内容が変わることのないページや、コードとともに掲載内容を管理している記事ページなどでは有効ですが、 チケット販売や天気情報などのリアルタイム性が必要なもの、Headless CMSを用いて記事を管理しているケースでは使用することができません。 これを改善するための機能が、ISRです。

    ISR(Incremental Static Regeneration)

    ISRはインクリメンタル静的再生成という手法を指します。 基本的にはSSGの挙動と同じなのですが、クライアント側のリクエストに対しビルド時に生成された静的ページを返し、 尚且、バックグラウンドで一定期間ごとに静的ページの再生成をサーバー側で行うといったものです。 これを用いることで、天気情報サイトなどの変更頻度が低いページで気軽にSSGすることが可能になりました。

    ISRを用いたデータ取得の実装

    それではISRを用いたデータ取得を実装していきましょう。pages/posts/ssg.jsxをコピーしてisr.jsx というファイル名に変更し、下記のコードに変更して見ましょう。

    posts/isr.jsx
    const index = ({ posts }) => {
      return (
        <>
          <h1>POST一覧</h1>
          <ul>
            {posts.map((post) => {
              return <li key={post.id}>{post.title}</li>;
            })}
          </ul>
        </>
      );
    }
    
    // export const getServerSideProps = async() => {
    export const getStaticProps = async() => {
      const res = await fetch(`https://jsonplaceholder.typicode.com/posts`);
      const posts = await res.json();
      return { props: { posts } 
      , revalidate: 60 }; // 秒数を指定
    }
    
    export default index;

    SSGで利用する際の非同期関数であるgetStaticPropsからreturnするオブジェクトの中で、revalidate指定を一行追加するだけです。 ここでは例として20秒を指定しました。上記の様に実装することで初回アクセス(キャッシュが存在していない状態)から20秒経過後、 クライアント側からリクエストがあったら クライアントには既に生成されたページを見せつつ(キャッシュ)、 バックグラウンドでデータの再取得及び再レンダリングするページを再生成し、 次のリクエストに対しては再生成されたページを返します。 今回は秒数を60秒として説明しましたが、データの性質に応じてこの秒数を長くしたり、あるいは短くしたりすることができます。 なお、先ほども説明したとおりではありますが、revalidate で指定した時間の経過後にリクエストが行われ、ページを再生成している間は、 再生成する前の(つまり、生成済みの)HTML のキャッシュを返すので、ページを再生成するタイミングで応答が遅くなるというようなことはありません。 また、再生成に際してデータの再取得に失敗するなどしてページの再生成ができなかった場合は、それまで生成していたキャッシュがその後も使用されます。 いま説明した部分をイメージ図として表すと以下のようになります。図中の経過秒数等はあくまでも 1 つの例として考えてください。 nextjs-6-6

    ISRのメリット

    ISRを使用する際のメリットを解説していきます。一番のメリットはSSGを使用した際のページでもrevalidateを設定することにより SSRの様に最新情報が取得できる点です。これにより、時間の経過に応じて内容が変化するデータであっても、アプリ全体を再ビルドすることなくその変化を反映させることができます。 またSSRと比べてデータ取得する頻度が少ないのでデータ取得先のDBへの負荷が軽くなります。

    ISRが適しているサービス

    ISRが適しているサービスは常に最新の情報である必要性はないが適度な更新頻度のあるサービスです。 SNS等のリアルタイム性が求められるサービスではやはりSSRを用いるのが得策です。 ですが、冒頭で挙げた天気予報サイトやチケット販売サイト等ではISRを用いることで描画速度を損なうことなく比較的新しい情報を表示させることができます。

    Lesson 6 Chapter 5
    レンダリング方式ごとの比較

    レンダリング方式ごとの比較

    チャプター1〜チャプター4ではそれぞれ性能の異なる4種類のレンダリングについて学んでいきました。 本チャプターでは主な機能、メリット、デメリット、適したサービスについて比較した表を見ながらおさらいしていきましょう。

    CSR(SWRライブラリ)

    データ取得の流れ

    nextjs-6-7
    項目 内容
    主な機能 ・クライアントサイドでレンダリングする仕組み。 ブラウザからHTTPリクエストされると、サーバー側はビルドされたJSとCSS、中身ほぼ空っぽなHTMLファイルをHTTPレスポンスとして返却し、 その後初期データを取得してブラウザがHTMLをレンダリングする。
    メリット ・一度読み込んだページはその後必要な部分のコンテンツのみを描画させるため画面全体が再描画されるストレスがなくなる→UI/UXの向上
    デメリット ・初回アクセス時にWebサイトのデータをまとめて読み込むので、初回の表示まで時間がかかる。
    ・クライアントサイドのJavaScriptの処理が増えるので、CPUやメモリーが少ないスマホなどのデバイスでは、操作性が損なわれる可能性がある。
    ・SEOに弱い
    適したサービス ・ユーザーが頻繁にページ遷移やコンテンツの操作するような滞在時間の長いサービスに適している。 ・SEOをそこまで意識しなくてもいい何かしたのサービスの管理画面等。

    SSR

    データ取得の流れ

    nextjs-6-8
    項目 内容
    主な機能 ・サーバサイドでレンダリングしてクライアントサイドで描画する仕組み。 初回アクセス時はクライアント側からHTTPリクエストが送られて、APIからデータを取得する。 その後、サーバ内で動的にHTMLファイルを生成(Node.jsが実行される)しHTTPレスポンスとしてレンダリング済のHTMLをクライアントに返却する。
    メリット ・初回アクセス時はクライアント側でAPIサーバにアクセスしないので描画が早い。
    ・SEOに強い。
    ・ユーザーの通信環境に左右されにくい。(サーバサイドでレンダリングするので、CPUやメモリーが少ないスマホなどのデバイスでも操作性に影響が少ない。)
    デメリット ・SSRするためのNode.jsを実行出来るWebサーバーが必要になる。 ・サーバ側の負荷が高い。(サーバのCPU負荷が増える。) ・上記2つの結果、ホスティングサーバの導入コストが高くなる。
    適したサービス ・動画投稿サービスやSNSサービス等のコンテンツ更新頻度の高いサービスなど。 比較的に大規模サービス向けのレンダリング手法。

    SSG

    データ取得の流れ

    nextjs-6-9
    項目 内容
    主な機能 ・アプリケーションのビルド時に、APIなどからデータを取得し、HTMLを最初に生成(プリレンダリング)する。 サーバーへのリクエストがあった場合には、この生成されたHTMLファイルを返却します。
    メリット ・SSRよりもレスポンスが高速。
    ・SEOに強い。
    ・キャッシュの設定等を気にしなくていい。
    デメリット ・ビルド以降、データが更新されてもページに反映されない。
    ・ページの数やコンテンツの数が多くなるとビルド時間が長くなる。
    ・頻繁にデータ更新があるサイトには向かない。
    適したサービス ・更新頻度の少ないブログやコポレートサイトなど。
    比較的に中規模サービス向けのレンダリング手法。

    ISR

    データ取得の流れ

    データ取得の流れについてはSSGと同様です。

    項目 内容
    主な機能 ・基本的にはSSGの挙動と同様でまずクライアント側のリクエストに対しビルド時に生成された静的ページを返却する。 またバックグラウンドで一定期間ごとに静的ページの再生成をサーバー側で行うという仕組み。
    メリット ・SSGのBuild時間を短縮できる。
    ・SSRと比較したらDB負荷は軽め。
    デメリット ・現段階ではVercelサービス依存。脱Vercelサービスとなった場合に使用できなくなる。
    適したサービス ・天気予報サイト等。SSRほど更新頻度は高くないが適度に更新頻度のあるサービス。

    まとめ

    本レッスンでは4種類のレンダリング方法について詳しく学んでいきました。 Next.jsではページごとに各レンダリング方法をよしなに選択する事が出来るのでそのページに合った最適なWebパフォーマンスをユーザへ届ける事が出来ます。 是非活用してみください。