components

components/ ディレクトリには、すべての Vue コンポーネントを配置します。

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

ディレクトリ構造
-| components/
---| AppHeader.vue
---| AppFooter.vue
app/app.vue
<template>
  <div>
    <AppHeader />
    <NuxtPage />
    <AppFooter />
  </div>
</template>

コンポーネント名

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

ディレクトリ構造
-| components/
---| base/
-----| foo/
-------| Button.vue

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

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

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

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

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

動的コンポーネント

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

例:

app/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 バンドルサイズの最適化に役立ちます。

app/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>

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

Lazy コンポーネントは、アプリのチャンクサイズを制御するのに最適ですが、条件付きでレンダリングされない限り、積極的に読み込まれるため、常にランタイムパフォーマンスを向上させるわけではありません。実際のアプリケーションでは、一部のページには多くのコンテンツと多くのコンポーネントが含まれる場合がありますが、ほとんどの場合、ページが読み込まれた直後にそれらすべてがインタラクティブである必要はありません。それらすべてを積極的に読み込むと、パフォーマンスに悪影響を与える可能性があります。

アプリを最適化するために、一部のコンポーネントのハイドレーションを、それらが可視になるまで、またはブラウザがより重要なタスクを完了するまで遅延させたい場合があります。

Nuxt は、遅延 (または Lazy) ハイドレーションを使用してこれをサポートし、コンポーネントがいつインタラクティブになるかを制御できます。

ハイドレーション戦略

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

遅延ハイドレーションされたコンポーネントの prop が変更されると、すぐにハイドレーションがトリガーされます。(例: hydrate-never を持つコンポーネントの prop を変更すると、ハイドレートされます)
現在、Nuxt の組み込み Lazy ハイドレーションは単一ファイルコンポーネント (SFC) でのみ機能し、テンプレートで prop を定義する必要があります (v-bind を介して prop のオブジェクトを展開するのではなく)。また、#components からの直接インポートでは機能しません。

hydrate-on-visible

コンポーネントがビューポートで可視になったときにハイドレートします。

app/pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-on-visible />
  </div>
</template>
hydrate-on-visible のオプションについて詳しく読む。
内部的には、Vue の組み込みを使用しますhydrateOnVisible 戦略.

hydrate-on-idle

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

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

app/pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-on-idle />
  </div>
</template>
内部的には、Vue の組み込みを使用しますhydrateOnIdle 戦略.

hydrate-on-interaction

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

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

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

内部的には、Vue の組み込みを使用しますhydrateOnInteraction 戦略.

hydrate-on-media-query

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

app/pages/index.vue
<template>
  <div>
    <LazyMyComponent hydrate-on-media-query="(max-width: 768px)" />
  </div>
</template>
内部的には、Vue の組み込みを使用しますhydrateOnMediaQuery 戦略.

hydrate-after

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

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

hydrate-when

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

app/pages/index.vue
<template>
  <div>
    <LazyMyComponent :hydrate-when="isReady" />
  </div>
</template>

<script setup lang="ts">
const isReady = ref(false)
function myFunction () {
  // trigger custom hydration strategy...
  isReady.value = true
}
</script>

hydrate-never

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

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

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

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

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

<script setup lang="ts">
function onHydrate () {
  console.log('Component has been hydrated!')
}
</script>

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

遅延ハイドレーションはパフォーマンス上の利点をもたらしますが、正しく使用することが不可欠です

  1. ビューポート内のコンテンツを優先する: 重要な、ファーストビューのコンテンツには遅延ハイドレーションを使用しないでください。すぐに必要とされないコンテンツに最適です。
  2. 条件付きレンダリング: Lazy コンポーネントで v-if="false" を使用する場合、遅延ハイドレーションは必要ないかもしれません。通常の Lazy コンポーネントを使用するだけで十分です。
  3. 共有状態: 複数のコンポーネント間での共有状態 (v-model) に注意してください。1 つのコンポーネントでモデルを更新すると、そのモデルにバインドされているすべてのコンポーネントでハイドレーションがトリガーされる可能性があります。
  4. 各戦略の意図されたユースケースを使用する: 各戦略は特定の目的に最適化されています。
    • hydrate-when は、常にハイドレートする必要がない可能性があるコンポーネントに最適です。
    • hydrate-after は、特定の期間待機できるコンポーネント用です。
    • hydrate-on-idle は、ブラウザがアイドル状態のときにハイドレートできるコンポーネント用です。
  5. インタラクティブなコンポーネントで hydrate-never を避ける: コンポーネントがユーザーインタラクションを必要とする場合、決してハイドレートしないように設定すべきではありません。

直接インポート

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

app/pages/index.vue
<script setup lang="ts">
import { LazyMountainsList, NuxtLink } 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' },

    // It's important that this comes last if you have overrides you wish to apply
    // to sub-directories of `~/components`.
    //
    // ~/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'],    },
  ],
})

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

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

ディレクトリ構造
| components/
--| Comments.client.vue
app/pages/example.vue
<template>
  <div>
    <!-- this component will only be rendered on client side -->
    <Comments />
  </div>
</template>
この機能は、Nuxt の自動インポートと #components インポートでのみ機能します。これらのコンポーネントを実際のパスから明示的にインポートしても、クライアントのみのコンポーネントには変換されません。
.client コンポーネントはマウントされた後にのみレンダリングされます。onMounted() を使用してレンダリングされたテンプレートにアクセスするには、onMounted() フックのコールバックに await nextTick() を追加します。
<ClientOnly> コンポーネントでも同様の結果を得ることができます。

サーバーコンポーネント

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

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

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

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

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

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

サーバーコンポーネントは現在実験段階であり、使用するには nuxt.config で「コンポーネントアイランド」機能を有効にする必要があります

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

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

ディレクトリ構造
-| components/
---| HighlightedMarkdown.server.vue
app/pages/example.vue
<template>
  <div>
    <!--
      this will automatically be rendered on the server, meaning your markdown parsing + highlighting
      libraries are not included in your client bundle.
     -->
    <HighlightedMarkdown markdown="# Headline" />
  </div>
</template>

サーバー専用コンポーネントは、内部的に <NuxtIsland> を使用しているため、lazy プロップと #fallback スロットの両方が渡されます。

サーバーコンポーネント (およびアイランド) は、単一のルート要素を持つ必要があります。(HTML コメントも要素と見なされます。)
プロップは URL クエリパラメータを介してサーバーコンポーネントに渡されるため、URL の可能な長さに制限されます。したがって、プロップを介してサーバーコンポーネントに大量のデータを渡さないように注意してください。
各アイランドには余分なオーバーヘッドが追加されるため、他のアイランド内にアイランドをネストするときは注意してください。
サーバー専用コンポーネントとアイランドコンポーネントのほとんどの機能 (スロットやクライアントコンポーネントなど) は、単一ファイルコンポーネントでのみ利用可能です。

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

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

クライアントサイドで読み込みたいコンポーネントに nuxt-client 属性を設定することで、コンポーネントを部分的にハイドレートできます。

app/components/ServerWithClient.vue
<template>
  <div>
    <HighlightedMarkdown markdown="# Headline" />
    <!-- Counter will be loaded and hydrated client-side -->
    <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 つの半分」であり、サーバーサイドとクライアントサイドでのコンポーネントの個別の実装のための高度なユースケースで使用できます。

ディレクトリ構造
-| components/
---| Comments.client.vue
---| Comments.server.vue
app/pages/example.vue
<template>
  <div>
    <!-- this component will render Comments.server on the server then Comments.client once mounted in the browser -->
    <Comments />
  </div>
</template>

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

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

Docs > 4 X > API でさらに読む。

ライブラリ作成者

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

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

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

ディレクトリ構造
-| node_modules/
---| awesome-ui/
-----| components/
-------| Alert.vue
-------| Button.vue
-----| nuxt.ts
-| pages/
---| index.vue
-| nuxt.config.ts

次に、awesome-ui/nuxt.tsaddComponentsDir フックを使用できます。

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

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

    // Add ./components dir to the list
    addComponentsDir({
      path: resolver.resolve('./components'),
      prefix: 'awesome',
    })
  },
})

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

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

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

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

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

Docs > 4 X > Examples > Features > Auto Imports でライブサンプルを読み、編集する。