# SSR

フロントエンド開発におけるServer-Side Rendering(SSR)とは、初回アクセス時にサーバ側でコンテンツをレンダリングして、クライアントへ配信するアーキテクチャです。 SSRも、CSRと同様に、クライアント側ではSPAとして振る舞います。異なる点は、初回のレンダリング方法です。

CSRが、ブラウザ側でJavaScriptを実行してコンテンツを生成するのに対し、SSRはサーバ側でコンテンツを生成します。 初回のアクセス以降のページ遷移は、CSRと同様にブラウザ側でコンテンツを生成します。

例えば、初回アクセス時のhttp://example.com/ では、サーバでコンテンツを生成し、http://example.com/about へ移動したときは、リロードをせずにSPAとして遷移します。

フロントエンド開発におけるSSRの実装は、Next.jsやNuxtなどのフレームワークを用いて実装されることが一般的です。

# MPAとSPA

SSRと似たような仕組みで、Multi Page Application(MPA)というアプリケーションがあります。SPAがリロードをせずにページ遷移ができるのに対して、MPAは、ページを移動するたびに、サーバへのアクセスが必要なアプリケーションになります。 サーバ側で全てのコンテンツを生成するため、都度ブラウザをリロードしてアクセスする必要があります。

SSRとの違いは、SSRは初回のみサーバ側でレンダリングをして、それ以降はSPAとして振る舞います。MPAは毎回サーバ側でレンダリングする必要があります。

MPAは、SPAが登場する以前から広く使われており、PHPやRuby、Javaなどの言語を使って実装されていました。

# SSRの仕組み

SSRの仕組みをNext.jsを例に見てみましょう。 ECサイトのような商品リストを表示するWebページを想定します。

SSRが実行されクライアントへ配信するまでのステップはサーバとクライアントで次のようになります。

サーバ側で実行

  1. ブラウザがサーバへリクエストをする
  2. サーバはリクエストを受け取り、Reactのレンダリングを開始する
  3. コンテンツを生成するための商品データをAPIサーバに問い合わせる
  4. APIサーバから商品データを受け取り、コンテンツを生成する
  5. 生成したコンテンツ(HTML)をブラウザ(クライアント)に返す

サーバーサイドで実行

クライアント側で実行

  1. ブラウザ側でHTMLファイルを解析し、JavaScriptファイルをダウンロードする
  2. JavaScriptファイルを実行し、Reactを起動する
  3. すでに生成されたコンテンツ(HTML)に対して、ハイドレーションを実行する
  4. ハイドレーション後、アプリケーションが機能する状態になる

クライアントで実行

はじめに、ブラウザにURLを入力してアクセスすると該当のサーバへリソースの問い合わせをします。

次に、サーバ側でReactのレンダリングが開始されます。

React内部では外部APIへの問い合わせを次のように実行します。Next.jsの場合、getServerSideProps (opens new window)を使うと非同期でデータを取得することができます。

export async function getServerSideProps(context) {
  // 外部APIから商品データを取得
  const res = await fetch(`https://.../products`)
  const products = await res.json()

  // データをコンポーネントへ渡す
  return {
    props: { products },
  }
}

そして、渡された商品データをもとにコンテンツを生成します。

export function Products({ products }) {
  return (
    <div>
      <h1>商品</h1>
      <ul>
        {products.map((p) => (
          <li key={p.id}>{p.name}<li/>
        ))}
      </ul>
    </div>
  )
}

次に、生成したコンテンツ(HTML)をブラウザ(クライアント)に返します。 このとき、すでにレンダリングが完了しているのでブラウザの画面にコンテンツがすぐ反映されます。

しかし、ページ遷移やボタンをクリックするなどの動きは機能しません。なぜなら、生成されたDOMに対してイベントがバインディングされていないからです。

この時点では、ただの静的なHTMLの状態です。 イベントをバインディングするためには、Reactではハイドレーション (opens new window)というプロセスが必要になります。

hydrateRoot(document.getElementById('root'), <App />)

ハイドレーションをすることによって、DOMにイベントがバイディングされ、アプリケーションが機能するようになります。

CSRの実行ステップと異なる点は、初回アクセス時にサーバ側でコンテンツを生成している点とクライアントでハイドレーションをしている点になります。

# メリット

# FCPとTTIの向上

SSRでは、あらかじめコンテンツをサーバ側で生成するため、ブラウザですぐに画面を反映することができます。 これによりFCPまでの時間が短縮されます。 また、レンダリングに関わる処理が減るためTTIのパフォーマンスも向上します。

FCPとTTIの向上

結果として、ユーザがアプリケーションを動かすことができるまでの時間を短縮することができます。

# SEOの改善

FCPとTTIのパフォーマンスが良くなることによって、クローラーボットはコンテンツを遅延なくインデックスすることができます。 ブラウザ側でJavaScriptの実行が完了するまでコンテンツがない状態のCSRに比べると、インデックスするまでの時間が大幅に削減できるので、SEO上、有利になる可能性があります。

# デバイススペックに依存しない

CSRでは、ブラウザ側でJavaScriptを実行するため、実行するデバイスのスペックによってパフォーマンスが変わります。 SSRでは、サーバ側でレンダリングされるため、レンダリングのパフォーマンスはサーバのスペックに依存することになります。 そのため、クライアントのデバイスのスペックに関係なくパフォーマンスを発揮することができます。 ただし、初回以降のレンダリングはクライアント側になるので、CSRと同様に、レンダリングパフォーマンスの最適化は必要となります。

# デメリット

# Time To First Byte(TTFB)が遅くなる

Time To First Byte(TTFB)とは、クライアントからサーバへ問い合わせをし、最初のレスポンスが返ってくるまでの時間を指します。

TTFBは外部からの影響を受ける場合もあります。例えば、DNSルックアップやネットワークの遅延、物理的な距離による遅延などです。 また、サーバ側の処理が遅れた場合も影響を受けます。

SSRの仕組みの章で見たように、サーバ側でレンダリングする場合は、APIサーバへの問い合わせが必要になります。 もし、APIサーバの応答が遅かった場合はその分サーバからクライアントへの配信も遅くなります。 その結果、レンダリングが遅くなりTTFB低下に繋がります。 ユーザから見るといつまでも画面が更新されず真っ白な状態が続くことになるでしょう。

TTFBの低下を防ぐためには、サーバスペックの最適化やスケールアウト、キャッシュ、データベースの最適化など様々な要因を考慮する必要があります。

# SSRに適したサービス

SSRは、サーバ側で都度コンテンツを生成します。 キャッシュを使うアーキテクチャもありますが、基本的に動的にコンテンツを生成するのを得意としています。 そのため、アプリケーションの要件としては、動的コンテンツでリアルタイム更新性が求められるもの、リクエストによってコンテンツが変わるもの、SEO対策が必要なものがSSRに適しているでしょう。 例えば、金融商品を扱うようなサイトでは更新が即座に反映される必要があるでしょう。 また、リアルタイム性が求められて、SEO対策も必須となるニュースサイトなどはSSR + CDNが適しているでしょう。

逆にリアルタイムで更新しなくてもいいものはSSGが向いています。 例えば、更新性が低く静的コンテンツが多いコーポレイトサイトや記事数が少なめのブログやメディアサイトなどが当てはまります。

上記のようにアプリケーションの要件や特性を踏まえつつ、適切なアーキテクチャを選択することが重要となります。