CSR, SSR, SSGの比較
はじめに
既にSSR, SSG, CSRについて多くの記事が作成されていますが、実際に挙動を確認しながらそれぞれの違いを試したことがなかったので、簡単なサイトを作成して比較してみました。
https://next-rendering-comparison.vercel.app/
ディレクトリ構成
.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ディレクトリで作成しているので下記画像の手順でプロジェクトの設定をしていきます。
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
の文字が表示されています。
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へ遷移するときは少し時間がかかっているように見えます。
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の挙動の一例を見てきました。ここでは一部の情報しか試せていませんので、詳しい情報は公式ドキュメントや他のサイトを参考にしてください。それぞれのレンダリングの認識が間違っていましたら、ご指摘いただけると幸いです。
参考
Discussion