【Nuxt3】NuxtIslandとサーバーコンポーネントで軽量化！

閲覧頂き、ありがとうございます🙇

この記事は、Nuxt3で新たに導入された`<NuxtIsland>コンポーネント`について、
ざっくりわかったことを紹介します！

# 前提

| name | version |
| - | - |
| Nuxt.js(Vue.js) | v3.x |

# 経緯

* Nuxt3のアプリケーションに対して速度改善を行うこととなり、  
ドキュメントを漁っていたところこちらにたどり着いた🔍

# ざっくり3行まとめ💨

* `NuxtIslandコンポーネント` は`サーバーコンポーネント`🏝️
* `JSを含めない非インタラクティブなコンポーネント`で、
JSを含まないから`バンドルサイズを軽減`できる☁️
* コンポーネントファイル名が`.server.vue`とすることでも可能📦

# NuxtIsland と サーバーコンポーネント

## NuxtIslandコンポーネント とは 🏝️

> Nuxt provides the <NuxtIsland> component to render a non-interactive component without any client JS.
>> Nuxtは<NuxtIsland>コンポーネントを提供し、クライアントJSなしで非インタラクティブなコンポーネントをレンダリングします。

> When rendering an island component, the content of the island component is static, thus no JS is downloaded client-side.
Changing the island component props triggers a refetch of the island component to re-render it again.
>> アイランドコンポーネントをレンダリングする際、アイランドコンポーネントのコンテンツは静的であるため、クライアントサイドでJSがダウンロードされることはありません。
アイランドコンポーネントのpropsを変更すると、アイランドコンポーネントの再読み込みがトリガされます。

## サーバーコンポーネント とは 📦

> Server components allow server-rendering individual components within your client-side apps. It's possible to use server components within Nuxt, even if you are generating a static site. That makes it possible to build complex sites that mix dynamic components, server-rendered HTML and even static chunks of markup.
>> サーバコンポーネントを使用すると、クライアントサイドのアプリ内で個々のコンポーネントをサーバレンダリングすることができます。静的なサイトを生成する場合でも、Nuxt内でサーバーコンポーネントを使用することができます。そのため、動的コンポーネント、サーバレンダリングされたHTML、さらには静的なマークアップの塊が混在する複雑なサイトを構築することができます。

> Standalone server components will always be rendered on the server, also known as Islands components.
When their props update, this will result in a network request that will update the rendered HTML in-place.
>> スタンドアロンのサーバーコンポーネントは、常にサーバー上でレンダリングされます、 アイランド・コンポーネントとしても知られています。
それらのpropsが更新されると、レンダリングされたHTMLをインプレースで更新するネットワークリクエストが発生します。

## まとめると🔍

公式ドキュメントの本文を翻訳したものを並べたが、
要するに、

* NuxtIslandコンポーネントは、`アイランドコンポーネント`
* アイランドコンポーネントは、`スタンドアロンのサーバーコンポーネント`
* アイランドコンポーネントは、`静的でクライアント側でJSがダウンロードされない`
* アイランドコンポーネントは、`非インタラクティブ(ユーザーとのやり取りをしない)なコンポーネント`
* アイランドコンポーネントは、Propsが更新されると`ネットワークリクエストが走る`

になります！

僕の中での使い所としては、
「**クライアント側で動的にデータを切り替える動作がない箇所(CSSは除く)**」
になるのではないかと思います！

そうすることでJSを削減し、
ダウンロード量も減らせて軽量なWebサイト/アプリにできます💯

これが`Astro`とかで聞く、`アイランドアーキテクチャ`と呼ばれるものかと思う💡
(ページの一部がインタラクティブにする、その様が島(アイランド)のようなこと)

![アイランド.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130422/0cd08706-b5db-058c-9b78-6484462c96bc.png)

# 使い方🧑‍💻

## NuxtIslandコンポーネントの場合🏝️

必要なこととして、

* `componentIslands` を `true` にする
* `components/islands/` 配下にコンポーネントを置く
* `<NuxtIsland>` タグでコンポーネントを読み込む

になります！

```nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    componentIslands: true,
  },
})

```

```components/islands/AppTextIsland.vue
<script setup lang="ts">
const props = defineProps<{
  text: string
}>()
</script>
<template>
  <p>{{ props.text }}</p>
</template>

```

```pages/index.vue
<template>
  <div>
    <!-- nameにコンポーネント名 propsはオブジェクトとしてまとめて渡す -->
    <NuxtIsland name="AppTextIsland" :props="{ title: 'ページタイトル(NuxtIsland)' }"/>
  </div>
</template>
```

## サーバーコンポーネントの場合📦

必要なこととして、

* `componentIslands` を `true` にする
* コンポーネントファイル名を `.server.vue` にする

になります！

```nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    componentIslands: true,
  },
})

```

```components/AppText.server.vue
<script setup lang="ts">
const props = defineProps<{
  text: string
}>()
</script>
<template>
  <p>{{ props.text }}</p>
</template>

```

```pages/index.vue
<template>
  <div>
    <!-- 通常通りのコンポーネントの使い方 -->
    <AppTextServer title="ページタイトル(サーバー)" />
  </div>
</template>
```


# 動作の比較🔍

## 今回比較するコンポーネント

多少のJSを含むものとしたかったため、
以下のような`moment.js`をimportしたコンポーネントを用意し、
3パターンで親コンポーネントから呼び出しました。

```AppTest.vue
<script setup lang="ts">
import moment from 'moment'
const date = moment().format('MMMM Do YYYY, h:mm:ss a')
const props = defineProps<{
  title: string
  count: number
}>()
const doAlert = (title: string) => {
  alert(title)
}
</script>
<template>
  <div>
    <h1>{{ props.title }} - {{ props.count }}</h1>
    <div>{{ date }}</div>
    <button @click="doAlert(props.title)">アラート発出</button>
  </div>
</template>
```

呼び出し後は以下で動作確認しました！
```
$ npm run build
$ npm run preview
```

## 通常コンポーネント(結果)

![通常コンポーネント.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130422/f080fad2-3a0b-35ca-638b-b5fdbce98741.png)

## NuxtIslandコンポーネント(結果)

![NuxtIslandコンポーネント.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130422/06d7f0d2-be2c-ff1b-a942-db1cdc3bd69f.png)

## サーバーコンポーネント(結果)

![サーバーコンポーネント.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130422/db576a03-47b2-7419-aef4-b442fab3a65b.png)

| 種別 | リクエスト数 | リソースサイズ |
| - | - | - |
| 通常コンポーネント | 14件 | 232kB |
| NuxtIslandコンポーネント | 14件 | 187kB |
| サーバーコンポーネント | 14件 | 188kB |

小規模のアプリケーションのためリクエスト数に変化はなかったものの、
リソースサイズは通常に比べ**約40kBの削減**が見られました🎉

おそらく`NuxtIslandコンポーネント`と`サーバーコンポーネント`で、
JSが排除されたため軽くなったものと思われます🙌

また今回はSSRでの確認でしたが、
SSG(`nuxt generate`)でも削減されたことは確認済みです🙆

# 最後に

NuxtIslandだと指定ディレクトリにコンポーネントを置かないといけないため、
`.server.vue`での運用の方がわかりやすいと感じました🙆‍♂️

Webサイト/アプリでの軽量化には有効かと思います！
同じようにファイルダウンロードが多いことに悩んでいる方は、
試してみるのはいかがでしょうか？

P.S.
クライアントコンポーネントというものもあるようですが、
またの機会に調べてみます🔍

# 参考記事

* [Nuxt3公式： NuxtIsland - Nuxt Components](https://nuxt.com/docs/api/components/nuxt-island)
* [Nuxt3公式： Server Component - Nuxt Directory Structure](https://nuxt.com/docs/guide/directory-structure/components#server-components)
* [What is the NuxtIsland component?](https://michalkuncio.com/what-is-nuxtisland-component)
* [今回使用したNuxt環境(Github)](https://github.com/yuki-s-14/nuxt3-simplest-starter/tree/feature/add-island-component)

# 宣伝・・・

以下の記事も書いているので見てくれたら喜びます🐸

* [【Nuxt3】簡単！@nuxt/eslintを使ったESLintとフォーマッタの設定 ※2024年版](https://qiita.com/yuki_s_14/items/96620ccfb1dd897ce9f3)
* [【Nuxt3】5分で完了！Vitest と Testing Libraryを用いた単体テスト実行環境を作る！](https://qiita.com/yuki_s_14/items/be8416bd392065fda30a)