Angular SPAからUniversalにしてみたら、パフォーマンスがかなり改善された!


前提

Webアプリケーションを構築する場合、サービスの性質や要件を踏まえてSEO対策やモバイルデバイスへのパフォーマンス改善、ページ表示速度が重要であるかどうかをまずご検討ください。(これマジ。笑)
上記いづれかが重要な場合は最初からサーバーサイドレンダリング (SSR)で構築することをお勧めします。

概要

Angularで構築しているWebアプリケーションを元々はSPAで構築していましたが、サービス的にはSEOやパフォーマンス改善が重要になるため、Angular Universalを使ったサーバーサイドレンダリング (SSR)に対応しました。
その過程でいくつかつまづきポイントがあったので、注意点を含めてご紹介します。

ここはこうした方がいいよ、というアドバイスがあればコメントにて教えていただけると嬉しいです。

環境

  • Node v16.10.0
  • Angular CLI: 13.3.0
  • Package Manager: npm 7.24.0
  • OS: linux x64

最低限必要な手順

サーバー側のモジュールインストール

ng add @nguniversal/express-engine
src/
  index.html                 アプリの Web ページ
  main.ts                    クライアントアプリのブートストラップ
  main.server.ts             * サーバーアプリのブートストラップ
  style.css                  アプリのスタイル
  app/ ...                   アプリケーションコード
    app.server.module.ts     * サーバーサイドアプリケーションモジュール
server.ts                    * Express Web サーバー
tsconfig.json                TypeScript 基本構成
tsconfig.app.json            TypeScript クライアント構成
tsconfig.server.json         * TypeScript サーバー構成
tsconfig.spec.json           TypeScript 仕様の構成

ローカル環境での実行

npm run dev:ssr

http://localhost:4200 でアプリケーションにアクセスできます。

本番環境での実行

ビルドして、SSR用のサーバーを起動するイメージですね。

npm run build:ssr
npm run serve:ssr

http://localhost:4000 でアプリケーションにアクセスできます。

注意点

私が直面した経験ベースですが、ローカル環境とサーバー環境ごとの注意点をご紹介します。

ローカル環境

Browser Syncエラー

ローカル環境でDockerなどを利用してポートマッピングしている場合は注意が必要です。
npm run dev:ssrを実行すると、
http://localhost:4200/browser-sync/socket.io/?EIO=4&transport=polling&t=N-w12hpという形でpollingしているようです。
ポートが解放されていないと以下のようにエラーが出力されます。

Docker環境

参考までに私のDocker環境をご紹介します。

構成
.
┝ environment
│ └ development
│   ┝ docker-compose.yml
│   └ docker
│     └ node
│       └ Dockerfile
└ front # Angularプロジェクト
docker-compose.yml

ports'4200:4200'がポイントです。

docker-compose.yml
version: '3.9'

services:
  node:
    build: docker/node
    ports:
      - '80:4200'
      - '4200:4200'
    volumes:
      - './../../front:/projects:cached'
    tty: true
    command: bash -c "
      npm install;
      cp -f src/index.develop.html src/index.html;
      cp -f src/environments/environment.develop.ts src/environments/environment.ts;
      npm run dev:ssr;
      "
Dockerfile
FROM node:16.10.0

WORKDIR /projects

RUN npm cache clean -f
RUN npm install -g @angular/[email protected]

LocalStorageが使えない

localStorage is not definedとなりますので、Cookieの利用を検討ください。
私は、localStorageもCookieも利用しない方向でとりあえず修正しました。

サーバー環境(本番環境)

Node Express server listening port変更

npm run serve:ssrを実行するとポート4000でアクセスを受け付けるようになります。
しかし、私のサーバー環境では、80番ポートでリクエストを受け取るようにしていたので設定する必要があります。

どうやら、server.tsで設定されているようです。

server.ts
function run(): void {
  const port = process.env['PORT'] || 4000;

  // Start up the Node server
  const server = app();
  server.listen(port, () => {
    console.log(`Node Express server listening on http://localhost:${port}`);
  });
}

取り急ぎの変更であれば、上記の400080に変更すれば良いのですが、process.env['PORT']で切り替わるようにしてみましょう。
設定方法はいくつかありますが、Dockerを使っている場合はDockerfileでも指定できます。

FROM node:16.10.0

ENV PORT=80

環境に合った方法で設定してみてください。

Angular material

一番ハマったのですが、Angular materialはUniversalではそのままでは使えないようです。
ローカルではエラーが出ず、サーバー環境でのみエラーが出たので原因を調べるのがとても大変でした。笑

Universal用のパッケージ(universal-material)をインストールする必要があるようです。

1. Universal Materialインストール

npm i @universal-material/angular

2. Universal Material scssインポート

src/styles.scss
@import "node_modules/@universal-material/core/scss/universal-material.scss";

SSR対応の効果

Chromeの拡張ツールであるLighthouseで検証します。

Universal対応前

画像を残していなかったのですが、Performanceのスコアは13でした。

Universal対応後

Performanceのスコアは51に!!

※ Universal以外の対応は特にしていません。

SSR対応したサイト

開発・制作実績を紹介するWebアプリケーション Value Work です。

どんどんブラッシュアップしていきますので、開発・制作実績のある方はぜひご登録ください。