この記事は ベーシック アドベントカレンダー の12日目の記事です。
ベーシック開発部タインと申します。
WebSiteの初期表示を高速化するのはいつも重要なことですから、
今回は多様にある改善方法でProgressive Server Side Rendering(PSSR) の方法を紹介と試したいと思います。
Speedup.jpg

PSSRとは

PSSRの概念を理解するためには、まずCSRSSRの理解が必要となるのであります。

CSR

CSR、クライアントサイドレンダリングの略です。これはクライアントのブラウザで javascriptsの処理を行ってHTMLをレンダリングする方法です。

代表:React, Vue,...

  1. ブラウザからリクエストを送る
  2. サーバーがリクエストを処理してHTMLデータを返す
  3. ブラウザがJSダウンロードリクエストを作成する
  4. ブラウザがダウンロードされたJSを解析して、実行して、コンテンツをレンダリングする

CSR.png

メリット

  • 柔軟性が高い

デメリット

  • 初期表示速度が遅い
  • SEOに弱い
  • ブラウザの負荷がかかる

SSR

SSR、サーバーサイドレンダリングの略です。これはJavaScriptをサーバー内部で実行して、HTMLを生成することです。

代表:Next.js, Nuxt.js,...

  1. ブラウザからリクエストを送る
  2. サーバー内部でJavaScriptを実行して、HTMLを生成する
  3. ブラウザにそこのHTMLデータを渡す
  4. ブラウザがそこのデータをパースしてレンダリングする
    SSR.png

メリット

  • CSRより初期表示速度が早い

デメリット

  • サーバー内部で重要じゃない部分も含めて全部HTMLをレンダリングしますので、初期表示速度まだ改善できる

PSSR

PSSR、プログレッシブサーバーサイドレンダリングの略です。この方法はサーバー内部で重要な部分だけ先にレンダリングして、ストリーム経由でブラウザにHTMLデータを渡します。そこでユーザーの体感速度も向上になります。

  1. ブラウザからリクエストを送る
  2. サーバー側は大切な部分を先に処理して、HTMLを生成して、ストリーム経由でブラウザにHTMLデータを渡す
  3. ブラウザがそこのデータをパースしてレンダリングする
  4. その後サーバー側は残り部分を引き続きブラウザにストリームする
  5. ブラウザが残りのデータをレンダリングする

PSSR.png

PSSRはCSRとSSRのメリットを解決できるし、ユーザーの体験も改善できます。

SSR PSSR
Speedup Dec-09-2019 19-48-07.gif

PSSRを実現してみる

環境

  • MacOS Mojave
  • Node v10.14.0
  • Express v4.17.1

1.Nodeアップを作成する

$ npm init

Expressをインストール

$ npm install --save express

2. Patialを用意する

ページ全体のHTMLソースをpatialとして分けます。

<!-- views/1-openHTML.html -->
<html>
<!-- views/2-head.html -->
<head>
  <style>
    body {
      height: 100vh;
      margin: 0 100px;
    }
  </style>
</head>
<!-- views/3-openBody.html -->
<body>
<!-- views/4-header.html -->
<header>
</header>
<!-- views/5-main.html -->
<main>
</main>

..........

3. Patialの読み取りするモジュールを用意する

// src/renderer.js
module.exports = {
  openHTML: async () =>
    await loadHTML(__dirname + "/views/1-openHTML.html"),
  head: async () =>
    await loadHTML(__dirname + "/views/2-head.html"),
  openBody: async () =>
    await loadHTML(__dirname + "/views/3-openBody.html"),
  header: async () =>
    await loadHTML(__dirname + "/views/4-header.html"),
  contentOpen: async () =>
    await loadHTML(__dirname + "/views/5-contentOpen.html"),
  sideLeft: async () => 
    return await loadHTML(__dirname + "/views/6-sideLeft.html"),
  main: async () =>
    return await loadHTML(__dirname + "/views/7-main.html"),
  sideRight: async () => 
    return await loadHTML(__dirname + "/views/8-sideRight.html"),
  .....
};

4. Readable APIでHTMLデータを渡す

// src/app.js
const app = express();
const renderer = require("./renderer");

// localhost:3000をアクセスする
app.get('/', async (req, res) => {
  const openHTML = await renderer.openHTML();
  const head = await renderer.head();
  const openBody = await renderer.openBody();

  res.write(openHTML);
  res.write(head);
  res.write(openBody);

 // この順番でHTMLデータをストリームする
  const dataStreams = [
    renderer.header,
    renderer.contentOpen,
    renderer.main,
    renderer.sideLeft,
    renderer.sideRight,
    renderer.contentClose,
    renderer.footer,
    renderer.closeBody,
    renderer.closeHTML
  ];
  
  // 読み取り可能なストリームを作成する
  const readStream = new Readable({
    async read(size) {
      if (dataStreams.length) {
        const dataStream = dataStreams.shift();
        const dataStreamHTML = await dataStream();
        readStream.push(dataStreamHTML);
      } else {
        readStream.push(null); //読み取り可能なデータがないと、ストリームをストップする
      }
    }
  });
  // pipe() メソッドは読み取り可能なストリーム(readStream)と書き込み可能なストリーム(res)を関連付けるもので、
 // 後はデータを渡してくれる
  readStream.pipe(res);
});

5. 試する

$ npm run start
Server running at http://127.0.0.1:3000/

Live Demo
Github

まとめ

CSR: fast
SSR: faster
PSSR: more faster
???: fastest

今回の紹介したPSSRは静的なHTMLで試してみますが、ReactやVueなどもできると思います。
次のパーツはNext.js, Nuxt.jsでPSSRを試します。お待ちください😎

参考

https://qiita.com/Mizunashi_Mana/items/872354cd7bf25090932f