📝

CSR,SSR,SSG,ISRの違いと、Next.js(App Router)での実装方法

2024/04/10に公開

はじめに

「Next.jsを使ってブログを作ったけど、ページ遷移が全然高速じゃない!」

という問題にぶちあたりました。

結論、「俺、CSR,SSR,SSG,ISR全然理解してないやん!」

となって学び直しました。

間違っている部分ありましたら、コメントでお知らせいただけますと幸いですm(__)m

CSRとSSR

CSR(クライアントサイドレンダリング)と、SSR(サーバサイドレンダリング)は対象的なレンダリング方法としてよく比較されます。

その名の通り、CSRクライアントでレンダリングされ、SSRはサーバでレンダリングされます。

例えば、「記事の一覧の情報を持ったAPI」があって、それを取得してWebで表示させる様な実装をするとしましょう。

CSRの場合、script.jsでAPIから記事の情報を取得(レンダリング)して、index.htmlで読み込みます。ユーザはindex.htmlにアクセスし、script.jsが実行され、記事の一覧が表示されます。

SSRの場合、サーバにて、記事の情報を取得(レンダリング)し、HTMLを生成します。

今回の例だと、記事一覧の情報をクライアントで処理するか、サーバで処理するかがCSRとSSRの違いとなります。

CSRはクライアントに依存して処理のの速さが決まるのに対して、SSRはサーバに依存して処理の速さが決まります。

CSRの実装方法

App Routerでは、すべてのコンポーネントがサーバコンポーネントになりました。

ファイルの先頭に"use client"と書くことで、クライアントコンポーネントとすることができます。

例えばuseEffectなどのReact hooksはクライアントでしか使用することができないので、下記の様な記述になります。

"use client"
import { useEffect } from 'react';

export default function Comp () {
  useEffect(()=> {
    //
  })

  return(
    //
  )
}

親コンポーネント以下は全てクライアントコンポーネントとなるので、CSRはなるべくコンポーネント化して、サーバコンポーネントで読み込むがベターです。

SSRの実装方法

Next.jsではAPIの情報を取得する場合、fetchを使って下記の様に記述することができます。

export default async function Page() {
  const data = await fetch(`https://...`);

  return(
    //
  )
}

サーバに処理をさせることで、クライアントに依存しない処理を実現できたものの、いくらサーバでも膨大な処理を実行させるとなるとページの読み込みが遅くなってしまいます。

そこで登場するのがSSGです。

SSG(静的サイトジェネレーション)

SCR,SSRともに、ページにアクセスがあってからデータを取得(レンダリング)されることでページの表示が遅い、という問題がありました。

であれば、あらかじめレンダリングしたHTMLを用意しておき、ページにアクセスがあったときに表示するだけにしておけば高速なページの読み込みを実現できるのではないか? という発想で生まれたのがSSG(静的サイトジェネレーション)です

SSGはビルド時にレンダリングし、HTMLを生成します。ユーザがサイトにアクセスした時にクライアントに返すだけなので高速で表示ができます。

SSGの実装方法

App routerでSSGを使用するにはgenerateStaticParamsを使います。

generateStaticParamsは動的ルーティングと併せて使用されます。

下記は公式のサンプルコードままです。

app/blog/[slug]/page.js
export async function generateStaticParams() {
  const posts = await fetch('https://.../posts').then((res) => res.json())
 
  return posts.map((post) => ({
    slug: post.slug,
  }))
}

export default function Page({ params }) {
  const { slug } = params
  // ...
}

generateStaticParamsはダイナミックセグメント(slug)をparamsとして返します。

下記の様にgenerateStaticParamsが返したparamsslugを用いてfetchしてデータを取得するとでSSGを実装することができます。

app/blog/[slug]/page.js
 export async function generateStaticParams() {
   const posts = await fetch('https://.../posts').then((res) => res.json())
 
  return posts.map((post) => ({
    slug: post.slug,
  }))
}

export default function Page({ params }) {
  const { slug } = params
+ const posts = await fetch(`https://.../posts?slug={$slug}`).then((res) => res.json())
+ 
+ return (
+  //
+ )
}

SSGは、もともと生成したHTMLをクライアントに返すだけなので、高速表示ができます。

しかし、ブログの内容を更新した場合(APIの内容を更新した場合)、ビルド時に生成したHTMLにブログの内容は更新されません。

そこで登場するのがISRです。

ISR(Incremental Static Regeneration)

ISRは、今まで説明したSSGの挙動に加えて、一定時間ごとにバックグラウンドでデータの再取得、再レンダリングを行い、HTMLを再生成する手法です。

ISRの実装方法

ISRはgenerateStaticParamsに変更を加えることで実装することができます。

app/blog/[slug]/page.js
 export async function generateStaticParams() {
   const posts = await fetch('https://.../posts').then((res) => res.json())
 
  return posts.map((post) => ({
    slug: post.slug,
  }))
}

export default function Page({ params }) {
  const { slug } = params
- const posts = await fetch(`https://.../posts?slug={$slug}`).then((res) => res.json())
+ const posts = await fetch(`https://.../posts?slug={$slug}`,{ next: { revalidate: 60 }).then((res) => res.json())

  return (
    //
  )
}

fetchの引数に{ next: { revalidate: 60 }を加えることで、一定秒数以降にリクエストがきた時に、サーバー側でデータフェッチを再度行い、HTMLを再構築します。

表示されるデータは更新されても、新しいデータが表示されるようになりました。

じゃあ常にISRでいいんじゃないのか?

じゃあ常にISRを使っていけばいいんじゃない? と言うわけでもないみたいです。

「常に最新の状態で見れるようにしたい!」という場合はSSGで実装して、「ある程度、最新でない情報が見えても問題ない」という場合はISRで実装する。

ということになるかと思います。

ISRに関わらず、どのレンダリング方法を用いて実装するかは要件を踏まえてよく考える必要があるかと思います。

今回私はブログ構築でしたので、ISRで実装しました。

一次情報

https://nextjs.org/docs/app/api-reference/functions/fetch

https://nextjs.org/docs/app/api-reference/functions/generate-static-params

Discussion