brand logo

ドキュメント

components

components/ ディレクトリは、すべての Vue コンポーネントを配置する場所です。

Nuxt は、このディレクトリ内のコンポーネント(および使用しているモジュールによって登録されるコンポーネント)を自動的にインポートします。

Directory Structure
-| components/
---| AppHeader.vue
---| AppFooter.vue
app.vue
<template>
  <div>
    <AppHeader />
    <NuxtPage />
    <AppFooter />
  </div>
</template>

コンポーネント名

ネストされたディレクトリにコンポーネントがある場合、例えば:

Directory Structure
-| components/
---| base/
-----| foo/
-------| Button.vue

... コンポーネントの名前は、そのパスディレクトリとファイル名に基づき、重複するセグメントは削除されます。したがって、コンポーネントの名前は次のようになります:

<BaseFooButton />

明確にするために、コンポーネントのファイル名がその名前と一致することをお勧めします。したがって、上記の例では、Button.vueBaseFooButton.vue にリネームすることができます。

コンポーネントをパスではなく名前に基づいて自動インポートしたい場合は、設定オブジェクトの拡張形式を使用して pathPrefix オプションを false に設定する必要があります:

nuxt.config.ts
export default defineNuxtConfig({
  components: [
    {
      path: '~/components',
      pathPrefix: false, // [!code ++]
    },
  ],
});

これにより、Nuxt 2 で使用されていたのと同じ戦略を使用してコンポーネントが登録されます。例えば、~/components/Some/MyComponent.vue<MyComponent> として使用可能であり、<SomeMyComponent> ではありません。

動的コンポーネント

Vue の <component>{lang=vue} 構文を使用したい場合は、Vue が提供する resolveComponent ヘルパーを使用するか、#components から直接コンポーネントをインポートして is プロパティに渡す必要があります。

例えば:

pages/index.vue
<script setup lang="ts">
import { SomeComponent } from '#components'

const MyButton = resolveComponent('MyButton')
</script>

<template>
  <component :is="clickable ? MyButton : 'div'" />
  <component :is="SomeComponent" />
</template>

動的コンポーネントを処理するために resolveComponent を使用する場合、コンポーネントの名前以外は挿入しないようにしてください。名前はリテラル文字列であり、変数を含んではいけません。文字列はコンパイルステップで静的に解析されます。

推奨されませんが、すべてのコンポーネントをグローバルに登録することもできます。これにより、すべてのコンポーネントに非同期チャンクが作成され、アプリケーション全体で利用可能になります。

  export default defineNuxtConfig({
    components: {
+     global: true,
+     dirs: ['~/components']
    },
  })

また、~/components/global ディレクトリに配置するか、ファイル名に .global.vue サフィックスを使用することで、一部のコンポーネントを選択的にグローバル登録することもできます。上記のように、各グローバルコンポーネントは別々のチャンクでレンダリングされるため、この機能を過度に使用しないように注意してください。

global オプションは、コンポーネントディレクトリごとに設定することもできます。

動的インポート

コンポーネントを動的にインポートする(コンポーネントを遅延ロードする)には、コンポーネントの名前に Lazy プレフィックスを追加するだけです。これは、コンポーネントが常に必要でない場合に特に便利です。

Lazy プレフィックスを使用することで、適切なタイミングまでコンポーネントコードのロードを遅らせることができ、JavaScript バンドルサイズの最適化に役立ちます。

pages/index.vue
<script setup lang="ts">
const show = ref(false)
</script>

<template>
  <div>
    <h1>Mountains</h1>
    <LazyMountainsList v-if="show" />
    <button v-if="!show" @click="show = true">Show List</button>
  </div>
</template>

遅延(または遅延)ハイドレーション

遅延コンポーネントはアプリのチャンクサイズを制御するのに優れていますが、条件付きでレンダリングされない限り、実行時のパフォーマンスを向上させることはありません。実際のアプリケーションでは、多くのコンテンツやコンポーネントが含まれるページがあり、ページがロードされた直後にすべてがインタラクティブである必要はありません。すべてを急いでロードすると、パフォーマンスに悪影響を与える可能性があります。

アプリを最適化するために、コンポーネントが表示されるまで、またはブラウザがより重要なタスクを完了するまで、いくつかのコンポーネントのハイドレーションを遅らせることを検討するかもしれません。

Nuxt は遅延(または遅延)ハイドレーションをサポートしており、コンポーネントがインタラクティブになるタイミングを制御できます。

ハイドレーション戦略

Nuxt は、さまざまな組み込みのハイドレーション戦略を提供します。遅延コンポーネントごとに使用できる戦略は1つだけです。

現在、Nuxt の組み込みの遅延ハイドレーションは単一ファイルコンポーネント(SFC)でのみ機能し、テンプレートでプロップを定義する必要があります(v-bind を介してプロップのオブジェクトをスプレッドするのではなく)。また、#components からの直接インポートでは機能しません。

hydrate-on-visible

コンポーネントがビューポートに表示されるときにハイドレーションを行います。

pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-on-visible />
  </div>
</template>
こちらも参照 IntersectionObserver のオプションについて詳しく読む

内部では、Vue の組み込みの hydrateOnVisible 戦略 を使用しています。

hydrate-on-idle

ブラウザがアイドル状態のときにコンポーネントをハイドレーションします。これは、コンポーネントをできるだけ早くロードする必要があるが、重要なレンダリングパスをブロックしない場合に適しています。

最大タイムアウトとして機能する数値を渡すこともできます。

pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-on-idle />
  </div>
</template>

内部では、Vue の組み込みの hydrateOnIdle 戦略 を使用しています。

hydrate-on-interaction

指定されたインタラクション(例:クリック、マウスオーバー)後にコンポーネントをハイドレーションします。

pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-on-interaction="mouseover" />
  </div>
</template>

イベントやイベントのリストを渡さない場合、デフォルトでは pointerenterfocus でハイドレーションされます。

内部では、Vue の組み込みの hydrateOnInteraction 戦略 を使用しています。

hydrate-on-media-query

ウィンドウがメディアクエリに一致するときにコンポーネントをハイドレーションします。

pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-on-media-query="(max-width: 768px)" />
  </div>
</template>

内部では、Vue の組み込みの hydrateOnMediaQuery 戦略 を使用しています。

hydrate-after

指定された遅延(ミリ秒単位)後にコンポーネントをハイドレーションします。

pages/index.vue
<template>
  <div>
    <LazyMyComponent :hydrate-after="2000" />
  </div>
</template>

hydrate-when

ブール条件に基づいてコンポーネントをハイドレーションします。

pages/index.vue
<template>
  <div>
    <LazyMyComponent :hydrate-when="isReady" />
  </div>
</template>
<script setup lang="ts">
const isReady = ref(false)
function myFunction() {
  // カスタムハイドレーション戦略をトリガー...
  isReady.value = true
}
</script>

hydrate-never

コンポーネントをハイドレーションしません。

pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-never />
  </div>
</template>

ハイドレーションイベントのリスニング

すべての遅延ハイドレーションコンポーネントは、ハイドレーションされたときに @hydrated イベントを発行します。

pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-on-visible @hydrated="onHydrate" />
  </div>
</template>

<script setup lang="ts">
function onHydrate() {
  console.log("コンポーネントがハイドレーションされました!")
}
</script>

注意点とベストプラクティス

遅延ハイドレーションはパフォーマンスの向上を提供できますが、正しく使用することが重要です:

  1. ビューポート内のコンテンツを優先する: 重要な、折りたたみ上のコンテンツには遅延ハイドレーションを避けてください。すぐに必要でないコンテンツに最適です。

  2. 条件付きレンダリング: 遅延コンポーネントで v-if="false" を使用する場合、遅延ハイドレーションは必要ないかもしれません。通常の遅延コンポーネントを使用するだけで済みます。

  3. 共有状態: 複数のコンポーネント間で共有される状態(v-model)に注意してください。1つのコンポーネントでモデルを更新すると、そのモデルにバインドされたすべてのコンポーネントでハイドレーションがトリガーされる可能性があります。

  4. 各戦略の意図された使用ケースを使用する: 各戦略は特定の目的に最適化されています。

    • hydrate-when は、常にハイドレーションが必要でないかもしれないコンポーネントに最適です。
    • hydrate-after は、特定の時間を待つことができるコンポーネントに適しています。
    • hydrate-on-idle は、ブラウザがアイドル状態のときにハイドレーションできるコンポーネントに適しています。
  5. インタラクティブなコンポーネントに hydrate-never を使用しない: ユーザーのインタラクションが必要なコンポーネントは、ハイドレーションしない設定にしてはいけません。

直接インポート

Nuxt の自動インポート機能をバイパスしたい、またはする必要がある場合、#components からコンポーネントを明示的にインポートすることもできます。

pages/index.vue
<script setup lang="ts">
import { NuxtLink, LazyMountainsList } from '#components'

const show = ref(false)
</script>

<template>
  <div>
    <h1>Mountains</h1>
    <LazyMountainsList v-if="show" />
    <button v-if="!show" @click="show = true">Show List</button>
    <NuxtLink to="/">Home</NuxtLink>
  </div>
</template>

カスタムディレクトリ

デフォルトでは、~/components ディレクトリのみがスキャンされます。他のディレクトリを追加したり、このディレクトリのサブフォルダ内でコンポーネントがスキャンされる方法を変更したい場合は、設定に追加のディレクトリを追加できます:

nuxt.config.ts
export default defineNuxtConfig({
  components: [
    // ~/calendar-module/components/event/Update.vue => <EventUpdate />
    { path: '~/calendar-module/components' },

    // ~/user-module/components/account/UserDeleteDialog.vue => <UserDeleteDialog />
    { path: '~/user-module/components', pathPrefix: false },

    // ~/components/special-components/Btn.vue => <SpecialBtn />
    { path: '~/components/special-components', prefix: 'Special' },

    // 上書きを適用したいサブディレクトリがある場合は、これを最後に配置することが重要です。
    //
    // ~/components/Btn.vue => <Btn />
    // ~/components/base/Btn.vue => <BaseBtn />
    '~/components'
  ]
})

ネストされたディレクトリは最初に追加する必要があります。順番にスキャンされます。

npm パッケージ

npm パッケージからコンポーネントを自動インポートしたい場合、ローカルモジュールaddComponent を使用して登録できます。

import { addComponent, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup() {
    // import { MyComponent as MyAutoImportedComponent } from 'my-npm-package'
    addComponent({
      name: 'MyAutoImportedComponent',
      export: 'MyComponent',
      filePath: 'my-npm-package',
    })
  },
})

コンポーネント拡張子

デフォルトでは、nuxt.config.ts の extensions キー で指定された拡張子を持つファイルはコンポーネントとして扱われます。コンポーネントとして登録されるファイル拡張子を制限する必要がある場合は、コンポーネントディレクトリ宣言の拡張形式とその extensions キーを使用できます:

nuxt.config.ts
export default defineNuxtConfig({
  components: [
    {
      path: '~/components',
      extensions: ['.vue'], // [!code ++]
    }
  ]
})

クライアントコンポーネント

コンポーネントがクライアントサイドでのみレンダリングされることを意図している場合、コンポーネントに .client サフィックスを追加できます。

Directory Structure
| components/
--| Comments.client.vue
pages/example.vue
<template>
  <div>
    <!-- このコンポーネントはクライアントサイドでのみレンダリングされます -->
    <Comments />
  </div>
</template>

この機能は Nuxt の自動インポートと #components インポートでのみ機能します。これらのコンポーネントを実際のパスから明示的にインポートしても、クライアント専用コンポーネントにはなりません。

.client コンポーネントはマウント後にのみレンダリングされます。onMounted() を使用してレンダリングされたテンプレートにアクセスするには、onMounted() フックのコールバック内で await nextTick() を追加します。

こちらも参照 api > components > client-only

サーバーコンポーネント

サーバーコンポーネントは、クライアントサイドアプリ内で個々のコンポーネントをサーバーレンダリングすることを可能にします。静的サイトを生成している場合でも、Nuxt 内でサーバーコンポーネントを使用することが可能です。これにより、動的コンポーネント、サーバーレンダリングされた HTML、さらには静的なマークアップのチャンクを組み合わせた複雑なサイトを構築することが可能になります。

サーバーコンポーネントは単独で使用することも、クライアントコンポーネントと組み合わせて使用することもできます。

Daniel Roe の Nuxt サーバーコンポーネントに関するガイドを読む。

スタンドアロンのサーバーコンポーネント

スタンドアロンのサーバーコンポーネントは常にサーバーでレンダリングされ、アイランドコンポーネントとも呼ばれます。

そのプロップが更新されると、ネットワークリクエストが発生し、レンダリングされた HTML がその場で更新されます。

サーバーコンポーネントは現在実験的であり、使用するには nuxt.config で 'component islands' 機能を有効にする必要があります:

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

これで、.server サフィックスを持つサーバー専用コンポーネントを登録し、アプリケーション内のどこでも自動的に使用できます。

Directory Structure
-| components/
---| HighlightedMarkdown.server.vue
pages/example.vue
<template>
  <div>
    <!--
      これにより、マークダウンの解析とハイライトライブラリがクライアントバンドルに含まれないことを意味し、
      自動的にサーバーでレンダリングされます。
     -->
    <HighlightedMarkdown markdown="# Headline" />
  </div>
</template>

サーバー専用コンポーネントは、<NuxtIsland> を内部で使用しており、lazy プロップと #fallback スロットがそれに渡されます。

サーバーコンポーネント(およびアイランド)は単一のルート要素を持たなければなりません。(HTML コメントも要素と見なされます。)

プロップは URL クエリパラメータを介してサーバーコンポーネントに渡されるため、URL の可能な長さによって制限されるため、プロップを介してサーバーコンポーネントに膨大なデータを渡さないように注意してください。

他のアイランド内にアイランドをネストする際には注意が必要です。各アイランドは追加のオーバーヘッドを追加します。

サーバー専用コンポーネントとアイランドコンポーネントのほとんどの機能、スロットやクライアントコンポーネントなどは、単一ファイルコンポーネントでのみ利用可能です。

サーバーコンポーネント内のクライアントコンポーネント

この機能を使用するには、設定内で experimental.componentIslands.selectiveClient を true にする必要があります。

nuxt-client 属性を設定することで、コンポーネントを部分的にハイドレーションし、クライアントサイドでロードされるようにすることができます。

components/ServerWithClient.vue
<template>
  <div>
    <HighlightedMarkdown markdown="# Headline" />
    <!-- Counter はクライアントサイドでロードされ、ハイドレーションされます -->
    <Counter nuxt-client :count="5" />
  </div>
</template>

これはサーバーコンポーネント内でのみ機能します。クライアントコンポーネントのスロットは experimental.componentIsland.selectiveClient'deep' に設定した場合にのみ機能し、サーバーサイドでレンダリングされるため、クライアントサイドではインタラクティブではありません。

サーバーコンポーネントコンテキスト

サーバー専用またはアイランドコンポーネントをレンダリングする際、<NuxtIsland>NuxtIslandResponse を返すフェッチリクエストを行います。(サーバーでレンダリングされる場合は内部リクエスト、クライアントサイドナビゲーションでレンダリングされる場合はネットワークタブで確認できるリクエストです。)

これにより:

  • サーバーサイドで NuxtIslandResponse を作成するために新しい Vue アプリが作成されます。
  • コンポーネントをレンダリングする際に新しい 'アイランドコンテキスト' が作成されます。
  • アプリの残りの部分から 'アイランドコンテキスト' にアクセスすることはできず、アイランドコンポーネントからアプリの残りの部分のコンテキストにアクセスすることもできません。言い換えれば、サーバーコンポーネントまたはアイランドはアプリの残りの部分から 隔離 されています。
  • プラグインはアイランドをレンダリングする際に再度実行されますが、env: { islands: false } が設定されている場合は除きます(オブジェクト構文プラグインで設定できます)。

アイランドコンポーネント内では、nuxtApp.ssrContext.islandContext を通じてそのアイランドコンテキストにアクセスできます。アイランドコンポーネントがまだ実験的である間、このコンテキストの形式は変更される可能性があります。

スロットはインタラクティブであり、display: contents; を持つ <div> 内にラップされています。

クライアントコンポーネントとペアリング

この場合、.server + .client コンポーネントはコンポーネントの2つの '半分' であり、サーバーとクライアントサイドでのコンポーネントの別々の実装のための高度なユースケースで使用できます。

Directory Structure
-| components/
---| Comments.client.vue
---| Comments.server.vue
pages/example.vue
<template>
  <div>
    <!-- このコンポーネントはサーバーで Comments.server をレンダリングし、ブラウザでマウントされた後に Comments.client をレンダリングします -->
    <Comments />
  </div>
</template>

組み込みの Nuxt コンポーネント

Nuxt が提供するコンポーネントには、<ClientOnly><DevOnly> などがあります。API ドキュメントでそれらについて詳しく読むことができます。

こちらも参照 api

ライブラリアーサー

自動ツリーシェイキングとコンポーネント登録を備えた Vue コンポーネントライブラリを作成するのは非常に簡単です。✨

@nuxt/kit から提供される addComponentsDir メソッドを使用して、Nuxt モジュール内でコンポーネントディレクトリを登録できます。

次のようなディレクトリ構造を想像してください:

Directory Structure
-| node_modules/
---| awesome-ui/
-----| components/
-------| Alert.vue
-------| Button.vue
-----| nuxt.ts
-| pages/
---| index.vue
-| nuxt.config.ts

その後、awesome-ui/nuxt.tsaddComponentsDir フックを使用できます:

import { createResolver, defineNuxtModule, addComponentsDir } from '@nuxt/kit'

export default defineNuxtModule({
  setup() {
    const resolver = createResolver(import.meta.url)

    // ./components ディレクトリをリストに追加
    addComponentsDir({
      path: resolver.resolve('./components'),
      prefix: 'awesome',
    })
  },
})

これで完了です!プロジェクト内で、UI ライブラリを Nuxt モジュールとして nuxt.config ファイルにインポートできます:

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['awesome-ui/nuxt']
})

... そして、pages/index.vue でモジュールコンポーネント(awesome- プレフィックス付き)を直接使用できます:

<template>
  <div>
    My <AwesomeButton>UI button</AwesomeButton>!
    <awesome-alert>Here's an alert!</awesome-alert>
  </div>
</template>

これにより、使用されている場合にのみコンポーネントが自動的にインポートされ、node_modules/awesome-ui/components/ 内のコンポーネントを更新するときに HMR をサポートします。

サンプルコードの編集とプレビューexamples > features > auto-imports

tips

このセクションは公式ドキュメントの翻訳ではなく、本サイト独自の補足記事です。

Nuxtのコンポーネント自動インポート機能とは?

Nuxtは、components/ ディレクトリに配置したVueコンポーネントを自動的にインポートしてくれる便利な機能を備えています。これにより、従来のようにコンポーネントごとに明示的なimport文を書く必要がなくなり、コードの記述量が減り、開発効率が大幅に向上します。

この機能は、特に中規模以上のプロジェクトでコンポーネントが増えてきた際に、管理の手間を軽減し、コードの可読性を保つのに役立ちます。また、動的インポートやグローバル登録の設定も柔軟に行えるため、パフォーマンス最適化にも貢献します。


まず結論:Nuxtのコンポーネント自動インポートのポイント

  • components/ 配下のVueコンポーネントは自動的に登録され、ファイル名やディレクトリ構造に基づいた名前で利用可能
  • ネストされたディレクトリ構造はコンポーネント名に反映されるが、重複するパスセグメントは省略される
  • pathPrefix: false を設定すると、パスを名前に含めずに単純なファイル名でコンポーネントを呼び出せる
  • 動的コンポーネントは resolveComponent を使って名前で解決し、動的切り替えが可能
  • Lazy プレフィックスを付けるとコンポーネントの遅延ロード(コード分割)が自動で行われる
  • グローバル登録も可能だが、チャンクサイズ増加に注意が必要

いつ使うべきか?使わない方がよいケースは?

使うべきケース

  • プロジェクト内で多数のコンポーネントを使い分ける場合
    → 自動インポートによりimport文の管理が不要になり、開発効率が向上します。

  • コンポーネント名とファイル名を規則的に管理している場合
    → 名前の衝突や混乱を防ぎつつ、直感的にコンポーネントを呼び出せます。

  • 動的に切り替えるコンポーネントがある場合
    resolveComponent を使うことで柔軟に対応可能です。

  • パフォーマンスを考慮して遅延ロードを活用したい場合
    Lazy プレフィックスで必要なタイミングまで読み込みを遅らせられます。

使わない方がよいケース

  • コンポーネント名の命名規則が曖昧で重複しやすい場合
    → 自動インポートの名前解決が混乱し、意図しないコンポーネントが使われる恐れがあります。

  • 非常に小規模なプロジェクトでコンポーネント数が少ない場合
    → 明示的なimportの方がシンプルで分かりやすいこともあります。

  • グローバル登録を多用してしまう場合
    → JavaScriptのチャンクサイズが肥大化し、初期ロードが遅くなる可能性があります。


実務でよくあるユースケースとサンプルコード

1. ネストしたコンポーネントの自動インポート

components/base/ui/Button.vue を自動インポートすると、<BaseUiButton /> として利用可能です。
ファイル名を BaseUiButton.vue にリネームしておくと名前が明確になります。

<template>
  <BaseUiButton @click="handleClick">クリック</BaseUiButton>
</template>

<script setup lang="ts">
function handleClick() {
  alert('ボタンがクリックされました')
}
</script>

2. pathPrefix: false を使ったシンプルな名前付け

nuxt.config.ts で以下のように設定すると、components/Some/MyComponent.vue<MyComponent /> として使えます。

export default defineNuxtConfig({
  components: [
    {
      path: '~/components',
      pathPrefix: false,
    },
  ],
})

これにより、名前空間を気にせずにコンポーネントを呼び出せるため、既存のVueプロジェクトからの移行時に便利です。

3. 動的コンポーネントの切り替え

動的にコンポーネントを切り替えたい場合は、resolveComponent を使います。

<script setup lang="ts">
import { resolveComponent } from 'vue'
const currentComponent = ref('MyButton')
const MyButton = resolveComponent('MyButton')
const MyCard = resolveComponent('MyCard')
</script>

<template>
  <component :is="currentComponent === 'MyButton' ? MyButton : MyCard" />
  <button @click="currentComponent = currentComponent === 'MyButton' ? 'MyCard' : 'MyButton'">
    切り替え
  </button>
</template>

4. 遅延ロード(Lazyプレフィックス)

必要なときだけコンポーネントを読み込むことで初期ロードを軽くできます。

<script setup lang="ts">
const showList = ref(false)
</script>

<template>
  <button @click="showList = true" v-if="!showList">リスト表示</button>
  <LazyMountainsList v-if="showList" />
</template>

よくある落とし穴・注意点

SSRとHydrationの問題

自動インポートされたコンポーネントはサーバーサイドレンダリング(SSR)でも動作しますが、動的コンポーネントの名前解決は静的解析に依存するため、変数で名前を渡すとエラーになります。必ず文字列リテラルで指定しましょう。

パフォーマンスへの影響

グローバル登録を多用すると、すべてのコンポーネントが初期ロード時にバンドルされるため、JavaScriptのサイズが大きくなり、ページの表示速度が低下します。必要なコンポーネントだけを遅延ロードする設計が望ましいです。

コンポーネント名の重複

複数のディレクトリに同名のコンポーネントがある場合、名前の衝突が起きやすくなります。命名規則を統一し、必要に応じてpathPrefixオプションを活用して名前空間を付与しましょう。


まとめ

Nuxtのコンポーネント自動インポート機能は、import文の記述を省略できるだけでなく、命名規則やディレクトリ構造に基づいた直感的なコンポーネント呼び出しを可能にします。動的コンポーネントや遅延ロードと組み合わせることで、パフォーマンス面でも効果的です。

ただし、命名の一貫性やグローバル登録の乱用には注意が必要です。実務ではこれらのポイントを踏まえ、適切に設定・運用することで、開発効率とユーザー体験の両立を実現できます。


コンポーネントのファイル名と実際に使う名前を一致させることで、コードの可読性と保守性が大幅に向上します。

動的コンポーネントの名前は必ず文字列リテラルで指定し、変数や計算式を使わないようにしましょう。

グローバル登録を多用するとJavaScriptのバンドルサイズが肥大化し、初期表示速度が遅くなるため注意してください。


title: 'Nuxtの遅延ハイドレーションの実践的な活用と注意点' description: 'Nuxtの遅延ハイドレーション機能を活用してパフォーマンスを最適化する方法を、実務でのユースケースや注意点を交えて詳しく解説します。初〜中級者向けにわかりやすく説明。'

Nuxtの遅延ハイドレーションとは?パフォーマンス改善の鍵

Nuxtの遅延ハイドレーションは、ページの初期ロード時にすべてのコンポーネントを即座にインタラクティブにするのではなく、ユーザーの操作や表示状況に応じて段階的にハイドレーション(クライアント側でのVueの再活性化)を行う仕組みです。これにより、初期ロードのパフォーマンスが向上し、ユーザー体験の改善につながります。

特に大規模なページや多くのインタラクティブ要素を含むアプリケーションでは、すべてのコンポーネントを一度にハイドレーションするとブラウザの負荷が高まり、描画遅延や操作のもたつきが発生しやすくなります。遅延ハイドレーションを使うことで、重要な部分を優先的にハイドレーションし、後回しにできる部分はユーザーの操作や画面表示に合わせて処理することが可能です。


まず結論:Nuxtの遅延ハイドレーションのポイント

  • ハイドレーション戦略を選べる:表示時、アイドル時、ユーザー操作時、メディアクエリ、時間経過、条件判定など多彩なトリガーがある
  • パフォーマンス最適化に有効:初期ロードの負荷を分散し、ユーザー体験を向上させる
  • 使いどころを見極めることが重要:重要なコンテンツは即時ハイドレーション、補助的なコンテンツは遅延させるのが基本
  • イベントでハイドレーション完了を検知可能@hydrated イベントで処理のタイミングを制御できる
  • 単一ファイルコンポーネント(SFC)での利用が前提:テンプレートで明示的にプロップを定義する必要がある

いつ使うべき?使わない方がよいケースは?

遅延ハイドレーションを使うべきケース

  • ページに多数のインタラクティブコンポーネントがあり、すべてを一度にハイドレーションするとパフォーマンスが低下する場合
  • ユーザーがすぐに操作しない補助的なUI(例:サイドバーのウィジェット、下部の広告や関連記事リストなど)がある場合
  • モバイル端末や低スペック環境での負荷軽減を図りたい場合
  • ユーザーの操作や画面表示に応じて段階的に機能を有効化したい場合

遅延ハイドレーションを避けるべきケース

  • 折りたたみ上(ファーストビュー)に表示される重要なコンテンツや操作性が求められる部分
  • ユーザーのインタラクションが必須のコンポーネント(フォーム、ボタンなど)で、遅延するとUXが悪化する場合
  • 共有状態(v-modelなど)を複数コンポーネントで同期している場合、遅延ハイドレーションが複雑な挙動を招くことがある
  • 単純にコンポーネントの表示・非表示を制御したいだけで、遅延ハイドレーションの恩恵が少ない場合

実務でよくあるユースケースとサンプルコード

1. ビューポートに入ったらハイドレーション(hydrate-on-visible

ユーザーがスクロールしてコンポーネントが画面に入ったタイミングでハイドレーションを開始。広告や関連記事リストなど、画面外にあるコンテンツに最適。

<template>
  <div>
    <LazyRelatedArticles hydrate-on-visible @hydrated="onHydrated" />
  </div>
</template>

<script setup lang="ts">
function onHydrated() {
  console.log('関連記事リストがハイドレーションされました')
}
</script>

2. ブラウザがアイドル状態のときにハイドレーション(hydrate-on-idle

重要度は高いが初期ロードを妨げたくないコンポーネントに。例えば、ユーザーのプロフィール情報や通知パネルなど。

<template>
  <div>
    <UserProfile hydrate-on-idle />
  </div>
</template>

3. ユーザーの操作後にハイドレーション(hydrate-on-interaction

ユーザーが特定の操作(クリックやマウスオーバー)をしたときに初めてハイドレーション。例えば、ツールチップやドロップダウンメニュー。

<template>
  <div>
    <DropdownMenu hydrate-on-interaction="click" />
  </div>
</template>

よくある落とし穴・注意点

SSRとCSRのハイドレーションの違いに注意

遅延ハイドレーションはサーバーサイドレンダリング(SSR)で生成された静的なHTMLをクライアント側でVueが再活性化するタイミングを遅らせるものです。遅延しすぎると、ユーザーがインタラクションを試みた際に反応が遅れることがあります。

HydrationのタイミングとUXのバランス

遅延しすぎるとユーザーが操作できない状態が続き、逆にUXが悪化します。重要なUIは即時ハイドレーションを心がけ、遅延は補助的な部分に限定しましょう。

共有状態の同期問題

複数のコンポーネントで同じ状態を共有している場合、遅延ハイドレーションが原因で状態の同期が崩れることがあります。特にv-modelで双方向バインディングしている場合は注意が必要です。

.client サフィックスとの違い

クライアント専用コンポーネント(.client)はサーバーでレンダリングされず、クライアントでのみマウントされます。一方、遅延ハイドレーションはSSRでHTMLを生成しつつ、クライアントでのVueの活性化を遅らせる仕組みです。用途に応じて使い分けましょう。


まとめ

Nuxtの遅延ハイドレーションは、ページのパフォーマンスを向上させる強力な機能です。適切なハイドレーション戦略を選び、重要なコンテンツは即時ハイドレーション、補助的なコンテンツは遅延させることで、ユーザー体験を損なわずに高速な初期表示を実現できます。

ただし、共有状態の同期やユーザー操作のタイミングには注意が必要です。実務では、ビューポート表示やユーザー操作をトリガーにした遅延ハイドレーションが特に効果的であり、パフォーマンス改善に直結します。

Nuxtの遅延ハイドレーションを理解し、適切に活用して快適なSPA体験を提供しましょう。


title: 'Nuxt サーバーコンポーネントの実践的な活用と注意点' description: 'Nuxt のサーバーコンポーネント機能について、基本的な使い方から実務でのユースケース、注意すべきポイントまで丁寧に解説します。初〜中級者が理解を深め、効果的に活用できるように補足情報を提供します。'

Nuxt サーバーコンポーネントとは?〜メリットと解決できる課題〜

Nuxt のサーバーコンポーネントは、Vue コンポーネントの一部をサーバー側でレンダリングし、その結果の HTML をクライアントに送る仕組みです。これにより、クライアントの負荷を軽減しつつ、SEO に強いページを効率的に作成できます。

従来の SSR(サーバーサイドレンダリング)ではページ全体をサーバーでレンダリングしていましたが、サーバーコンポーネントは「アイランドアーキテクチャ」として、ページの一部だけをサーバーで処理し、必要に応じてクライアント側でインタラクティブな部分を補完します。

この仕組みは、以下のような課題を解決します。

  • クライアントバンドルの肥大化を防ぎ、初期表示速度を向上させる
  • 複雑なロジックや重い処理をサーバーにオフロードし、クライアントのパフォーマンスを改善
  • SEO に重要な静的コンテンツを確実にサーバーでレンダリング
  • 動的なインタラクションはクライアントコンポーネントで補完し、UX を損なわない

まず結論:Nuxt サーバーコンポーネントのポイント

  • サーバーコンポーネントは .server サフィックスで定義し、サーバー側でのみレンダリングされる
  • クライアントコンポーネントと組み合わせて使うことで、動的な UI と静的なコンテンツを効率的に分離できる
  • nuxt.config.tsexperimental.componentIslands を有効にする必要がある(現時点では実験的機能)
  • プロップは URL クエリパラメータ経由で渡されるため、大量のデータを渡すのは避けるべき
  • サーバーコンポーネントは単一のルート要素を持つ必要があり、ネストや複雑な構造には注意が必要
  • クライアント側でのインタラクティブな処理は nuxt-client 属性を使ったクライアントコンポーネントで補完可能

いつ使うべきか?使わない方がよいケースは?

使うべきケース

  • 重い処理や外部 API からのデータ取得をサーバー側で行いたい場合
    例:マークダウンのパースやシンタックスハイライト、認証情報を含む処理など

  • SEO 対応が必須で、かつ初期表示速度を重視する場合
    サーバーで静的にレンダリングされた HTML を返すため、検索エンジンに最適

  • クライアントバンドルのサイズを抑えたい場合
    クライアントに不要なライブラリや処理を含めずに済む

  • アイランドアーキテクチャを採用し、ページの一部だけを動的にしたい場合

使わない方がよいケース

  • 完全にクライアントサイドで完結するインタラクティブなコンポーネント
    例:ユーザー操作に即時反応する UI コンポーネントはクライアントコンポーネントで実装すべき

  • 大量のデータをプロップで渡す必要がある場合
    URL クエリパラメータの制限により、パフォーマンスや安定性に影響が出る可能性がある

  • 複雑なネスト構造や複数のアイランドを重ねる場合
    オーバーヘッドが増え、パフォーマンス低下や管理の難しさが生じる


実務でよくあるユースケースとサンプルコード

1. マークダウンのサーバーサイドレンダリング

ブログやドキュメントサイトで、マークダウンのパースとハイライトをサーバーで行い、クライアントに軽量な HTML を送る例。

<!-- components/HighlightedMarkdown.server.vue -->
<template>
  <div v-html="renderedHtml" />
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { useMarkdownParser } from '~/composables/useMarkdownParser'

const props = defineProps<{ markdown: string }>()
const renderedHtml = computed(() => useMarkdownParser(props.markdown))
</script>
<!-- pages/blog.vue -->
<template>
  <HighlightedMarkdown :markdown="postContent" />
</template>

<script setup lang="ts">
const postContent = '# Nuxt サーバーコンポーネントの紹介\n\nこれはサーバーでレンダリングされます。'
</script>

2. クライアントコンポーネントとの組み合わせ

サーバーコンポーネント内で、動的なカウンターなどのクライアントコンポーネントを部分的にハイドレーション。

<!-- components/ServerWithClient.vue -->
<template>
  <div>
    <HighlightedMarkdown markdown="# 見出し" />
    <Counter nuxt-client :count="10" />
  </div>
</template>

3. コメント機能の分離

コメント表示はサーバーコンポーネントで高速にレンダリングし、コメント投稿フォームはクライアントコンポーネントで実装。

components/
  Comments.server.vue
  Comments.client.vue
<!-- pages/article.vue -->
<template>
  <Comments />
</template>

よくある落とし穴・注意点

SSR と CSR の境界でのハイドレーション問題

サーバーコンポーネントはサーバーでレンダリングされた HTML をクライアントに送るため、クライアント側でのハイドレーションが発生します。
この際、サーバーとクライアントでレンダリング結果が異なると、Vue の警告や再レンダリングが発生しパフォーマンス低下の原因になります。
特に動的な値や日時、乱数などはサーバーとクライアントで差異が出やすいので注意が必要です。

プロップのサイズ制限

サーバーコンポーネントのプロップは URL クエリパラメータ経由で渡されるため、サイズに制限があります。
大量のデータを渡すと URL が長くなりすぎてエラーになる可能性があるため、必要最低限のデータに絞るか、API 経由で取得する設計が望ましいです。

アイランドのネストによるオーバーヘッド

複数のサーバーコンポーネント(アイランド)をネストすると、それぞれが独立したレンダリングコンテキストを持つため、リクエスト数や処理負荷が増加します。
パフォーマンスを考慮し、必要な箇所だけに限定して使うことが重要です。

実験的機能であることの理解

現時点でサーバーコンポーネントは Nuxt の実験的機能です。
将来的に API や挙動が変更される可能性があるため、本番環境での利用は慎重に検討し、アップデート情報を常にチェックしましょう。


まとめ

Nuxt のサーバーコンポーネントは、パフォーマンスと SEO を両立しつつ、クライアントバンドルの肥大化を防ぐ強力な機能です。
サーバーで重い処理を行い、クライアントでは必要なインタラクションだけを担当させることで、効率的なモダンウェブアプリケーションを構築できます。

ただし、現状は実験的機能であり、プロップのサイズ制限やネストのオーバーヘッドなど注意点も多いため、ユースケースを見極めて適切に活用することが重要です。

今後の Nuxt のアップデートでさらに使いやすくなることが期待されているため、積極的に試しつつ最新情報を追いかけることをおすすめします。

サーバーコンポーネントの利用には nuxt.config.tsexperimental.componentIslands を有効にする必要があります。
また、クライアントコンポーネントとの組み合わせやスロットの使い方など、公式ドキュメントやコミュニティの情報も参考にしてください。