10.8K Views
March 14, 25
スライド概要
CSR、SSR、SSG、ISR、PPRなどのレンダリング方式と、Cloudflare Workersなどのエッジランタイムについて解説します。
我々はなぜSSRを使うのか ついでにエッジランタイムも知ろう ashphy @ashphy@mstdn.nere9.help
※私はSSRしてません
Webページはどのように表示されるか? • CSR: Client Side Rendering ブラウザ サーバー S3 データベース GET index.html index.html GET script.js script.js GET /api/data data SELECT data
Webページはどのように表示されるか? • CSR: Client Side Rendering ブラウザ S3 GET index.html index.html GET script.js index.html サーバー データベース <html> <head> <script type=“module” src=“script.js” /> </head> <body><div id=“root”></div></body> </html> script.js GET /api/data data SELECT data
Webページはどのように表示されるか? • CSR: Client Side Rendering ブラウザ S3 GET index.html index.html GET script.js index.html サーバー データベース <html> <head> <script type=“module” src=“script.js” /> </head> <body><div id=“root”></div></body> </html> script.js GET /api/data data ↑ 中身は書かれていない SELECT data
Webページはどのように表示されるか? • CSR: Client Side Rendering ブラウザ S3 GET index.html index.html GET script.js index.html サーバー データベース <html> <head> <script type=“module” src=“script.js” /> </head> <body><div id=“root”></div></body> </html> script.js GET /api/data data SELECT data
Webページはどのように表示されるか? • CSR: Client Side Rendering ブラウザ サーバー S3 GET index.html index.html GET script.js データベース script.js function App() { useEffect(() => { fetch(”/api/data”) }); } script.js GET /api/data data SELECT data ※よい子はuseEffectでデータフェッチしないようにしましょう
Webページはどのように表示されるか? • CSR: Client Side Rendering ブラウザ サーバー S3 GET index.html index.html GET script.js データベース script.js function App() { useEffect(() => { fetch(”/api/data”) }); } script.js GET /api/data data ⇐ページが表示される SELECT data ※よい子はuseEffectでデータフェッチしないようにしましょう
もっと速くならないか?
もっと速くならないか? • CSR: Client Side Rendering ブラウザ サーバー S3 データベース GET index.html index.html GET script.js script.js GET /api/data 遅いなー data SELECT data
もっと速くならないか? • CSR: Client Side Rendering ブラウザ サーバー S3 GET index.html index.html データベース <ここで返してくれればいいのに GET script.js script.js GET /api/data data SELECT data
サーバでレンダリングすればいい
Server Side Rendering ブラウザ サーバー データベース GET index.html SELECT data HTML生成 index.html
Server Side Rendering ブラウザ index.html GET index.html <html> <head> <script type=“module” src=“script.js” /> </head> <body> <div id=“root”> <h1>Application</h1> <p>Test App</p> </div> index.html </body> </html> サーバー データベース SELECT data HTML生成
Server Side Rendering ブラウザ サーバー index.html GET index.html <html> <head> <script type=“module” src=“script.js” /> </head> <body> <div id=“root”> <h1>Application</h1> <p>Test App</p> </div> index.html </body> </html> データベース SELECT data HTML生成 返ってきたHTMLに 表示内容が書いてある
Server Side Rendering • サーバーでHTMLをレンダリングして返す方式 • メリット • 読み込みが速い • 巨大なJavaScriptを読み込んで実行しなくても内容を表示できる • SEOに有利 • OGPが使える • キャッシュが最適化できる • デメリット • TTFBが遅くなる
巨大なJSを読まなくても表示できる ブラウザ サーバー S3 データベース GET index.html index.html GET script.js CSRはここが巨大になりがち script.js GET /api/data data SELECT data CSRでもWebpackでコード分割してReact.lazyで遅延読み込みとかできる
SEOに有利 • Core Web Vitalsの結果が悪いとランキングが下がる可能性がある 読み込み速度 応答性 視覚的安定性 • どの程度影響があるかは公表されてないが、LCPの結果だけでランキン グは決まらない • まずはコンテンツを充実させろ • 検索エンジンのクローラーに確実にコンテンツを認識させられる Web Vitals
OGPが使える • Open Graph Protocol: OGPとは コレ
OGP: Open Graph Protocol • OGPは参照元ページの<head>に<meta>で書かれている • XにURLを貼るとXのクローラーがこれを取りに来る <html> <head> <meta name="og:description" content="おまえにNext.jsは必要ない" /> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:site" content="@DocswellSlide" /> <meta name="twitter:title" content="You Don't Need Next.js | ドクセル" /> <meta name="twitter:description" content="おまえにNext.jsは必要ない" /> <meta name="twitter:image" content="https://bcdn.docswell.com/page/YE6YN3VGJV.jpg <meta name="twitter:label1" content="Published by" /> <meta name="twitter:data1" content="ashphy" /> </head> </html>
OGP: Open Graph Protocol • OGPは参照元ページの<head>に<meta>で書かれている • XにURLを貼るとXのクローラーがこれを取りに来る <html> <head> <meta name="og:description" content="おまえにNext.jsは必要ない" /> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:site" content="@DocswellSlide" /> <meta name="twitter:title" content="You Don't Need Next.js | ドクセル" /> <meta name="twitter:description" content="おまえにNext.jsは必要ない" /> <meta name="twitter:image" content="https://bcdn.docswell.com/page/YE6YN3VGJV.jpg <meta name="twitter:label1" content="Published by" /> <meta name="twitter:data1" content="ashphy" /> </head> </html> XやFacebookのクローラーはJSを実行しない
OGP: Open Graph Protocol • OGPは参照元ページの<head>に<meta>で書かれている • XにURLを貼るとXのクローラーがこれを取りに来る <html> <head> <meta name="og:description" content="おまえにNext.jsは必要ない" /> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:site" content="@DocswellSlide" /> <meta name="twitter:title" content="You Don't Need Next.js | ドクセル" /> <meta name="twitter:description" content="おまえにNext.jsは必要ない" /> <meta name="twitter:image" content="https://bcdn.docswell.com/page/YE6YN3VGJV.jpg <meta name="twitter:label1" content="Published by" /> <meta name="twitter:data1" content="ashphy" /> </head> </html> 固定の情報しか返せない XやFacebookのクローラーはJSを実行しない
OGP: Open Graph Protocol • OGPは参照元ページの<head>に<meta>で書かれている • XにURLを貼るとXのクローラーがこれを取りに来る <html> ↓データベースにある情報 <head> <meta name="og:description" content="おまえにNext.jsは必要ない" /> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:site" content="@DocswellSlide" /> <meta name="twitter:title" content="You Don't Need Next.js | ドクセル" /> <meta name="twitter:description" content="おまえにNext.jsは必要ない" /> <meta name="twitter:image" content="https://bcdn.docswell.com/page/YE6YN3VGJV.jpg <meta name="twitter:label1" content="Published by" /> <meta name="twitter:data1" content="ashphy" /> </head> </html> 固定の情報しか返せない XやFacebookのクローラーはJSを実行しない
まだ速くできないか? • ユーザーのリクエストが来てからレンダリングするのは遅い • 事前にレンダリングしておけばいい
Static Site Generation: SSG ブラウザ ビルドサーバ サーバー データベース SELECT data HTML生成 GET index.html index.html アップロード
Static Site Generation: SSG ブラウザ ビルドサーバ サーバー データベース SELECT data 事前にHTMLを生成しておく GET index.html index.html アップロード HTML生成
Static Site Generation: SSG • 事前にレンダリングしておく方式 • レンダリング済みの結果を返すだけなので爆速 • 静的な内容しか返すことができない • 更新するときは再ビルドが必要になる • 全ページビルドし直す必要がある
Static Site Generation: SSG • 事前にレンダリングしておく方式 • レンダリング済みの結果を返すだけなので爆速 • 静的な内容しか返すことができない • 更新するときは再ビルドが必要になる • 全ページビルドし直す必要がある たまには更新したいが、ページが多すぎてしんどい
Incremental Static Regeneration: ISR • SSGで生成されたページに有効期限を設ける • 時間で決めたり、任意のタイミングでinvalidateしたりできる • 期限切れのページをSSRで動的に再生成する • 全ページを再ビルドしなくてよい • 分散システム上で動かすとキャッシュの同期が難しくなる • アクセス先によって見える情報が異なってしまうかも • Learn Next.js (App Router) をやるとこれからやるはず
SSRでも速くしたい!
レンダリング以外でも速くできるぞ • Largest Contentful Paint: 読み込みパフォーマンス
LCPのタイミング ブラウザ サーバー データベース GET index.html SELECT LCP data HTML生成 index.html HTML表示
LCPまでの様子を詳しく見てみよう https://web.dev/articles/optimize-lcp?hl=jp
LCPまでの様子を詳しく見てみよう 読み込み待ち HTML読み込み CSS読み込み https://web.dev/articles/optimize-lcp?hl=jp
LCPまでの様子を詳しく見てみよう 待ってるだけだから無駄な時間 読み込み待ち HTML読み込み CSS読み込み https://web.dev/articles/optimize-lcp?hl=jp
Time to First Byte: TTFB TTFB https://web.dev/articles/ttfb?hl=ja
Time to First Byte: TTFB TTFB 名前解決 https://web.dev/articles/ttfb?hl=ja
Time to First Byte: TTFB TTFB TCP 3wayハンドシェイク クライアント サーバー SYN SYN+ACK 名前解決 ACK https://web.dev/articles/ttfb?hl=ja
Time to First Byte: TTFB TTFB TCP 3wayハンドシェイク Client Hello SYN ACK サーバー クライアント クライアント サーバー SYN+ACK TLSハンドシェイク 名前解決 Server Hello Server Certificate Server Hello Done Client Key Exchange Change Cipher Spec Finished Change Cipher Spec Finished https://web.dev/articles/ttfb?hl=ja
Time to First Byte: TTFB TTFB TCP 3wayハンドシェイク Client Hello SYN ACK サーバー クライアント クライアント サーバー SYN+ACK TLSハンドシェイク 名前解決 レスポンス待ち Server Hello Server Certificate Server Hello Done Client Key Exchange Change Cipher Spec Finished Change Cipher Spec Finished https://web.dev/articles/ttfb?hl=ja
光の速さを感じてみよう • Tips: 光ファイバー中の光速の60-70%程度 • 東京から直線距離で光速だと • 大阪 : 500km – 1.7ms • 那覇 : 1600km – 5.3ms • サンノゼ : 8200km – 27.4ms • ニューヨーク:10800km – 36.0ms • ロンドン : 9600km – 32.0ms (実際にはアメリカ経由になりがち) アメリカが遠い
アメリカが遠いなら近くに置けばいい • エッジ (ここでの定義) • ユーザーに近いデータセンターで処理させたい
Cloudflare • 世界中に300カ所以上のエッジロケーションがある • 東京 • 大阪 • 福岡 • 那覇 Cloudflare Global Network | Data Center Locations
でも全部にサーバ置いたら高くね? • サーバレスにしたいよね • Cloudflare Workers がある
Cloudflare Workers • エッジロケーションで動くJavaScriptランタイム • Node.js ではなく V8が動く • ChromeのJavaScript処理系 • 0ms Cold Startsができる • (無料枠がデカイ)
V8 JavaScript engine ってなに? Node.jsは内部でV8を使っている Node.jsのアーキテクチャ Event Loop and the Big Picture — NodeJS Event Loop Part 1
Cold Startsとはなにか? • よく知ってるAWS Lambdaの場合を見てみよう • Google Cloud派の人はCloud Run第2世代で置き換えてください • マイクロVM Firecracker • 125msで起動できる • そこからNode.jsを起動する • リクエストを受け付けられるようになるまで 時間がかかる A closer look at AWS Lambda
Cold Startsとはなにか? • よく知ってるAWS Lambdaの場合を見てみよう • Google Cloud派の人はCloud Run第2世代で置き換えてください • マイクロVM Firecracker • 125msで起動できる • そこからNode.jsを起動する • リクエストを受け付けられるようになるまで 時間がかかる EC2 ベアメタルインスタンス A closer look at AWS Lambda
Cold Startsとはなにか? • よく知ってるAWS Lambdaの場合を見てみよう • Google Cloud派の人はCloud Run第2世代で置き換えてください • マイクロVM Firecracker 顧客ごとに分離 • 125msで起動できる • そこからNode.jsを起動する • リクエストを受け付けられるようになるまで 時間がかかる EC2 ベアメタルインスタンス A closer look at AWS Lambda
Cold Startsとはなにか? • よく知ってるAWS Lambdaの場合を見てみよう • Google Cloud派の人はCloud Run第2世代で置き換えてください • マイクロVM Firecracker 顧客ごとに分離 • 125msで起動できる • そこからNode.jsを起動する • リクエストを受け付けられるようになるまで 時間がかかる 実行環境 一度に1つの呼び出しにつき実行環境1つ 実行後もしばらく生き残り再利用される EC2 ベアメタルインスタンス A closer look at AWS Lambda
Cold Startsとはなにか? • よく知ってるAWS Lambdaの場合を見てみよう • Google Cloud派の人はCloud Run第2世代で置き換えてください • マイクロVM Firecracker 顧客ごとに分離 • 125msで起動できる • そこからNode.jsを起動する • リクエストを受け付けられるようになるまで 時間がかかる 実行環境 一度に1つの呼び出しにつき実行環境1つ 実行後もしばらく生き残り再利用される EC2 ベアメタルインスタンス A closer look at AWS Lambda
Cold Startsとはなにか? • よく知ってるAWS Lambdaの場合を見てみよう • Google Cloud派の人はCloud Runで置き換えてください • マイクロVM Firecracker • 125msで起動できる • そこからNode.jsを起動する • リクエストを受け付けられるようになるまで 時間がかかる サーバありなら0msなのに… A closer look at AWS Lambda
V8 isolation • Cloudflare WorkersはV8 isolationを使っている • 実行コンテキストを分離するための機能 • マルチスレッドで動くので起動も速いし、メモリ消費量も少ない • 5msで起動できる Cloud Computing without Containers
でもまだ起動に5msかかる… • TLSハンドシェイク中に起動しちゃえ Eliminating cold starts with Cloudflare Workers
でもまだ起動に5msかかる… • TLSハンドシェイク中に起動しちゃえ リクエスト Eliminating cold starts with Cloudflare Workers
でもまだ起動に5msかかる… • TLSハンドシェイク中に起動しちゃえ TLS ハンドシェイク リクエスト Eliminating cold starts with Cloudflare Workers
でもまだ起動に5msかかる… • TLSハンドシェイク中に起動しちゃえ TLS ハンドシェイク どうせこのあとリクエスト来るし 先に起動しちゃえ! リクエスト Eliminating cold starts with Cloudflare Workers
でもデータベースが遠ければ意味がなくない? • それはそう 長距離の往復が発生 Smart Placement · Cloudflare Workers docs
Cloudflare Smart Placement • 自動でバックエンドに近い場所のWorkerを呼び出す 地理的に近い場所 Smart Placement · Cloudflare Workers docs
エッジでもデータベースを使いたい • Cloudflare D1 • エッジで動くSQLite • ユーザーに近いところにリードレプリカを配置する • 不思議な力でリードレプリカが同期される • テーブルサイズなどの制限が強い • (RDBが無料で使える) Building D1: a Global Database
データは置けないけどキャッシュなら • Cloudflare Workers KV • エッジで動くKey-Valueストレージ • データーベースをエッジに持ってこれなくても近いところでキャッシュ
どうしても遅いデータがある • SSRはすべてをレンダリングしてからでないとレスポンスできない • Streaming SSR • できたところから配信して段階的にレンダリングする • 1つのHTTP通信の中に詰め込むので無駄がない • でもどう分割する? → <Suspense>
React <Suspense> • 子要素が読み込み完了するまでフォールバックを表示する <div> <h1>{artist.name}</h1> <Suspense fallback={<Loading />}> <Albums artistId={artist.id} /> </Suspense> </div>
<Suspense> • 子要素が読み込み完了するまでフォールバックを表示する <div> <h1>{artist.name}</h1> <Suspense fallback={<Loading />}> <Albums artistId={artist.id} /> </Suspense> </div> 読み込み中表示
<Suspense> • 子要素が読み込み完了するまでフォールバックを表示する <div> <h1>{artist.name}</h1> <Suspense fallback={<Loading />}> <Albums artistId={artist.id} /> </Suspense> </div> 読み込み中表示 重い処理
<Suspense> • 子要素が読み込み完了するまでフォールバックを表示する <div> <h1>{artist.name}</h1> <Suspense fallback={<Loading />}> <Albums artistId={artist.id} /> </Suspense> </div> 外側の要素を先に描画できる 読み込み中表示 重い処理
ごめんエッジSSR速くなかったわ • Vercel役員、突然の懺悔
ごめんエッジSSR速くなかったわ • エッジでレンダリングすると速くならない • データベースの近くで実行しないと速くならない それはそう • ほとんどのデータはグローバルに複製されない それはそう • v8じゃなくてNode.jsにしたら速くなった 根拠がよくわからない • セキュリティ • 多くの顧客は他サービスへのプライベート接続が必要 それはそう • データをグローバルに分散させることに懸念がある それはそう • v0では • Node.jsでStreaming SSRが高速だった • そこでPartial Prerenderingを実験してる
Pertial Prerendering: PPR • Next.js v14で実装 • まだExperimental Partial Prerendering
レンダリングはコンポーネント単位 • レンダリングは「ページ単位」から「コンポーネント単位」へ • コンポーネント単位でレンダリングが静的か動的かが決まる • 1ページの中に静的/動的レンダリングが混ざるようになる
まとめ • Server Side Renderingはサーバ上でレンダリングして返す技術 • 早く表示できるのでユーザー体験が向上する • 動的なOGPに対応できる • 事前に用意しておけるならSSG、ISR • エッジコンピューティング • ユーザーになるべく近いところで実行する • Node.jsじゃないので起動が速くて、詰め込めるので安い • ページ単位で静的/動的レンダリングが変わっていたが、 今後はコンポーネント単位で切り替わるようになる
質疑応答用資料
React.lazy
103 Early Hints Early Hints: How Cloudflare Can Improve Website Load Times by 30%