# CSR

Client-Side Rendering(CSR)とは、JavaScriptを実行して、ブラウザ側でコンテンツをレンダリングする方法です。 Server-Side Rendering(SSR)が、サーバー側でJavaScriptを実行するのに対し、CSRは、ブラウザ側で、データの取得やルーティング、コンテンツの生成を実行します。

CSR-1.jpg

近年、フロントエンド開発では、ReactやVue.jsなどのUIフレームワークを使って、シングルページアプリケーション(SPAs)を実装することが主流となっています。 シングルページアプリケーションとは、ブラウザをリロードすることなくページの遷移やデータの取得をし、スムーズな操作性を実現するアプリケーションです。このシングルページアプリケーションを実装するために、CSRが広く使われるようになりました。

# CSRの仕組み

CSRの仕組みを、Reactを例に見てみましょう。

以下のようにHello, worldと表示されるWebページを想定します。

CSR

CSRが実行されるステップは、次のようなります。

  1. ブラウザがサーバーへリクエストし、HTMLファイルをダウンロードする
  2. ブラウザがHTMLファイルを解析し、JavaScriptファイルをダウンロードする
  3. JavaScriptが実行される
  4. Reactが起動し、レンダリングを実行して、コンテンツを生成する
  5. 画面(DOM)が更新される

はじめに、ブラウザにURLを入力してアクセスすると、該当のサーバーへリソースの問い合わせをします。次に、サーバーが該当のHTMLファイルをブラウザへ送信します。 この時点のHTMLファイルはコンテンツが空の状態です。なぜなら、サーバー側でJavaScriptが実行されてないためです。

<!doctype html>

<html lang="en">
<head>
</head>

<body>
  <div id="root">
    <!-- コンテンツが空の状態 -->
  </div>

  <script src="js/app.js"></script>
</body>
</html>

ブラウザは、ダウンロードしたHTMLを解析し、内部に定義してあるJavaScriptファイル(js/app.js)を再度サーバーへ問い合わせをしてダウンロードします。

ダウンロードされたJavaScriptファイルは、次のようなReactのコードが含まれています。

const root = ReactDOM.createRoot(document.getElementById('root'));
const element = <h1>Hello, world</h1>;
root.render(element);

ブラウザがJavaScriptを実行すると、Reactが起動します。起動したReactはコンテンツを生成し、画面(DOM)へレンダリングを実行します。 そして、画面(DOM)が更新されるとブラウザにHello, worldが表示されます。 このように、CSRは、JavaScriptをブラウザ(クライアント)側で実行することによって、コンテンツを生成する仕組みになっています。

# JavaScriptのバンドル

CSRは、ブラウザ側でJavaScriptを実行し、データの取得やページ遷移を実現します。 Reactを使った場合は、ページごとにコンテンツを生成する必要があるため、ページが増えるごとにファイル数も増加していきます。

例えば、ページが4つある場合は、以下のようにページごとにReactファイルを定義する必要があります。

pages/
├── about.tsx
├── contact.tsx
├── index.tsx
└── work.tsx

ページ以外にも、データを取得するためのロジックやライブラリ、UIコンポーネントのファイルなどアプリケーションに必要なファイルは多岐に渡ります。 そのようなファイルを、サーバーから一つ一つブラウザに送信するには手間がかかります。また、コードが複雑になり、メンテナンスも難しくなるでしょう。

そのような問題を解決するために、近年では、Webpack(Turbopack)をはじめとしたモジュールバンドラを使うことが主流となっています。 モジュールバンドラとは、JavaScriptファイルを一つ、または複数にまとめるためのツールになります。それ以外にも、CSS、SCSSをJavaScriptでインポートするための変換や、TypeScriptのトランスパイルなども行います。 モジュールバンドラを使うことによって大量のファイルを統合し、効率よく配信することが可能になります。 CSRでは、このモジュールバンドラが生成したファイルをダウンロードして実行するのが一般的になっています。

# メリット

# 操作性の向上

CSRをベースとしたシングルページアプリケーションは、ページ遷移をするたびにリロードをする場合のWebアプリケーション(MPA)と比べて、操作性が格段に向上します。 データの取得やコンテンツの生成をすべてクライアント側で実行できるので、ユーザの操作に対して素早く反応することができます。

また近年では、Optimistic UI(データの変更を素早く画面へ反映する更新方法)などの手法も採用される機会が多くなり、複雑なUIでもシームレスなデータ更新が可能となっています。

# コードのメンテナンス性

CSRは、コードの実行がクライアント側だけになるので、SSRと比べるとコードのメンテナンス性が良くなります。 SSRではサーバーでの実行とクライアント側での実行(Hydration)も意識してコードを書く必要があります。 Next.jsやNuxtなどのフレームワークを使用すれば、必要なAPIが用意されているのでそこまで強く意識する必要はありませんが、自前でSSRを実装するとなると考慮する点が多くなります。サーバーで実行するときとクライアントで実行するとき、のような条件分岐が発生するでしょう。 例えば、ブラウザのAPIやDOM APIを使うときは、クライアント側だけ実行するなどの対応が必要になります。 その点、CSRはクライアントで実行するだけなので、考慮するポイントが少なくなるというメリットがあります。

# デプロイのしやすさ

CSRは、静的コンテンツ(HTML、CSS、JavaScript)をサーバーにアップロードするだけなので、デプロイがシンプルな構成になります。 例えば、ビルドした静的ファイルをAmazon S3にアップロードしておけば、あとは静的コンテンツとして配信することができます。

SSRではサーバーでJavaScriptを実行する環境を用意する必要があります。そのため、Node.jsなどのミドルウェアが必要となるでしょう。 CSRでは、ビルドを実行し、ビルドされた静的ファイルをサーバーにアップロードするだけでデプロイは完了します。

# デメリット

# 初回アクセスのパフォーマンス低下

JavaScriptのバンドルで解説した通り、CSRはバンドルされたJavaScriptファイルをダウンロードしブラウザ側で実行します。 バンドルされたJavaScriptは、ページ生成やデータ取得などのロジックが多く含まれ、肥大化しやすい傾向にあります。 ページや機能を追加するたびに、JavaScriptファイルも大きくなっていきます。

そのため、初回アクセス時に、ファイルをダウンロードする時間がかかり、アプリケーションの起動が遅くなる可能性があります。 CSRでは、初回アクセス時のパフォーマンスを低下させないように、バンドルサイズを減らすなどの対策が必要となります。

# SEO

初回アクセス時のパフォーマンスが低下すると、SEOの評価も下がる可能性があります。 CSRでは、ブラウザでJavaScriptの実行が完了するまで、コンテンツがない状態となります。そのため、クローラーがコンテンツを解析するまでタイムラグが発生してしまいます。

一方、SSRやSSGでは、サーバー側でコンテンツを生成して配信することができます。コンテンツが既に生成してあるため、サーバーからのレスポンスが早くなり、インデックスされやすい傾向にあります。 クローラーはJavaScriptが完了するまで待つことができますが、コンテンツの生成に時間がかかる場合、SEO上、不利になる可能性があります。

# デバイス依存

SSRでは、サーバー側でJavaScriptを実行するため、レンダリングのパフォーマンスはサーバーのスペックに依存します。 一方、CSRではクライアントで実行されるため、使用している端末のスペックに依存します。 例えば、高スペックの最新のPCと、スペックの低い古いスマートフォンでは、パフォーマンスに差が出るでしょう。 そのため、スペックの低いデバイスでもパフォーマンスを維持できるようにするなど、考慮が必要となります。

# APIの型の整合性

CSRでは、データの取得はAPIサーバーから取得するのが一般的です。 CSRで実行するコードは、JavaScriptで記述しますが、APIサーバーでどのプログラミング言語を使用するかは、実装者の判断になります。

SSRでは、サーバーとクライアントで共にTypeScriptを使用すれば、型の整合性やコードの共通化ができます。 しかし、CSRで、APIのプログラミング言語がGoだった場合、サーバーとクライアントで異なる言語を使用することになるため、APIとの型の整合性をとるのが難しくなります。

近年では、このような問題を解決するために、OpenAPI (opens new window)tPRC (opens new window)GraphQL Codegen Generator (opens new window)などを使って、APIとの型の整合性を保つことができるようになりました。 CSRで開発する際は、上述したようなツールを使って型の整合性に対処する必要があります。

# パフォーマンス

CSRでは、JavaScriptファイルの肥大化により、初回のレンダリングパフォーマンスが低下する可能性があります。 レンダリングパフォーマンスが低下するとは、First Contentful Paint(FCP)とTime To Interactive(TTI)のパフォーマンス低下を意味します。 FCPとTTIは、Webアプリケーションのパフォーマンス指標の一つで、重要な意味を持ちます。 CSRが、この指標にどのような影響を与えるのか見てみましょう。

# First Contentful Paint(FCP)

First Contentful Paint(FCP)とは、ページが読み込まれてからページ内のいずれかのコンテンツが最初に表示されるまでの時間を指します。

CSR-パフォーマンス-1

出典: First Contentful Paint (FCP) (opens new window)

下記の図では、2フレームでFCPが発生しているのが分かります。ヘッダー部分が最初に表示されたコンテンツとなります。

CSR-パフォーマンス-2.jpg

一般的に、First Contentful Paintは1.8秒以下になることが望まれます。

CSRでは、ダウンロードしたHTMLは、コンテンツが空の状態となります。 空の状態からJavaScriptをダウンロードし、実行してコンテンツを生成します。そのため、JavaScriptの実行が遅れると、FCPも遅れてしまいます。JavaScriptをダウンロードしてから生成するまでの時間が遅れるほど、FCPの遅延になります。

CSR-パフォーマンス-3

# FCPの改善

では、どのようにFCPを改善すればいいのでしょうか。

FCPを改善するためには、先に静的コンテンツを配信するのが有効です。 例えば、ヘッダーやフッター、ナビゲーションメニューなどをHTMLに含めて配信します。そして、動的なコンテンツは、あとからJavaScriptで生成します。そうすることで、JavaScriptを実行するまで何も映らない、という状況を回避することができます。それにより、FCPの改善が見込まれます。

先ほどのReactを例に、見てみましょう。

<!doctype html>

<html lang="en">
<head>
</head>

<body>
  <header>ヘッダー</header> <!-- 静的コンテンツ -->
  <nav>ナビゲーションメニュー</nav> <!-- 静的コンテンツ -->
  <div id="root">
    <!-- コンテンツが空の状態 --> <!-- 動的コンテンツ -->
  </div>
  <footer>フッター</footer> <!-- 静的コンテンツ -->

  <script src="js/app.js"></script>
</body>
</html>

上記のようにHTMLにヘッダーやナビゲーション、フッターなどの静的コンテンツを含ませることで、HTMLをダウンロード後、即座に画面に反映され、FCPを短縮することができます。

CSR-パフォーマンス-4

さらに、静的コンテンツ部分をService Workerでキャッシュすることで、より素早いユーザー体験を実現することも可能です。 これは、Application Shell Architecture (opens new window)と呼ばれるアーキテクチャです。

# Time to Interactive(TTI)

Time to Interactiveとは、ユーザーが画面を操作できるようになるまでの時間を指します。

CSR-パフォーマンス-5

具体的には、リンクをクリックして画面遷移したり、文字を入力してデータを更新できるなど、アプリケーションの機能が使える状態までの時間を指します。 Reactでは、DOMをレンダリングして、イベントがバインディングされた状態(ハイドレーション)を指します。 アプリケーションが機能するまでには、JavaScriptをダウンロードし、コンテンツを生成する必要があります。 そのため、TTIを改善するには、リソースの最適化やFCPの改善も同時に求められます。

# リソースの最適化

CSRで、初回のレンダリングパフォーマンスを改善するにはダウンロードするリソースの最適化が求められます。 例えば、次のようなパフォーマンス施策が有効でしょう。

  • リソースの最適化
  • JavaScriptファイルの圧縮
  • Code SplittingによるJavaScriptファイルの分割
  • Lazy loading
  • Preload
  • キャッシュの活用
  • SSR

具体的なパフォーマンス改善の仕方は、リソースの最適化JavaScriptファイルの最適化をご確認ください。