Closed9

Nuxt Server Components を試す

jay-esjay-es

気になったので試してみる。

まずはスキャフォールディング。

npx nuxi@latest init

設定ファイルと app.vue だけのごく質素なプロジェクトが生成された(こんな感じ)。前はもっと色々なファイルがあった気がする。慣れている人には余計なものがなくてよさそうだが、Nuxt を初めて使うような場合は途方に暮れそう。

jay-esjay-es

公式ドキュメントに貼られている動画の内容を再現していく。
https://nuxt.com/docs/guide/directory-structure/components#server-components
https://www.youtube.com/watch?v=u1yyXe86xJM

0:53
まずは通常通りにコンポーネントを作る。

これはマークダウンの文字列を HTML にパースして表示するコンポーネントで、変換には markdown-it というファイルサイズが大きいライブラリを使う。

MarkdownComponent.vue
<script setup lang="ts">
import MarkdownIt from 'markdown-it';
const md = new MarkdownIt();
const string = `# my component
- markdown list
- rendered as html
- cool
`;
const html = md.render(string);
</script>

<template>
  <div v-html="html" />
</template>

<style scoped>
div {
  color: #15ca82;
}
</style>

依存パッケージもインストール。

npm i markdown-it
npm i -D @types/markdown-it

ルートコンポーネントにマウント。

app.vue
<template>
  <MarkdownComponent />
</template>

dev サーバーを立ち上げ、ページを表示。

リクエストに 208kB の markdown-it.js が含まれている。
トータルは 108 requests | 4.3 MB transferred | 4.2 MB resources となっている。

jay-esjay-es

ここからサーバーコンポーネントに変えていく。

1:37
現在、サーバーコンポーネントはまだ実験的機能なので、設定ファイルを修正して有効化する。

nuxt.config.js
export default defineNuxtConfig({
  devtools: { enabled: true },
+ experimental: {
+   componentIslands: true,
+ },
});

そしてコンポーネントファイルをリネームすれば変更完了。

MarkdownComponent.vue
↓
MarkdownComponent.server.vue

ブラウザーをリロード。

markdown-it.js はなくなり、トータルも 96 requests | 3.8 MB transferred | 3.8 MB resources と削減されている。
先程の結果からリクエストが 12 も減っていたり、容量も大幅に削減されていて単純な引き算になっていないのは dev サーバーだからか。

jay-esjay-es

ちゃんとビルドして確認してみた。
サーバーコンポーネントにすると、CSS ファイルが 1 つなくなり、JS ファイルの容量が 95 kB 減っている。

クライアントコンポーネント MarkdownComponent.vue バージョン

サーバーコンポーネント MarkdownComponent.server.vue バージョン

jay-esjay-es

2:12
サーバーコンポーネントにインタラクティブな処理を追加。
ボタンを押しても何も反応しない。
サーバー側で処理されるので、クライアント側には JS が配信されないため。

MarkdownComponent.server.vue
  <script setup lang="ts">
  import MarkdownIt from 'markdown-it';

  const md = new MarkdownIt();
  const string = `# my component
  - markdown list
  - rendered as html
  - cool
  `;
  const html = md.render(string);
+ const count = ref(0);
  </script>

  <template>
+   <button @click="count++">count is {{ count }}</button>
    <div v-html="html" />
  </template>
jay-esjay-es

2:29

状態を親に移動し、コンポーネントには props を渡すように変更。

app.vue
+ <script setup lang="ts">
+ const count = ref(0);
+ </script>

  <template>
+   <button @click="count++">increment</button>
-   <MarkdownComponent />
+   <MarkdownComponent :count="count" />
  </template>
MarkdownComponent.server.vue
  <script setup lang="ts">
  import MarkdownIt from 'markdown-it';

+ defineProps<{
+   count: number;
+ }>();

  const md = new MarkdownIt();
  const string = `# my component
  - markdown list
  - rendered as html
  - cool
  `;
  const html = md.render(string);
- const count = ref(0);
  </script>

  <template>
-   <button @click="count++">count is {{ count }}</button>
+   <p>count is {{ count }}</p>
    <div v-html="html" />
  </template>

カウントは更新されるようになるが、ボタンを押す(props が更新される)たびにリクエストが飛び、サーバーで再計算した結果がブラウザーに反映されている。

これらの結果、動画では「サーバーコンポーネントはインタラクティブである必要がないコンポーネントや、頻繁に更新されないコンポーネントに使うのがよい」と言っている。

後述する通り、こういう場合はスロットを使うのがよさそう。

jay-esjay-es

制限について。
動画は 2023/3/20 に投稿されたものでいくつかの情報は古くなっている。

制限1

3:18 非同期コンポーネントは使えない → v3.3.2 で解決済み。
https://github.com/nuxt/nuxt/issues/18500

制限2

3:24 スロットは使えない → v3.5 で解決済み。
https://github.com/nuxt/nuxt/pull/20284

制限3

3:27 (Next.js のように)サーバーコンポーネントの中にクライアントコンポーネントを置けない

これは現時点では未解決。
以下のようなコンポーネントを作って MarkdownComponent.server.vue 内に配置したが、ボタンを押してもカウントは増えなかった。(サーバーコンポーネント内に直接インタラクティブな処理を書いたときと同じ)

CountButton.vue
<script setup lang="ts">
const count = ref(0);
</script>

<template>
  <button @click="count++">count is {{ count }}</button>
</template>

その代わり、スロットを使えばサーバーコンポーネントの中にクライアントコンポーネントを(擬似的に)置くことはできる。これにより、ボタンがインタラクティブになる(props の時のような再リクエストもない)。

MarkdownComponent.server.vue
  </script>

  <template>
    <div>
+     <slot />
      <div v-html="html" />
    </div>
  </template>
app.vue
  <template>
    <MarkdownComponent>
      <CountButton />
    </MarkdownComponent>
  </template>

制限4

3:44 (コンポーネントの?)ソースをリモートから取得 → v3.7 で解決済み。
https://github.com/nuxt/nuxt/issues/12343

このスクラップは2023/10/02にクローズされました