Slide 1

Slide 1 text

STORES 株式会社 wattanx Deep dive into Nuxt Server Components

Slide 2

Slide 2 text

Ryota Watanabe(wattanx) ・STORES 株式会社 ソフトウェアエンジニア。 ・猫が好き。大阪在住。 ・Nuxt コミッター、Nuxt Bridge のメンテナ。 自己紹介

Slide 3

Slide 3 text

Agenda 3 Nuxt のレンダリングモードと課題 Nuxt Server Components について Nuxt Server Components の活用方法 作って学ぶ Nuxt Server Components 他の似たような技術との違い 01 02 03 04 05

Slide 4

Slide 4 text

はじめに ちょっとアンケート

Slide 5

Slide 5 text

本セッションの狙い ● どんなことができるのか ● どんな課題を解決するのか ● どんな仕組みなのか ● 他の技術との違いは何なのか Nuxt Server Components について知ってもらう

Slide 6

Slide 6 text

Nuxt のレンダリングモードについて

Slide 7

Slide 7 text

Nuxt のレンダリングモードについて ● Client-Side Rendering ● Server-Side Rendering Nuxt がサポートしているレンダリングモード

Slide 8

Slide 8 text

Nuxt のレンダリングモードについて ● 動的にデータを取得する ● 取得したデータを JavaScript を使って画面に表示する ● JavaScript で画面遷移を実現 Client-Side Rendering

Slide 9

Slide 9 text

Nuxt のレンダリングモードについて (Client-Side Rendering) Browser Server GET / GET /bundle.js Render JS をダウンロードする レンダリングされる 空の HTML を ダウンロードする HTML JS

Slide 10

Slide 10 text

Nuxt のレンダリングモードについて (Client-Side Rendering) ● パフォーマンス ● 検索エンジンの最適化 Client-Side Rendering の課題

Slide 11

Slide 11 text

Nuxt のレンダリングモードについて (Client-Side Rendering) ● パフォーマンス ○ ブラウザが JavaScript をダウンロードし、解析・実行するのを待 たなければならない ○ そのため、コンテンツが表示されるまで遅い ● 検索エンジンの最適化 ○ クローラーとの相性が悪い ○ JavaScript を解釈できるクローラーもあるが...

Slide 12

Slide 12 text

Nuxt のレンダリングモードについて Server-Side Rendering が課題を解決する

Slide 13

Slide 13 text

Nuxt のレンダリングモードについて (Server-Side Rendering) ● Client-Side Rendering の課題を解決する ● Server は完全にレンダリングされた HTML をブラウザに返す Server-Side Rendering(Universal Rendering)

Slide 14

Slide 14 text

Nuxt のレンダリングモードについて (Server-Side Rendering) Browser Server GET / GET /bundle.js Hydration HTML JS レンダリング済みの HTML をダウンロードする JS をダウンロードする Hydration される Render

Slide 15

Slide 15 text

Nuxt のレンダリングモードについて (Server-Side Rendering) ● Server から受け取った HTML を Interactive にする ○ Server から受け取った HTML はイベントリスナがセット されていない ○ イベントリスナをセットする作業が Hydration Hydration とは

Slide 16

Slide 16 text

Nuxt のレンダリングモードについて まだ課題が残っている

Slide 17

Slide 17 text

Nuxt のレンダリングモードについて ● bundle size の増大 残っている課題

Slide 18

Slide 18 text

Nuxt のレンダリングモードについて markdown で書いたコンテンツを HTML に変換して表示する例 以下のソースコードは Server, Client 両方で実行される (Server-Side Rendering を想定)

Slide 19

Slide 19 text

Nuxt のレンダリングモードについて Q: なぜ Server, Client 両方で実行する必要があるのか A: 両方実行して同じ DOM 構造にならないと Hydration できないから

Slide 20

Slide 20 text

Nuxt のレンダリングモードについて Server, Client 両方で実行されるため 以下のライブラリは Client の bundle にも含まれる sanitize-html: 194.8Kb (80.9Kb gzipped) marked: 35.9Kb (11.2Kb gzipped) でかい...減らしたい...

Slide 21

Slide 21 text

Nuxt のレンダリングモードについて fs などは Server でしか使えないので、 API Routes を使っている

Slide 22

Slide 22 text

Nuxt のレンダリングモードについて 本当はこう書きたいし、bundle size も減らしたい Server でしか使えないけど、 Server でだけ実行できれば... Hydration が必要ないので、 Server でだけ実行できれば...

Slide 23

Slide 23 text

Nuxt のレンダリングモードについて Server でだけレンダリングされる コンポーネントがあれば解決する

Slide 24

Slide 24 text

Nuxt のレンダリングモードについて そんな都合のいい仕組みがあるんですか?

Slide 25

Slide 25 text

Nuxt Server Components

Slide 26

Slide 26 text

Nuxt Server Components ● Nuxt 3 から導入された機能 ○ まだ experimental ● Server でだけレンダリングされるコンポーネントをつくることが できる Nuxt Server Components

Slide 27

Slide 27 text

Nuxt Server Components ● Server でだけ実行されて、Hydration されない ○ Client bundle に不要な JS も含まれない ● Server が必須ではない。 ○ ビルド時にアプリケーションで使用される Server Component を プリレンダリングする ○ そのため、完全に静的なサイトであっても動作する

Slide 28

Slide 28 text

Nuxt Server Components Server Component 使わない場合 Server Component 使う場合 約 66.4 % 減 詳しいソースコード https://github.com/wattanx/mini-nuxt-sc/pull/1

Slide 29

Slide 29 text

Nuxt Server Components ● 拡張子を .server.vue にする ● NuxtIsland コンポーネントを使う Nuxt Server Components の使い方

Slide 30

Slide 30 text

Nuxt Server Components experimental.componentIslands を 有効化する

Slide 31

Slide 31 text

Nuxt Server Components 拡張子を .server.vue にすると Server Component になる 書きたかったコードが書ける!

Slide 32

Slide 32 text

Nuxt Server Components 使う側は import なしで使用する もしくは #components からの import (実際のパスから import するのはまだサポートできていない)

Slide 33

Slide 33 text

Nuxt Server Components ● Server Component の基盤 ○ .server.vue は NuxtIsland コンポーネントを使ったコー ドに変換される ● components/islands にコンポーネントを作ると、Server で だけレンダリングできるコンポーネントになる NuxtIsland コンポーネント

Slide 34

Slide 34 text

Nuxt Server Components NuxtIsland コンポーネントの使い方

Slide 35

Slide 35 text

Nuxt Server Components 静的な部分を島(Island)としている NuxtIsland は動的な部分に静的なコンテンツを埋め込むアーキテクチャ 海: 動的(JS で Interactive になっている) 島: 静的(Interactive ではない)

Slide 36

Slide 36 text

Nuxt Server Components ● Async Component が使える ● Server Component と Client Component を入れ子にできる ● Server Component のうち、一部だけ Hydration できる Nuxt Server Components でできること(一部抜粋)

Slide 37

Slide 37 text

Nuxt Server Components Async Component が書ける

Slide 38

Slide 38 text

Nuxt Server Components Slot を使うことで Server Component と Client Component を入れ子 にする

Slide 39

Slide 39 text

Nuxt Server Components nuxt-client ディレクティブを使うことで Server Component の一部を Hydration することができる

Slide 40

Slide 40 text

Nuxt Server Components の活用方法

Slide 41

Slide 41 text

Nuxt Server Components の活用方法 ● ブログ ○ ほとんどの場合インタラクションが必要ないため ● 時限式のコンポーネント Nuxt Server Components を活かせるもの

Slide 42

Slide 42 text

Nuxt Server Components の活用方法 ● 時限式のコンポーネント ○ https://www.mizdra.net/entry/2024/08/27/190853 ○ ある特定の日に表示するようなコンポーネント ○ Client Component だと Client bundle にその日まで出したくな い情報が含まれてしまう ○ これは Nuxt Server Components においても効果的

Slide 43

Slide 43 text

Nuxt Server Components の活用方法 ● 頻繁に props が変更されるコンポーネント ○ props が変わるたびに Server へのリクエストが発生するため Nuxt Server Components を活かせないもの

Slide 44

Slide 44 text

Nuxt Server Components の活用方法 ● Server Component 同士の入れ子 ○ Server Component のレンダリングには Server へのリクエ スト が必要 ○ なので、入れ子にするとオーバーヘッドが増える ○ 理由は仕組みを理解するとわかる (このあと解説予定) 気をつけた方がいいところ

Slide 45

Slide 45 text

作って学ぶ Nuxt Server Components

Slide 46

Slide 46 text

作って学ぶ Nuxt Server Components Nuxt Server Components を作って仕組みを解説します

Slide 47

Slide 47 text

作って学ぶ Nuxt Server Components ● Server Component の基盤である NuxtIsland ● コンポーネントを Server でだけレンダリングする仕組み 最終的にできあがるもの

Slide 48

Slide 48 text

作って学ぶ Nuxt Server Components ● Vue で Server-Side Rendering できる環境 ● npm create vite-extra@latest app -- --template ssr-vue-ts 準備するもの

Slide 49

Slide 49 text

作って学ぶ Nuxt Server Components Nuxt Server Components の流れ (Server-Side Rendering の場合)

Slide 50

Slide 50 text

作って学ぶ Nuxt Server Components Browser Server GET / GET /bundle.js Hydration HTML JS Render GET /__nuxt_island Render component HTML Not Hydrated

Slide 51

Slide 51 text

作って学ぶ Nuxt Server Components Browser Server GET / GET /bundle.js Hydration HTML JS Render GET /__nuxt_island Render component HTML Not Hydrated 従来のレンダリングとの差分

Slide 52

Slide 52 text

作って学ぶ Nuxt Server Components Nuxt Server Components の構成要素 ● Vue コンポーネントをレンダリングするエンドポイント ● Server で Vue コンポーネントをレンダリングする処理 ● Vue コンポーネントをレンダリングするエンドポイントへリクエストを投げる コンポーネント

Slide 53

Slide 53 text

作って学ぶ Nuxt Server Components Nuxt Server Components の構成要素 ● Vue コンポーネントをレンダリングするエンドポイント 👈 まずはここ ● Server で Vue コンポーネントをレンダリングする処理 ● Vue コンポーネントをレンダリングするエンドポイントへリクエストを投げる コンポーネント

Slide 54

Slide 54 text

作って学ぶ Nuxt Server Components Browser Server GET / GET /bundle.js Hydration HTML JS Render GET /__nuxt_island Render component HTML Not Hydrated ここ

Slide 55

Slide 55 text

作って学ぶ Nuxt Server Components URL からコンポーネント名や props を抽出する処理をつくる 実際のURL /__nuxt_island/CodeExample_XdPPUtbPQW.json?props={"count":4} URL からコンポーネント名: CodeExample が抽出 クエリパラメータから props: { count: 4 } が抽出

Slide 56

Slide 56 text

作って学ぶ Nuxt Server Components エンドポイントを作る コンポーネント名や props を抽出して context に詰め込む render 関数はあとで説明

Slide 57

Slide 57 text

作って学ぶ Nuxt Server Components ● Server-Side Rendering 中に利用したいデータを追加できる ○ Nuxt では Request Event を詰めたりしている ○ useSSRContext を使うことでコンポーネント内からアクセ スできる ● https://ja.vuejs.org/api/ssr.html#ssr-context SSR Context

Slide 58

Slide 58 text

作って学ぶ Nuxt Server Components Nuxt Server Components の構成要素 ● Vue コンポーネントをレンダリングするエンドポイント ● Server で Vue コンポーネントをレンダリングする処理👈 次はここ ● Vue コンポーネントをレンダリングするエンドポイントへリクエストを投げる コンポーネント

Slide 59

Slide 59 text

作って学ぶ Nuxt Server Components Browser Server GET / GET /bundle.js Hydration HTML JS Render GET /__nuxt_island Render component HTML Not Hydrated ここ

Slide 60

Slide 60 text

作って学ぶ Nuxt Server Components コンポーネント名とコンポーネントのマッピングをつくる

Slide 61

Slide 61 text

作って学ぶ Nuxt Server Components 動的にコンポーネントをレンダリングするコンポーネントをつくる レンダリングしたいコンポーネントを取得 動的にコンポーネントをレンダリングできる

Slide 62

Slide 62 text

作って学ぶ Nuxt Server Components Server で Vue コンポーネントをレンダリングするには

Slide 63

Slide 63 text

作って学ぶ Nuxt Server Components createSSRApp と renderToString を使う https://ja.vuejs.org/guide/scaling-up/ssr.html

Slide 64

Slide 64 text

作って学ぶ Nuxt Server Components createSSRApp と renderToString、 動的にコンポーネントをレンダリングする処理を組み合わせる Server で Vue コンポーネントをレンダリングする 動的にコンポーネントをレンダリングする

Slide 65

Slide 65 text

作って学ぶ Nuxt Server Components この render 関数は /__nuxt_island で実行される

Slide 66

Slide 66 text

作って学ぶ Nuxt Server Components Nuxt Server Components の構成要素 ● Vue コンポーネントをレンダリングするエンドポイント ● Server で Vue コンポーネントをレンダリングする処理 ● Vue コンポーネントをレンダリングするエンドポイントへリクエストを投げる コンポーネント 👈 次はここ

Slide 67

Slide 67 text

作って学ぶ Nuxt Server Components Browser Server GET / GET /bundle.js Hydration HTML JS Render GET /__nuxt_island Render component HTML Not Hydrated ここ

Slide 68

Slide 68 text

作って学ぶ Nuxt Server Components だいぶ簡略化したコード /__nuxt_island へリクエストを投げる Server でレンダリングされているので Client-Side ではprops が変わったらリクエストを投げる HTML 文字列 レンダリングする Hydration されない

Slide 69

Slide 69 text

作って学ぶ Nuxt Server Components これが NuxtIsland コンポーネントです

Slide 70

Slide 70 text

作って学ぶ Nuxt Server Components NuxtIsland は /__nuxt_island へリクエストを投げて レスポンスの HTML 文字列をレンダリングする

Slide 71

Slide 71 text

作って学ぶ Nuxt Server Components コンポーネント単位でレンダリングする仕組みが完成 🎉

Slide 72

Slide 72 text

作って学ぶ Nuxt Server Components このままでは Server Component の子に interactive なコンポーネントをもつことができない

Slide 73

Slide 73 text

作って学ぶ Nuxt Server Components Slot を使うことで Server Component と Client Component を入れ子に できる

Slide 74

Slide 74 text

作って学ぶ Nuxt Server Components Q: Slot を含めてレンダリングするとどうなりますか? A: ただのHTML 文字列になるので、 Hydration されません (イベントハンドラなどの情報がない)

Slide 75

Slide 75 text

作って学ぶ Nuxt Server Components どうやって完全にレンダリングされた HTML 文字列に Slot を差し込むのか

Slide 76

Slide 76 text

作って学ぶ Nuxt Server Components Teleport が活躍する

Slide 77

Slide 77 text

作って学ぶ Nuxt Server Components Client Component を入れ子にするために必要なこと ● をビルド時に独自の placeholder に変換する ● Slot のコンテンツを Teleport で囲んでレンダリングする ● placeholder 部分に Slot のコンテンツを差し込む

Slide 78

Slide 78 text

作って学ぶ Nuxt Server Components Slot の部分がコンテンツに置き換わった状態で レンダリングされるのを目指す

Slide 79

Slide 79 text

作って学ぶ Nuxt Server Components Client Component を入れ子にするために必要なこと ● をビルド時に独自の placeholder に変換する 👈 まずはここ ● Slot のコンテンツを Teleport で囲んでレンダリングする ● placeholder 部分に Slot のコンテンツを差し込む

Slide 80

Slide 80 text

作って学ぶ Nuxt Server Components ビルド時に独自の placeholder に変換する

Slide 81

Slide 81 text

作って学ぶ Nuxt Server Components Client Component を入れ子にするために必要なこと ● をビルド時に独自の placeholder に変換する ● Slot のコンテンツを Teleport で囲んでレンダリングする 👈 次はここ ● placeholder 部分に Slot のコンテンツを差し込む

Slide 82

Slide 82 text

作って学ぶ Nuxt Server Components こんな感じに書き換える

Slide 83

Slide 83 text

作って学ぶ Nuxt Server Components Client-Side Rendering 時 Server-Side Rendering 時 Teleport 部分をわかりやすいように書くと以下のようなコードになる

Slide 84

Slide 84 text

作って学ぶ Nuxt Server Components Teleport 部分をわかりやすいように書くと以下のようなコードになる これを Server Side でレンダリングするとどうなるかが重要

Slide 85

Slide 85 text

作って学ぶ Nuxt Server Components ● コンポーネントにあるテンプレートの一部を、そのコンポー ネントの DOM 階層の外側に存在する DOM ノードに「テレ ポート」できる組み込みコンポーネント ● https://ja.vuejs.org/guide/built-ins/teleport.html Teleport

Slide 86

Slide 86 text

作って学ぶ Nuxt Server Components v-if が true になったとき body 要素に挿入される

Slide 87

Slide 87 text

作って学ぶ Nuxt Server Components Server-Side でレンダリングしたとき、SSR Context の teleports に Teleport 内のコンテンツが公開される (SSR Context は renderToString の第二引数に渡すオブジェクト) https://ja.vuejs.org/guide/scaling-up/ssr.html#teleports

Slide 88

Slide 88 text

作って学ぶ Nuxt Server Components

Slide 89

Slide 89 text

作って学ぶ Nuxt Server Components Teleport を含む NuxtIsland コンポーネントを Server-Side Rendering すると以下のように Teleport 内のコンテンツが手に入る

Slide 90

Slide 90 text

作って学ぶ Nuxt Server Components Client Component を入れ子にするために必要なこと ● をビルド時に独自の placeholder に変換する ● Slot のコンテンツを Teleport で囲んでレンダリングする ● placeholder 部分に Slot のコンテンツを差し込む 👈 次はここ

Slide 91

Slide 91 text

作って学ぶ Nuxt Server Components ビルド時に独自の placeholder に変換した

Slide 92

Slide 92 text

作って学ぶ Nuxt Server Components NuxtIsland がレンダリングされると data-island-uid がセットされる

Slide 93

Slide 93 text

作って学ぶ Nuxt Server Components uid=xxx,slot=default を見るとどこに差し込むべきかわかる 一致

Slide 94

Slide 94 text

作って学ぶ Nuxt Server Components 差し込む場所がわかったので Teleport のコンテンツを差し込む (Server でレンダリングするときに正規表現で置換している) ここに差し込んで こうなる

Slide 95

Slide 95 text

作って学ぶ Nuxt Server Components Server で出来上がった HTML

Slide 96

Slide 96 text

作って学ぶ Nuxt Server Components Client-Side Rendering 時

Slide 97

Slide 97 text

作って学ぶ Nuxt Server Components to に指定した attribute が一致するので Teleport される

Slide 98

Slide 98 text

作って学ぶ Nuxt Server Components Teleport されるとこうなる

Slide 99

Slide 99 text

Nuxt のレンダリングモードについて Q: なぜ Server, Client 両方で実行する必要があるのか A: 両方実行して同じ DOM 構造にならないと Hydration できないから

Slide 100

Slide 100 text

作って学ぶ Nuxt Server Components Server で出来上がった HTML と比較すると Server Client

Slide 101

Slide 101 text

作って学ぶ Nuxt Server Components Server で出来上がった HTML と比較すると Server Client 完全に一致

Slide 102

Slide 102 text

作って学ぶ Nuxt Server Components Hydration できる 🎉

Slide 103

Slide 103 text

作って学ぶ Nuxt Server Components Nuxt Server Components 完全に理解した 🎉

Slide 104

Slide 104 text

作って学ぶ Nuxt Server Components ● Minimal Nuxt Server Components ● Nuxt に依存しないように作った ● Vue を使えば作れる https://github.com/wattanx/mini-nuxt-sc

Slide 105

Slide 105 text

他の似たような技術との違い

Slide 106

Slide 106 text

他の似たような技術との違い ● バンドル前に事前にレンダーされるコンポーネント ● Server がなくても実行できる (Nuxt と同じ) ● RSC Payload と呼ばれる特別なデータ形式にレンダリングされる ○ Nuxt Server Components は HTML にレンダリングされる React Server Components

Slide 107

Slide 107 text

他の似たような技術との違い ● 静的な部分に動的(JS で Interactive)な部分を埋め込むアプローチ ● Nuxt Server Components とは逆 ○ Nuxt は動的な部分に静的なコンテンツを埋め込むというアプローチ Island Architecture

Slide 108

Slide 108 text

まとめ

Slide 109

Slide 109 text

● Nuxt Server Components は動的な部分に静的なコンテンツを埋め込 む技術 ● Nuxt Server Components を使うことで client bundle を減らすこと ができる ● Nuxt Server Components は Teleport の使い方がおもしろい まとめ

Slide 110

Slide 110 text

まとめ ● Remote Source もレンダリングできる ● Server Page を作ることができる ● Lazy Server Component を作ることができる 話せていないことがいっぱいある

Slide 111

Slide 111 text

まとめ まだ experimental なので使ってフィードバックしよう