Next.js / TypeScript でリニューアル
運用におけるハマりどころ

2019-06-26 React Tokyo Plus @mercari

@tkdn

Who

  {
    "name"  : "武田 諭 / Satoshi Takeda",
    "social": "tkdn",
    "from"  : "株式会社 mediba",
    "dept"  : "CD本部創造開発部/ものづくり推進部",
    "job"   : "Front-End web developer",
    "love"  : "family, alcohol, DX"
  }

Who

  • 1980年生まれ
  • 2003〜2013 俳優業
  • 2013〜2017 エンジニア転向、受託開発など
  • 2017〜 mediba, 入社2年目

株式会社 mediba

  • KDDI グループ

    • au スマートパス
    • au Wallet ポイントプログラム
    • OpenDoctors
    • ...
    • au Webポータル ⬅️ 今回の話

au Webポータル

なぜリニューアルしたのか

  • mediba におけるフロントエンドの取り組み

    • 事業成長に伴う施策はフロントエンド

      • BFF を含むアプリエンジニアをフロントエンドの範疇とした
    • 人的リソースの効率化を行うため, FE/BE 比率の変更
    • 技術的な成長要素と市場価値の創出

今日お話すること

Next.js/TypeScript を中心に据えた技術スタックでリニューアル,
それらを経て得たものを中心にお話します。

TypeScript みが少ないかもしれません
包括的な内容が多いので気になるところは懇親会で聞いてください

アジェンダ

  1. リニューアルで得たもの
    ✅ パフォーマンス値の上昇
    ✅ ABテスト, コンポーネント設計の例
  2. Next.js, next.config.js
    ✅ TypeScript との食い合わせ
    ✅ next.config.js との付き合い方
  3. リニューアルで得られなかったもの
    ✅ Perf 知見不足による失敗
    ✅ Perf どう取り組んでいくか

1. リニューアルで得たもの
パフォーマンス値の向上

📈 成功1: 数年継続して取得していた
(特定の)パフォーマンス値の向上

TTFB / FirstPaint 上昇

📈 どうやって計測してる?

  • DataStudioとGASでWebPagetestの計測結果をグラフ化する
  • gas-webpagetestでWebPagetestのパフォーマンス計測を自動化、可視化する | Web Scratch

    • uknmr/gas-webpagetest
※ 今後は DataDog の custom metrics でプロット・ビジュアライズ一括にしちゃおうと思ってる

なぜ TTFB が向上した?

Akamai CDN

  • フロントサーバの画面キャッシュに利用
  • GraphQL API のレスポンスもキャッシュ
  • 日本のエッジサーバ数が多い
  • すでに実績あり、導入が容易
  • コストも見積もりやすい

なぜ FP,FMP が向上した?

  • リニューアル前 CSR で埋めていた UI を SSR 化

    • frontend friendly な BFF の存在
    • サーバフェッチで埋められる容易性
  • Akamai 画面キャッシュも合わせて画面提供の速度が UP ⚡

1. リニューアルで得たもの
事業グロースに合わせて
変更容易性をもったコンポーネント例

変更容易性って例えば?

  • AB テストによる仮設検証, とにかくすばやく検証リリース
  • UI 決定後のリリースサイクルも早める工夫が必須
  • 条件: Google Analytics を使用した
    URL のクエリストリング付与によるリダイレクト

どうする 😤

  • redux state で状態注入する?

    • 🤔 わざわざ connect したくない
  • local state で振る舞い変える?

    • 🤔 ロジックが都度漏れていく

Context API により上位で A/B を判定
=> 下位でいつでも利用可能になる

分岐ロジックなしで JSX らしく宣言的に書きたい

こんなイメージ

Context Provider

  • 初期値を引数に Context を作成, Provider を作って下層に渡す
  • Next.js pages の getInitialProps で res.query を渡すイメージ

Next.js pages で使ってみるとこんな感じ

Context Consumer

再掲

リニューアルによって

  • 👍 TTFB, FP/FMP "一部の" パフォーマンスの向上
  • 👍 ABテストはじめコードの変更容易性を担保

2. Next.js
TypeScript との食い合わせ

Next.js の TypeScript との食い合わせってどうなの?

  • 2018 初頭プラグインで ts-loader によるコンパイル
  • 2018.3 プラグインで @babel/preset-typescript Babel によるコンパイル

    • Compile TypeScript with Babel by herrstucki · Pull Request #162 · zeit/next-plugins

最近の Canary ブランチ

  • Add automatic TypeScript setup by ijjk · Pull Request #7125 · zeit/next.js
  • pluggable ではなく Next.js に同梱されている
  • tsconfig.json があれば zero-config で始められそう
  • 型チェックの fork-ts-checker-webpack-plugin も同梱されている
  • @types/next にあった型定義ファイルもすでに同梱されている
Next.js w/TypeScript すぐ始めることができそう

とはいえ今のプロジェクト

  • monorepo 構成で TypeScript がコンパイルを担当するワークスペース

    • Express サーバを tsc でコンパイル
    • GraphQL API サーバを tsc でコンパイル
  • Babel に任せるよう書き換えた方が幸せかもしれない

Next.js を使って monorepo するなら
Next.js の依存に合わせた方が幸せかも

2. next.config.js
next.config.js だけでの運用は結構きつい

実際に next.config.js だけでの運用は結構きつい

  • server/client, 2 pass build の理解
  • webpack が出来そうなことが結構遠回り

    • Plugin どう挟むの
    • Plugin カスタマイズしたいのにどうすれば

next.config.js

デプロイ周りと Next.js

  • 現プロジェクトでのデプロイ

    • ➡️ GitHub Enterpise
    • ➡️ Travis CI Enterprise
    • ➡️ CodePipeline Source Artifact
    • ➡️ CodeBuild(4工程)
    • ➡️ DeployAPI, DeployS3
    • ➡️ DeployServer
  • デプロイする際に考慮すべきだったもの

    • BUILD_ID

BUILD_ID とは?

  • /_next/static/7Fi662KeEj0FPozITJ6xZ/pages/index.js

  • ソースコードをシードにしたハッシュ値ではない
  • 内部的に nanoid を利用してランダム値生成

何が困ったのか

  • デプロイ都合上 CodeBuild で3工程ほど並列で走っている
  • どの工程も同じ BUILD_ID でないと不都合

どう解決したのか

  • 置換と書き換えスクリプトで postbuild 的な後処理 📦
  • リポジトリで切られたタグのコミットハッシュを BUILD_ID として置換
  • 複数インスタンス or コンテナにデプロイする場合は要注意

最近の Canary ブランチ

  • generateBuildId という関数が用意されている

3. リニューアルで得られなかったもの
Perf 知見不足による失敗

au Webポータルですが
実は現在クライアントサイドで
React が動いていません

CSR はリニューアル以前の Backbone.js, jQuery で動いています

理由

  • クライアントパフォーマンスの影響で事業に貢献できそうにないという判断
  • 苦渋の選択で React による CSR から以前のスタックへ

何が問題だったか

  • React.hydrate が長い
  • CPU バウンドなロングタスクが多く TTI が遅延
  • 3rd party script との共存

3rd party script を優先せざるを得なかった

  • アプリケーションコードをコールバックキューに積んで遅延実行
    ➡️ 効果はある。TTI は遅延、オススメしない
  • <link rel="preload"> で 3rd party を投機的読み込み
    ➡️ 効果大。ブラウザの実行優先度を理解するのが良いかも

Priority on Browser(Chrome)

AddyOsmani.com - JavaScript Loading Priorities in Chrome

Uncanny Valley という現象

AddyOsmani.com - The Cost Of Client-side Rehydration

3. リニューアルで得られなかったもの
Perf どう取り組んでいくか

Progressive Hydration

progressive-rendering-frameworks-samples/hydrator.js at master · GoogleChromeLabs/progressive-rendering-frameworks-samples

SSR: 愚直にレンダー
CSR: cSU false, dangerlously... 空値 => hydrate 処理軽量
何某かのブラウザ API をフックに ReactDOM.hydrate

ConcurrentMode

  • React 16 Roadmap では ~Q2 2019 と記載
  • 当初は非同期描画モードのようなイメージ
  • 今は優先度付けし描画するモードと認識したほうがよさそう
// Two ways to opt in:
// 1. Part of an app (not final API)
<React.unstable_ConcurrentMode>
  <Something />
</React.unstable_ConcurrentMode>
// 2. Whole app (not final API)
ReactDOM.unstable_createRoot(domNode).render(<App />);

scheduler

Scheduling in React | Philipp Spiess

import {
  unstable_LowPriority,
  unstable_runWithPriority,
  unstable_scheduleCallback
} from "scheduler";

function sendDeferredAnalyticsNotification(value) {
  unstable_runWithPriority(unstable_LowPriority, () => {
    unstable_scheduleCallback(() => {
      sendAnalyticsNotification(value);
    });
  });
}

React Europe

  • Scheduling is the Future - Brandon Dail aka @aweary at @ReactEurope 2019 - YouTube
  • 標準化を進めるとなるとおそらく someday, future という言葉になりそう
  • isInputPending: Facebook's first browser API contribution - Facebook Code
  • 標準化の動きも今年始まったばかり

どの方法も良さそうに見えるが… 🤔

  • 無駄なコンポーネントのアップデートが走ってないか
  • CPU バウンドなロングタスクはどこにあるか
  • 泥臭いところにしかパフォーマンス改善はない

API や抜本的になにか変わることを期待してはダメ
小さなことからコツコツと積み重ね計測することが一番近道なはず

  • サービスによるが React SSR + CDN の効果は高い
  • Next.js w/ TypeScript はじめやすくなってる
  • Production 運用は config を覚悟して
  • パフォーマンスは抜本解決より地道な作業を
  • 五十番 - 高畠/ラーメン
  • 麺匠 竹虎 六本木店 - 六本木/ラーメン
  • 四川担々麺荘 彩たまや (サイタマヤ) - 川口/担々麺

以上
ありがとうございました

宣伝

フロントエンドランチ

  • mediba ではフロントエンドランチを毎週水曜やってます
  • Slack にてチャンネルを共有できます
  • いっしょにランチしませんかー 🍱
  • フロントエンドランチ - mediba

We are hiring.

  • Wantedly, Findy... FE 募集してます