🔥

CSR, SSR, SSGの比較

2023/09/20に公開

はじめに

既にSSR, SSG, CSRについて多くの記事が作成されていますが、実際に挙動を確認しながらそれぞれの違いを試したことがなかったので、簡単なサイトを作成して比較してみました。

https://next-rendering-comparison.vercel.app/

https://github.com/shun1121/next-rendering-comparison

ディレクトリ構成

.next/
node_modules/
public/
src/
  pages/
    ├─ api/
    ├─ csr/
    │   ├─ index.tsx
    ├─ ssg/
    │   ├─ index.tsx
    ├─ ssr/
    │   ├─ index.tsx
    ├─ _app.tsx
    ├─ _document.tsx
    ├─ index.tsx
    ├─ styles/
.eslintrc.json
.gitignore
next-env.d.ts
next.config.js
package.json
postcss.config.js
README.md
tailwind.config.ts
tsconfig.json
yarn.lock

!https://static.zenn.studio/images/copy-icon.svg

Next.jsのプロジェクト作成

yarn create next-app

でNext.jsのプロジェクトを作成します。今回はpagesディレクトリで作成しているので下記画像の手順でプロジェクトの設定をしていきます。

https://gyazo.com/070d4581456d5959bfca0ab0b69b0d9e

CSR, SSR, SSGの実装はそれぞれcsr, ssr, ssgのディレクトリ配下のページに実装しています。

CSR, SSR, SSGについて

CSRとは

Client Side Renderingの略で、クライアント側でJavaScriptを使用してページコンテンツを生成する方法です。クライアントのリクエストに対して空のHTMLやJavaScript等のリソースを返し、クライアント側でJavaScriptが実行されページのレンダリングやデータフェッチなどが行われます。

実装が比較的簡単で画面遷移を高速で行えるなどのメリットがある一方、アプリケーションに必要なリソースを最初に取得し、クライアント側でJavaScriptが実行されてレンダリングが起こるので初期表示が遅かったり、空のHTMLが返るのでSEOやOGPに対応できない、などのデメリットもあります。

SSRとは

Server Side Renderingの略で、サーバ側でデータを取得、レンダリングして生成したHTMLをクライアントに返す方法です。サーバ側でHTMLが生成されるためSEOやOGPに対応することができ、また、リクエストがあるたびにページを生成するため、初回にすべてのリソースを読み込む必要がなく、初回の表示速度も短くなるといったメリットがあります。しかし、リクエストの都度リソースを取得してHTMLを生成するので、2回目以降のリクエストではCSRよりも表示速度が遅くなるなどのデメリットもあります。

SSGとは

Static Site Generationの略で、ビルド時にサーバ側でデータを取得し、静的なHTMLを事前に作成する方法です。リクエストがあったときにビルド時に生成した静的なHTMLを返します。生成ファイルをCDNにキャッシュすることにより、高速なレスポンスを返すことができるなどのメリットがあります。しかし、ビルド時にページを作成してしまうので、ビルド以降データが更新されてもページに反映されなかったり、ページ数などが多くなるとビルドにかかる時間が長くなるなどのデメリットもあります。

CSRを使用する

src/csr/index.tsx

import Link from 'next/link';
import useSWR from 'swr';

export const CSR = () => {
  const { data, error } = useSWR("https://jsonplaceholder.typicode.com/users", async (url) => {
    const response = await fetch(url)
    const data = await response.json()
    return data
  })
  const time = new Date()
  const hour = time.getHours()
  const minute = time.getMinutes()
  const second = time.getSeconds()
  const currentTime = hour + "時" + minute + "分" + second + "秒"
  if (error) {
    return <div>error</div>
  }
  if (!data) {
    return <div>Loading</div>;
  }
  return (
    <div className="w-full mx-auto py-8">
      <nav className="ml-10 flex gap-2 underline">
        <Link href="/ssg">
          ssg
        </Link>
        <Link href="/ssr">
          ssr
        </Link>
      </nav>
      <div className="w-full mx-auto py-8">
        <h2 className="text-[32px] text-center py-8">CSR</h2>
        <div className="text-[32px] text-center">{currentTime}</div>
        <table className="border max-w-[800px] w-full mx-auto">
          <thead className="border">
            <tr>
              <th className="border">id</th>
              <th className="border">name</th>
              <th className="border">username</th>
              <th className="border">email</th>
            </tr>
          </thead>
          <tbody>
            {
              data.map((user: any) => (
                <tr key={user.id}>
                  <td className="border">{user.id}</td>
                  <td className="border">{user.name}</td>
                  <td className="border">{user.username}</td>
                  <td className="border">{user.email}</td>
                </tr>
              ))
            }
          </tbody>
        </table>
      </div>
    </div>
  )
}

export default CSR

Json Place Holderを使ってTableにユーザーデータを表示しています。また、現在の時間も表示しています。基本的にスムーズに動きますが、初回のアクセス時にdataが取得できるまで少し時間がかかるため、画面左上にLoadingの文字が表示されています。

Video by Gyazo

SSRを使用する

src/ssr/index.tsx

import Link from "next/link"

export const SSR = ({ usersData, currentTime }: any) => {
  return (
    <div className="w-full mx-auto py-8">
      <nav className="ml-10 flex gap-2 underline">
        <Link href="/csr">
          csr
        </Link>
        <Link href="/ssg">
          ssg
        </Link>
      </nav>
      <h2 className="text-[32px] text-center pt-[64px] pb-8">SSR</h2>
      <div className="text-[32px] text-center">{currentTime}</div>
      {usersData ? (
        <table className="border max-w-[800px] w-full mx-auto">
          <thead className="border">
            <tr>
              <th className="border">id</th>
              <th className="border">name</th>
              <th className="border">username</th>
              <th className="border">email</th>
            </tr>
          </thead>
          <tbody>
            {
              usersData.map((user: any) => (
                <tr key={user.id}>
                  <td className="border">{user.id}</td>
                  <td className="border">{user.name}</td>
                  <td className="border">{user.username}</td>
                  <td className="border">{user.email}</td>
                </tr>
              ))
            }
          </tbody>
        </table>
      ) : (
        <div>loading</div>
      )}
      <div>
        <a href="../ssg/"></a>
      </div>
    </div>
  )
}

export const getServerSideProps = async () => {
  const res = await fetch("https://jsonplaceholder.typicode.com/users")
  const users = await res.json()
  const usersData = users.map((user: any) => ({
    id: user.id,
    name: user.name,
    username: user.username,
    email: user.email,
  }))
  const time = new Date()
  const hour = time.getHours()
  const minute = time.getMinutes()
  const second = time.getSeconds()
  const currentTime = hour + "時" + minute + "分" + second + "秒"

  return {
    props: {
      usersData,
      currentTime
    }
  }
}

export default SSR

getServerSidePropsを用いて、サーバ側で取得したユーザーデータと現在時間を渡しています。表示内容はCSRと同じ内容ですが、ssg ⇄ csrとssg ⇄ ssr のアクセス遷移の時間を見てみると、ssgからssrへ遷移するときは少し時間がかかっているように見えます。

Video by Gyazo

SSGを使用する

src/ssg/index.tsx

import Link from "next/link"

export const SSG = ({ usersData, currentTime }: any) => {
  return (
    <div className="w-full mx-auto py-8">
      <nav className="ml-10 flex gap-2 underline">
        <Link href="/csr">
          csr
        </Link>
        <Link href="/ssr">
          ssr
        </Link>
      </nav>
      <h2 className="text-[32px] text-center pt-[64px] pb-8">SSG</h2>
      <div className="text-[32px] text-center">{currentTime}</div>
      <table className="border max-w-[800px] w-full mx-auto">
        <thead className="border">
          <tr>
            <th className="border">id</th>
            <th className="border">name</th>
            <th className="border">username</th>
            <th className="border">email</th>
          </tr>
        </thead>
        <tbody>
          {
            usersData.map((user: any) => (
              <tr key={user.id}>
                <td className="border">{user.id}</td>
                <td className="border">{user.name}</td>
                <td className="border">{user.username}</td>
                <td className="border">{user.email}</td>
              </tr>
            ))
          }
        </tbody>
      </table>
    </div>
  )
}

export const getStaticProps = async () => {
  const res = await fetch("https://jsonplaceholder.typicode.com/users")
  const users = await res.json()
  const usersData = users.map((user: any) => ({
    id: user.id,
    name: user.name,
    username: user.username,
    email: user.email,
  }))
  const time = new Date()
  const hour = time.getHours()
  const minute = time.getMinutes()
  const second = time.getSeconds()
  const currentTime = hour + "時" + minute + "分" + second + "秒"

  return {
    props: {
      usersData,
      currentTime
    }
  }
}

export default SSG

getStaticPropsを用いて、サーバ側で取得したユーザーデータと現在時間を渡しています。ユーザーデータに変わりはありませんが、表示時間を見てみると、csrとssrは共にリロードをした際に時間が変わっているのに対し、ssgは何度リロードをしても時間が変わりません。ビルド時に生成した静的ページのままデータが更新されていないことが確認できます。

最後に

以上、簡単なアプリを作成してCSR, SSR, SSGの挙動の一例を見てきました。ここでは一部の情報しか試せていませんので、詳しい情報は公式ドキュメントや他のサイトを参考にしてください。それぞれのレンダリングの認識が間違っていましたら、ご指摘いただけると幸いです。

参考

https://nextjs.org/docs/pages/building-your-application/data-fetching
https://zenn.dev/a_da_chi/articles/105dac5573b2f5
https://yutaro-blog.net/2021/12/03/spa-csr-ssr-ssg-sg/
https://dev.classmethod.jp/articles/nextjs-rendering/
https://qiita.com/hiroki-yama-1118/items/b3388c5dcb155e2e367d

GitHubで編集を提案

Discussion