Open37

📝 Nuxt 3 公式ドキュメント 日本語訳

torish14torish14

💎 ハイブリッド Vue フレームワーク

Vue 3 で次のアプリケーションを構築し、ハイブリッドレンダリング、パワフルなデータフェッチなどの新機能を体験してください。Nuxt 3 は、Web 開発をシンプルかつパワフルにするオープンソースのフレームワークです。

GitHub で ❤️ する

新機能を搭載

Nuxt 3 は、より小さなコアで再設計され、より速いパフォーマンスとより優れた開発者経験のために最適化されています。

  • 🪶 より軽く
    • 最新のブラウザで最大 75 倍、サーバーデプロイとクライアントバンドルサイズを小さくしました。
  • 🐇 より速く
    • nitro(ナイトロと呼ぶらしい)による動的なサーバーのコード分割でコールドスタート(ハードウェアが初期化された状態から再起動すること)を最適化しました。
  • ❎ ハイブリッド soon
    • ISG(Incremental Static Generation)などの高度なモードが使用可能になりました。
  • 🔷 Suspense
    • ナビゲーションの前でも後でも、あらゆるコンポーネントでデータをフェッチできます。
  • ♻️Composition API
    • Composition API と Nuxt 3 の composables を使用して、真のコードの再利用性を実現します。
  • 🧰 Nuxt CLI
    • 新しい依存ゼロの体験により、雛形の作成とモジュールの統合が容易になりました。
  • 🐞 Nuxt Devtools soon
    • ブラウザ上で情報を確認したり、quick fix を使うこどより速く作業ができます。
  • 🛠️ Nuxt Kit
    • TypeScript とクロスバージョンの互換性による全く新しいモジュール開発を確立します。
  • ⏬ Webpack 5
    • 設定なしで、ビルド時間の短縮とバンドルサイズの縮小を実現しました。
  • ⚡ Vite
    • Vite をパンドラとして使用することで、光速の HMR(Hot Module Replacement、画面の再描画なしに JS の変更をブラウザに適用する機能)を体験してください。
  • 💚 Vue 3
    • Vue 3 は、あなたの次の web アプリケーションのための強固な基盤です。
  • 🕵️ TypeScript
    • ネイティブな TypeScript と ESM で構築されているため、余計な手順は必要ありません。

⚙️ Nitro エンジン

私たちは、Nuxt の新しいサーバーエンジンである Nitro の開発に 9 ヶ月間取り組みました。Nitro は、Nuxt サーバーとそれを超える新しい フルスタック機能 を解放します。

開発環境では、Rollup と Node.js workers をサーバーのコードとコンテキストの分離に使用します。また、server/api にあるファイルと server/middleware にある サーバーミドルウェア を読み込んで、サーバー API を生成します

本番環境では、アプリとサーバーをひとつのユニバーサルな .output ディレクトリに構築します。この 出力は軽量 で、最小化され、あらゆる Node.js モジュール(polyfills を除く)から削除されています。この出力は、Node.js、サーバーレス、Workers、Edge Side Rendering、純粋な静的レンダリングなど、JavaScript をサポートするあらゆるシステムでデプロイすることが可能です。

出力はランタイムコードと組み合わされ、あらゆる環境で Nuxt サーバーを実行し(実験的なブラウザの Service Workers を含む!)、静的ファイルを提供でき、JAMStack のための 真のハイブリッドフレームワーク となります。さらに、ネイティブストレージレイヤーが実装されており、マルチソース、ドライバー、ローカルアセットをサポートしています。

Nitro サーバーの基盤は、rollup と h3 で、高いパフォーマンスと移植性のために構築された最小限の http フレームワークです。

🌉 Nuxt Bridge

私たちは、Vue 3 に移行し、4 年間の開発期間を経て Nuxt を書き直し、将来に向けて強固な基盤にしました。

Nuxt 3 へのスムーズなアップグレード
Nuxt 2 と Nuxt 3 間のアップグレードをできるだけ簡単に行えるようにしました。

  • ✅ レガシーなプラグインやモジュールは引き続き動作します
  • ✅ Nuxt 2 の構成と互換性あり
  • ✅ 部分ページオプション API が利用可能

既存の Nuxt 2 プロジェクトに Nuxt 3 の経験を取り入れる
Nuxt 3 の新機能を開発するにあたり、その一部を Nuxt 2 にバックポートしました。

  • Nitro サーバーと Nuxt 2 の併用
  • ✅ Nuxt 2 で Nuxt 3 と同じ Composition API を使用
  • ✅ Nuxt 2 で新しい CLI と Devtools を使用
  • ✅ Nuxt 3 への段階的なアップグレード
  • ✅ Nuxt 2 のモジュールエコシステムとの互換性
  • ✅ 少しずつアップグレード(Nitro、Composition API、Nuxt Kit)
torish14torish14

はじめかた

はじめに

Nuxt 3 を使いはじめるのは簡単です。

Nuxt ってなに?

Nuxt について初めて学ぶ方、Nuxt 3 についてもっとよく知りたい方は、まず コンセプトセクションを読むことをお勧めします。

前提条件

はじめる前に、推奨されるセットアップがインストールされていることを確認してください。

  • Node.js* (最新 LTS 版) 👉 ダウンロード
  • Visual Studio Code 👉 ダウンロード
  • Volar Extension 👉 ダウンロード
    • Take Over Mode を有効にする(推奨)
    • ... または TypeScript Vue Plugin(Volar) を追加 👉 ダウンロード
  • すでに Node.js をインストールしている場合は、node --versionv14 または v16 を使用していることを確認してください。
export default defineNuxtConfig({
  typescript: {
    shim: false
  }
})

Nuxt 3 か Bridge か?

次に、ゼロからはじめるか、既存の Nuxt 2 プロジェクトをアップグレードするかを決定します。

新しい Nuxt プロジェクトを開始する

  • ℹ️ Vue 3 を使って楽しむ
  • ℹ️ すべての新しい composables を使用可能
  • ℹ️ 新しいテンプレートシステムと規約が有効です

Nuxt 2 プロジェクトの移行

既存の Nuxt 2 プロジェクトをお持ちの場合は、まず Nuxt Bridge を使用することを 強くお勧めします 。この方法では、変更を最小限に抑えながら、ほとんどの新機能を試すことができます。

  • ℹ️ リスクはありません!いつでも設定からモジュールを削除することができます
  • ℹ️ あなたのプロジェクトを Nuxt 3 にほぼ対応させることができます
  • ℹ️ Vue 3 用に大きく書き換えることなく、新しい開発者体験の改良を楽しむことができます
  • ℹ️ プラットフォームに依存しない最適化されたデプロイメントのための Nitro エンジンの使用
  • ℹ️ Nuxt 3 の安定化と欠陥の発見に貢献する
  • ℹ️ Nuxt Bridge は、現時点では Nuxt 3 よりも安定しています

比較

以下の表は、Nuxt の 3 つのバージョンを簡単に比較したものです。

特徴/バージョン Nuxt 2 Nuxt Bridge Nuxt 3
Vue 2 2 3
安定性 😊 安定してる 😌 ちょっと安定 😬 安定してない
パフォーマンス 🏎 速い ✈️ より速い 🚀 もっとも速い
Nitro エンジン
ESM サポート 🌙 部分的 👍 ベター
TypeScript ☑️ オプトイン 🚧 部分的
Composition API 🚧 部分的
Options API
Components Auto Import
<script setup> 構文 🚧 部分的
Auto Imports
Webpack 4 4 5
Vite ⚠️ 部分的 🚧 部分的 🚧 実験的
Nuxt CLI ❌ 古い ✅ nuxti ✅ nuxti
静的サイト 🚧
torish14torish14

インストール

Nuxt 3 を使いはじめるのは簡単です。

オンラインで試す

Nuxt 3 はオンラインの sandbox などを使用してブラウザから使いはじめることができます。

新しいプロジェクト

ターミナルを開くか、Visual Studio Code から 統合ターミナル を開き、次のコマンドを使用して新しいスタータープロジェクトを作成します:

npx
npx nuxi init nuxt3-app

or

yarn2
yarn dlx nuxi init nuxt3-app

or

pnpm
pnpm dlx nuxi init nuxt3-app

visual studio code で nuxt3-app フォルダを開いてください:

code nuxt3-app

依存関係をインストールします:

yarn
yarn install 

or

npm
npm install 

or

pnpm
pnpm install --shamefully-hoist

開発サーバー

これで、yarn dev を使って開発モードで nuxt アプリを起動できるようになります:

yarn
yarn dev -o

or

npm
npm run dev -- -o

or

pnpm
pnpm run dev -- -o

次のステップ

これで Nuxt 3 プロジェクトが作成できたので、アプリケーションの構築を開始する準備ができました。

  • コンセプト について学ぶ
  • 使用方法 について詳しく知る
torish14torish14

Bridge

既存の Nuxt 2 のプロジェクトで Nuxt 3 の機能を体験する。

Bridge は、Nuxt モジュールをインストールして有効にするだけで、Nuxt 3 の新機能の多くを体験できる前方互換性のあるレイヤーです。

Nuxt Bridge を使用することで、プロジェクトを Nuxt 3 に(ほぼ)対応させることができ、大幅な書き換えや変更を加えるリスクなしに最高の開発者体験を得ることができます。

Nuxt 2 をアップグレード

開発サーバー(nuxt dev)が起動していないことを確認し、package lock ファイル(package-lock.jsonyarn.lock)を削除し、最新の nuxt-edge をインストールしてください:

package.json
- "nuxt": "^2.15.0"
+ "nuxt-edge": "latest"

そのあと、依存関係を再インストールしてください:

Yarn
yarn install

or

Npm
npm install

Nuxt Bridge をインストール

開発依存として @nuxt/bridge-edge をインストールします:

Yarn
yarn add --dev @nuxt/bridge@npm:@nuxt/bridge-edge

or

Npm
npm install -D @nuxt/bridge@npm:@nuxt/bridge-edge

scripts をアップデート

また、Nuxt が Nitro サーバーをビルド出力として生成するようになったことを反映して、package.json 内の scripts を更新する必要があります。

Nuxi

Nuxt 3 では、新しい Nuxt CLI コマンドである nuxi が導入されました。Nuxt Bridge のより良いサポートを利用するために、以下のように scripts を更新してください:

{
  "scripts": {
-   "dev": "nuxt",
+   "dev": "nuxi dev",
-   "build": "nuxt build",
+   "build": "nuxi build",
-   "start": "nuxt start",
+   "start": "nuxi preview"
  }
}

静的ターゲット

nuxt.configtarget: 'static' を設定している場合は、ビルド scripts を nuxi generate に更新することを確認する必要があります。

package.json
{
  "scripts": {
    "build": "nuxi generate"
  }
}

サーバーターゲット

それ以外の場合は、nuxi build コマンドを使用します。

package.json
{
  "scripts": {
    "build": "nuxi build",
    "start": "nuxi preview"
  }
}

nuxt.config をアップデート

設定ファイルでは、module.exportsrequirerequire.resolve などの CommonJS の構文を使わないように注意してください。

代わりに、static import、dynamic import()export default を使用することができます。また、nuxt.config.ts に名前を変更することで TypeScript を使用することも可能であり、推奨されています。

nuxt.config.ts
import { defineNuxtConfig } from '@nuxt/bridge'

export default defineNuxtConfig({
  // 設定
})

tsconfig.json をアップデート

TypeScript を使用している場合、tsconfig.json を編集することで、自動生成された Nuxt の型を利用することができます。

tsconfig.json
{
+ "extends": "./.nuxt/tsconfig.json",
  "compilerOptions": {
    ...
  }
}

Composition API に移行

以前、@vue/composition-api@nuxtjs/composition-api を使用していた場合は、composition api 移行ガイド をお読みください。

CommonJS から ESM に移行

Nuxt 3 は TypeScript と ECMAScript Modules をネイティブでサポートしています。詳細とアップグレードについては、Native ES Modules をご覧ください。

互換性のないモジュールや旧式のモジュールを削除

  • nuxt/content を削除(1.x)してください。nuxt 3 用の書き換えが予定されています (2.x)
  • nuxt-vite を削除してください。Bridge で同機能を実現します
  • nuxt/typescript-build を削除してください。Bridge で同機能を実現します
  • nuxt/typescript-runtimenuxt-ts を削除してください。Nuxt 2 はランタイムサポートを内蔵しています
  • nuxt/nitro を削除してください。Bridge は同じ機能を持っています
  • vue/composition-api を依存関係から削除してください (移行ガイド )
  • nuxtjs/composition-api を依存関係から削除してください(および nuxt.config のモジュールから) (移行ガイド)

ビルドした Nitro フォルダを git から除外

.gitignore ファイルに .output フォルダを追加します。

すべてがうまくいくことを確認する

nuxi devnuxi build(または nuxi generate)で試してみて、すべてがうまくいくかどうか確認してください。

🐛 何か問題がありますか? issue を作成してお知らせください。また、その間に bridge を無効にすることも簡単にできます。

nuxt.config.ts
import { defineNuxtConfig } from '@nuxt/bridge'

export default defineNuxtConfig({
  bridge: false // bridge 統合の一時的な無効化
})

新しいプラグインフォーマット(オプション)

Nuxt 2 とは形式が若干異なる Nuxt 3 プラグイン API に移行することができるようになりました。

プラグインは 1 つの引数(nuxtApp)だけを取るようになりました。詳しくはドキュメントをご覧ください。

export default defineNuxtPlugin(nuxtApp => {
  nuxtApp.provide('injected', () => 'my injected function')
  // `nuxtApp.$injected` で利用可能になりました。
})

新しい useMeta(オプション)

Nuxt Bridge は新しい Nuxt 3 meta API を提供し、新しい useMeta composable でアクセスすることができます。

<script setup>
import { useMeta } from '#app'
useMeta({
  title: 'My Nuxt App',
})
</script>

また、nuxt.config でこの機能を明示的に有効にする必要があります:

import { defineNuxtConfig } from '@nuxt/bridge'

export default defineNuxtConfig({
  bridge: {
    meta: true
  }
})

この useMeta composable は、(vue-meta ではなく) @vueuse/head を内部に使って <head> を操作しています。したがって、Nuxt 2 のネイティブな head() プロパティと useMeta の両方を使用することは、競合する可能性があるので、推奨されません。

この composable の使い方の詳細は、ドキュメントを参照してください。

機能フラグ

オプションで bridge からいくつかの機能を無効にしたり、より安定性の低い機能をオプトインすることができます。通常の場合、デフォルトのままにしておくのが一番です!

最新のデフォルトは、bridge/src/module.ts で確認できます。

nuxt.config.ts
import { defineNuxtConfig } from '@nuxt/bridge'

export default defineNuxtConfig({
  bridge: {

    // -- オプトインの特徴 --

    // Webpack 4 の代わりに Vite をバンドラーとして使用する
    // vite: true,

    // Nuxt 3 互換の useMeta を使用可能にする。
    // meta: true,

    // -- デフォルトの特徴 --

    // Nitro の代わりにレガシーサーバーを使用する
    // nitro: false,

    // Nuxt 3 互換の `nuxtApp` インターフェースを無効にする
    // app: false,

    // Composition API サポートを無効にする
    // capi: false,

    // ... あるいは、従来の Composition API のサポートを無効にする
    // capi: {
    //   legacy: false
    // },

    // モジュールをトランスパイルしない
    // transpile: false,

    // <script setup> サポートを無効にする
    // scriptSetup: false,

    // composables の自動インポートを無効にする
    // autoImports: false,

    // モジュールの非互換性についての警告を表示しない
    // constraints: false
  },

  vite: {
    // Vite 用の設定
  }
})
torish14torish14

コマンド

Nuxi は Nuxt 3 の新しい CLI 体験です

Nuxt 3 には、開発用サーバーを起動するコマンドと、本番用アセットを作成するコマンドの大きく 2 つがあります。

Nitro サーバーを導入したことにより、Nuxt 3 は開発依存となったため、package.json に 2 つのコマンドを追加するだけでいいです:

package.json
"scripts": {
  "dev": "nuxi dev",
  "build": "nuxi build",
}

そして、npm run <command> または yarn <command> を使って各コマンドを実行することができます。

開発サーバー

http://localhost:3000 を開いて、ホットモジュール交換による開発モードの Nuxt を起動する:

Yarn
yarn dev

or

Npm
npm run dev

HTTPS https://localhost:3000 (自己署名証明書)を使って開発モードで Nuxt を起動する:

Yarn
yarn dev --https

or

Npm
npm run dev -- --https

実運用に向けたビルド

Nuxt アプリケーションを本番用にビルドするには、以下を実行します:

Yarn
yarn build

or

Npm
npm run build

Nuxt は、アプリケーション、サーバー、および依存関係をすべて含む .output ディレクトリを作成し、デプロイする準備ができます。Nitro を使用して Nuxt アプリケーションをデプロイする場所と方法については、デプロイメント のセクションを参照してください。

CLI 用語集

torish14torish14

移行

Nuxt 3 への移行ガイドです。作業中です 🚧

Nuxt 2 から Nuxt 3 へ

現時点では、Nuxt 2 から Nuxt 3 への移行ガイドはありませんし、さらなる変更の可能性があるため、移行は推奨されません。できるだけスムーズに移行できるよう、安定した移行ガイドとツールの提供に取り組んでいます。代替ツールについては、Bridge をご確認ください。

Nuxt 2 から削除された機能、Nuxt 3 にまだ実装されていない機能、Nuxt 3(および Bridge)で新たに追加された機能があります。

Nuxt Bridge の要件以外で、Nuxt 3 における顕著な、または重要な変更点は以下のとおりです:

  • ℹ️ Vue アプリのテンプレートを書き直しました
  • ℹ️ Vue を 3.x にアップグレード
  • ℹ️ 非同期データ取得のために <Suspense> を使用
  • ℹ️ Webpack 5.x (vite を使用しない場合)
  • ℹ️ コンポーネントの発見機能を書き直しました
  • ℹ️ メインである app.vue コンポーネントを導入
  • ℹ️ 新しい layouts システムを導入
  • ℹ️ pages/ ディレクトリの規約を変更

下表は、全体的な機能比較のハイライトです。

特徴/バージョン Nuxt 2 Nuxt 3 必要な変更か 詳細
Vue のバージョン 2 3 はい
app.vue -
Suspense -
Assets いいえ
Components いいえ
Layouts はい
Middleware はい
Pages はい
Pages: Dynamic Params はい
Pages: _.vue はい(新しい名前付けシステム)
Plugins はい(デフォルトで互換性がある)
Transitions
Options API: asyncData はい(defineNuxtComponent を使ってください)
Options API: fetch 代わりに asyncData を使ってください
Error Pages 🚧 はい ランタイムエラーへの対応
Store 🚧 はい グローバルストアをサポート
Static builds 🚧 ルートキャッシングルール(ハイブリッド)

モジュールの互換性

すべての Nuxt 2 モジュールは、bridge に移行する場合、またはすでにガイドラインに従っている場合は、Nuxt 3 と前方互換性があるはずです。

@nuxt/kit で作成されたすべての(今後の)モジュールは、Nuxt 3 / Bridge 専用の機能に依存していない限り、Nuxt 2 プロジェクトと後方互換性があるべきです(bridge を使用していない場合も)。

プラグイン互換性

ほとんどの Nuxt 2 プラグインは、私たちが導入する魔法の互換レイヤーを使って Nuxt 3 と前方互換性があるはずです。

Nuxt 3 のプラグインは Nuxt 2 との後方互換性がありません

Vue との互換性

composition API やコンポーネントを使用するプラグインは、Vue 2 または Vue 3 に排他的に対応する必要があります。

vue-demi を使用することで、Nuxt 2 と 3 の両方に互換性があるはずです。

モジュールの移行

Nuxt 3 ユーザーがあなたのモジュールを追加すると、@nuxt/kit から互換性のあるモジュールコンテナレイヤーが自動的に導入されるので、あなたのコードが以下のガイドラインに従っている限り、そのまま動作し続けるはずです。

nuxt/bridge でテストする

@nuxt/bridge への移行は、Nuxt 3 をサポートするための最初で最も重要なステップです。

モジュール内にフィクスチャやサンプルがある場合、その設定に @nuxt/bridge パッケージを追加します (を参照)。

CommonJS から ESM への移行

Nuxt 3 は TypeScript と ECMAScript Modules をネイティブにサポートしています。詳細とアップグレードについては、Native ES Modules を参照してください。

プラグインがデフォルトでエクスポートされるようにする

export default を持たない Nuxt プラグイン(グローバルな Vue プラグインなど)を導入する場合は、export default () => { } を末尾に追加することを確認してください。

Before
// ~/plugins/vuelidate.js
import Vue from 'vue'
import Vuelidate from 'vuelidate'

Vue.use(Vuelidate)
After
// ~/plugins/vuelidate.js
import Vue from 'vue'
import Vuelidate from 'vuelidate'

Vue.use(Vuelidate)

export default () => { }

ランタイムモジュールを避ける

Nuxt 3 では、Nuxt はビルド時のみの依存関係になりました。これは、モジュールが Nuxt ランタイムにフックしようとすべきではないことを意味します。

モジュールのニーズは、(モジュールではなく)buildModules に追加されただけでも動作するはずです。例えば、

  • Nuxt モジュール内で process.env を更新し、nuxt プラグインで読み込むことは避け、代わりに runtimeConfig を使用します。
  • (*) vue-renderer:* のようなランタイムフックに依存した制作は避けましょう。
  • (*) serverMiddleware をモジュール内部でインポートして追加することは避けてください。代わりに、ファイルパスを参照して追加することで、モジュールのコンテキストに依存しないようにします。

(*) nuxt dev の目的のみで、if (nuxt.options.dev) { } でガードされている場合は別です。

TypeScript を使用する(オプション)

必須ではありませんが、Nuxt のエコシステムのほとんどが TypeScript の利用にシフトしていますので、移行を検討されることを強くお勧めします。

torish14torish14

ガイド

Nuxt とは?

Nuxt の目標は、優れた開発者体験を念頭に置き、web 開発を直感的かつ高性能にすることです。

なんで Nuxt なの?

Nuxt とは何かを理解するためには、モダンなアプリケーションを作るために何が必要かを理解する必要があります。

  • ✅ リアクティビティ(反応性)とウェブコンポーネントをもたらすJavaScript フレームワークである、Vue.js を私たちは選びました。
  • ✅ 開発中の Hot Module Replacement(画面の再描画なしに JS の変更をブラウザに適用する機能)をサポートし、本番用にコードをバンドルするモジュールバンドラーである、Webpack 5Vite の両方をサポートしています。
  • ✅ レガシーな(古い)ブラウザをサポートしながら、最新の JavaScript 構文を記述するためのトランスパイラ(あるプログラミング言語から他のプログラミング言語に変換するもの)である esbuild を使用しています。
  • ✅ 開発中のアプリケーションを提供するためのサーバーでありながら、サーバーサイドレンダリングや API ルートをサポートする Nuxt はサーバーレス、ワーカー、Node.js などの多様なデプロイと比類のないパフォーマンスのために h3 を使用しています。
  • ✅ クライアントサイドのナビゲーションを処理するためのルーティングライブラリである、vue-router を私たちは選びました。

これは氷山の一角です。想像してみてください。プロジェクトにこれらすべてを設定し、動作させ、そして長期間にわたって保守することを。私たちは 2016 年 10 月からこれを行い、あらゆる Vue アプリケーションにベストな最適化とパフォーマンスを提供するために、すべての設定をチューニングしています。

Nuxt がこれらすべてを引き受けます。だからあなたは大事なこと: つまり web アプリケーションをつくること に集中することができます。

この設定に加えて、Nuxt はあなたが設定ではなく、作成に集中できるように、特定の機能に焦点を当てた従うべき ディレクトリ構造 を提供します。

どのように機能するの?

Nuxt は様々な コアパッケージ で構成されています:

  • ℹ️ コアエンジン: nuxt 3
  • ℹ️ バンドラー: @nuxt/vite-builder@nuxt/webpack-builder
  • ℹ️ コマンドラインインターフェース: nuxi
  • ℹ️ サーバーエンジン: @nuxt/nitro
  • ℹ️ 開発キット: @nuxt/kit
  • ℹ️ Nuxt 2 Bridge: @nuxt/bridge

Nuxt の機能、および各パッケージの範囲を完全に把握するために、各コンセプトを読むことをお勧めします。

あなたは Nuxt を使いますか?

Nuxt は、Vue.js プロジェクトの基幹であり、柔軟性を保ちながら自信を持ってプロジェクトを構築するための構造を提供します。

強力なモジュールエコシステムとフックエンジンによって拡張可能で、REST や GraphQL エンドポイント、お気に入りの CMS、CSS フレームワークなどを簡単に接続することができます。PWA とAMP のサポートは、Nuxt のプロジェクトにモジュールを追加するだけです。

あなたは Nuxt を使う勇気をもっていますか?

オープンな issue に挑戦してみよう。これは実際にコードに飛び込むことで、Nuxt を学ぶ最良の方法なのです。Nuxt をより良くするためのアプローチや代替案をもたらすことさえできるかもしれませんよ!それで、あなたはなにを待っているのですか?さあ、飛び込みましょう!

torish14torish14

Vue.js 開発

Nuxt は、フロントエンドフレームワークとして Vue を使用し、コンポーネントの自動インポートやファイルベースのルーティングなどの機能を追加しています。Nuxt 3 は、Vue の新しいメジャーリリースである Vue 3 を統合し、Nuxt ユーザーのための新しいパターンを可能にします。

Vue.js

Nuxtを使用するために Vue の深い知識は必要ありませんが、ドキュメントを読み、vuejs.org にあるいくつかの例を見てみることをお勧めします

Nuxt は、フロントエンドフレームワークとして常に Vue を使用しています。私たちが Nuxt を Vue の上に構築することを選択したのは、以下のような理由からです。

  • Vue のリアクティビティ(反応的な)・モデルは、データの変化が自動的にインターフェースの変化を引き起こすというものです。
  • コンポーネントベースのテンプレートは、HTML をウェブの共通言語として維持しながら、直感的なパターンを可能にし、インターフェイスの一貫性を保ちつつ、パワフルなものにします。
  • 小規模なプロジェクトから大規模な web アプリケーションまで、Vue はスケールアップしても優れたパフォーマンスを発揮し続け、アプリケーションがユーザーに価値を提供し続けることを保証します。

Vue と Nuxt

単一ファイルコンポーネント

Vue の単一ファイルコンポーネント(SFC、または *.vue ファイル)は、Vue コンポーネントのマークアップ(<template>)、ロジック(<script>)、スタイル(<style>)をカプセル化したものです。Nuxt は、シームレスな開発者体験を提供する Hot Module Replacement(画面の再描画無しに JS の変更をブラウザに適用してくれるもの) により、SFC のためのゼロ設定体験を提供します。

コンポーネントの自動インポート

Nuxt プロジェクトの components/ ディレクトリに作成されたすべての Vue コンポーネントは、インポートしなくてもあなたのプロジェクトで利用できるようになります。コンポーネントがどこにも使用されていない場合、プロダクションのコードには含まれません。

Vue Router

ほとんどのアプリケーションでは、複数のページと、それらの間を移動する方法が必要です。これをルーティングと呼びます。Nuxt は、pages/ ディレクトリと命名規則を使用して、公式の Vue Router ライブラリ を使用してファイルにマッピングされたルートを直接作成します。

app.vue ファイルは、ブラウザのウィンドウに表示されるページを表すエントリーポイントです。

コンポーネントの <template> 内では、components/ ディレクトリに作成された<Welcome> コンポーネントをインポートすることなく使用しています。

<template> の内容をカスタムのウェルカムメッセージに置き換えてみてください。右のブラウザウィンドウは、リロードせずに自動的に変更内容をレンダリングします。

Nuxt 2 または Vue 2 の以前のユーザーであれば、Vue 2 と Vue 3 の違いや、Nuxt がこれらの進化をどのように統合しているかを知るために読み続けてください。

そうでない場合は、次の章で Nuxt のもう一つの重要な機能を発見してください: レンダリングモード

Nuxt 2 / Vue 2 とのちがい

Nuxt 3 は、Vue 3 をベースにしています。新しい Vue のメジャーバージョンでは、Nuxt が活用できるいくつかの変更点が導入されています:

  • パフォーマンスの向上
  • Composition API
  • TypeScript サポート

より高速なレンダリング

Vue Virtual DOM (VDOM)はゼロから書き直され、より良いレンダリングパフォーマンスを可能にしました。さらに、コンパイルされた単一ファイルコンポーネントを扱う場合、Vue コンパイラは、静的マークアップと動的マークアップを分離することによって、ビルド時にコンポーネントをさらに最適化することができます。

その結果、最初のレンダリング(コンポーネント作成)と更新が高速化され、メモリ使用量も少なくなります。Nuxt 3 では、サーバーサイドレンダリングの高速化も可能になりました。

より小さいバンドル

Vue 3 と Nuxt 3 では、バンドルサイズの縮小に重点が置かれています。バージョン 3 では、テンプレートディレクティブや組み込みコンポーネントなど、Vue の機能のほとんどが Tree shaking(実行されないコードを削除すること)が可能になっています。それらを使用しない場合、プロダクションバンドルにはそれらが含まれません。

こうすることで、最小限のVue 3アプリケーションを gzip で 12kb に圧縮することができます。

Composition API

Vue 2 でコンポーネントにデータやロジックを提供する唯一の方法は Options API で、datamethods のようなあらかじめ定義されたプロパティをテンプレートに返すことができるようになりました:

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment(){
      this.count++
    }
  }
}
</script>

Vue 3 で導入された Composition API は、Options API に代わるものではありませんが、アプリケーション全体でより良いロジックの再利用を可能にし、複雑なコンポーネントで懸念事項によってコードをグループ化する、より自然な方法を提供します。

<script> 定義の setup キーワードを使用して、上記のコンポーネントを Composition API と Nuxt 3 の自動インポートされた Reactivity API で書き直したものがこちらです:

<script setup>
  const count = ref(0)
  const increment = () => count.value++
</script>

Nuxt 3 の目標は、Composition API を中心とした優れた開発者体験を提供することです。

  • Vue と Nuxt 3 内蔵の composables から自動インポートされた Reactivity 関数 を使用します。
  • composables/ ディレクトリに自動インポートされた再利用可能な独自の関数を記述します。

TypeScript サポート

Vue 3 と Nuxt 3 はどちらも Typescript で書かれています。完全に型付けされたコードベースはミスを防ぎ、API の使い方を文書化します。これは、Typescript を活用するためにアプリケーションを書かなければならないということではありません。Nuxt 3 では、ファイル名を .js から .ts に変更したり、コンポーネント内に <script lang="ts"> を追加することで、TypeScript を使用することができます。

torish14torish14

レンダリングモード

ブラウザとサーバーは、Javascript のコードを解釈して、Vue.js コンポーネントを HTML 要素にレンダリングすることができます。このステップをレンダリングと呼びます。Nuxt はクライアントサイドとユニバーサルレンダリングの両方をサポートしています。この 2 つのアプローチには長所と短所があり、このセクションで説明します。

クライアントサイドのみのレンダリング

従来の Vue.js アプリケーションは、すぐにブラウザ(または クライアント)上でレンダリングされます。そして、ブラウザが現在のインターフェイスを作成するための命令を含むすべての Javascript コードをダウンロードして解析した後、Vue.js は HTML 要素を生成します。

この手法により、複雑でダイナミックな UI を構築し、スムーズなページ遷移を実現することができますが、異なる長所と短所があります。

長所

  • 開発スピード: 完全にクライアントサイドで作業する場合、window オブジェクトのようなブラウザ専用の API を使用するなど、コードのサーバー互換性を気にする必要がありません。
  • 低コスト: サーバーを稼働させる場合、Javascript をサポートするプラットフォームで稼働させる必要があるため、インフラストラクチャのコストがかかります。クライアント専用のアプリケーションは、HTML、CSS、Javascript のファイルだけで、どんな静的なサーバーでもホストすることができます。
  • オフライン: コードはすべてブラウザ上で実行されるため、インターネットが利用できない状態でも、きれいに動作し続けることができます。

短所

  • パフォーマンス: ユーザーは、ブラウザが JavaScript ファイルをダウンロードし、解析し、実行するのを待つ必要があります。ダウンロードはネットワークに依存し、解析と実行はユーザーのデバイスに依存するため、時間がかかり、ユーザーエクスペリエンスに影響を与える可能性があります。
  • 検索エンジン最適化: クライアントサイドレンダリングで配信されるコンテンツのインデックス作成と更新には、すでに構築された HTML ドキュメントよりも多くの時間がかかります。これは、先ほど説明したパフォーマンスの欠点と関係があります。検索エンジンのクローラーは、ページをインデックスするための最初の試みで、インターフェイスが完全にレンダリングされるのを待たないからです。したがって、純粋なクライアントサイド・レンダリングでは、検索結果ページでのコンテンツの表示と更新に時間がかかります。

事例

クライアントサイドレンダリングは、インデックス作成の必要がない、または同じユーザーを頻繁に使用する、高度にインタラクティブな ウェブアプリケーション に適した方法です。ブラウザのキャッシュを活用して、SaaSバックオフィスアプリケーションオンラインゲーム など、次回以降の訪問時にダウンロードフェーズをスキップすることができます。

ユニバーサルレンダリング

ユニバーサル(クライアントサイド+サーバーサイド)レンダリングを有効にしてブラウザが URL をリクエストすると、完全にレンダリングされた HTML ページがブラウザに返されます。ページが事前に生成されてキャッシュされている場合も、オンザフライでレンダリングされる場合も、Nuxt はサーバー環境で Javascript(Vue.js)のコードを実行し、HTML ドキュメントを生成していることに変わりはありません。ユーザーは、クライアントサイドレンダリングとは逆に、我々のアプリケーションのコンテンツをすぐに手に入れることができます。このステップは、PHP や Ruby のアプリケーションが行う従来の サーバーサイドレンダリング に似ています。

動的なインターフェースやページ遷移など、クライアントサイドレンダリング方式の利点を失わないために、サーバー上で実行される JavaScript コードは、HTML ドキュメントがダウンロードされるとバックグラウンドでクライアントによってロードされます。HTML 文書がブラウザによって再び解釈されると(ユニバーサルレンダリング のため)、Vue.js が文書を制御し、インタラクティブ性を実現します。

静的なページをブラウザ上でインタラクティブにすることを "ハイドレーション "と呼びます。

ユニバーサルレンダリングにより、Nuxt アプリケーションはクライアントサイドレンダリングの利点を生かしながら、ページの読み込み時間を短縮することができる。さらに、コンテンツがすでに HTML ドキュメント内に存在するため、クローラーはオーバーヘッドなしにインデックスを作成することができる。

長所

  • パフォーマンス: ブラウザは、Javascript で生成されたものよりも静的なコンテンツをより速く表示できるため、ユーザーはページのコンテンツにすぐにアクセスすることができます。同時に、Nuxt はハイドレーションプロセスが発生しても、web アプリケーションのインタラクティブ性を維持します。
  • 検索エンジン最適化: ユニバーサルレンダリングは、ページの HTML コンテンツ全体をクラシックなサーバーアプリケーションとしてブラウザに配信します。ウェブクローラーは、ページのコンテンツを直接インデックスすることができます。そのため、ユニバーサルレンダリングは、迅速にインデックスしたいあらゆるコンテンツに最適な選択肢となります。

短所

  • 開発の制約: サーバーとブラウザーの環境は同じ API を提供していないため、両側でシームレスに動作するコードを書くのは難しい場合があります。幸いなことに、Nuxt はガイドラインと特定の変数を提供し、コードの一部がどこで実行されるかを決定するのに役立ちます。
  • 高コスト: オンザフライでページをレンダリングするためには、サーバーを稼働させる必要があります。これには、従来のサーバーと同様に毎月のコストがかかります。しかし、ユニバーサルレンダリングでは、クライアントサイドのナビゲーションをブラウザが引き継ぐため、サーバーの呼び出しは大幅に削減されます。

事例

ユニバーサルレンダリングは非常に汎用性が高く、ほとんどのユースケースに対応できます。特に、ブログマーケティングウェブサイトポートフォリオEコマースマーケットプレイス など、コンテンツ重視のウェブサイトに適しています。

概要

クライアントサイドとユニバーサルレンダリングは、ブラウザでインターフェイスを表示するための異なる戦略です。

デフォルトでは、Nuxt はより良いユーザー体験とパフォーマンスを提供し、検索エンジンのインデックスを最適化するために ユニバーサルレンダリング を使用しますが、1 行の設定 でレンダリングモードを切り替えることができます。

Nuxt 3 で登場

多くの場合、Nuxt 2 で実行されるユニバーサルレンダリングは、ユーザーと開発者に良い経験を提供します。しかし、Nuxt 3 では、ハイブリッドレンダリングとエッジサイドレンダリングを導入することにより、ユニバーサルレンダリングをさらに進化させます。

ハイブリッドレンダリング

ハイブリッドレンダリングは、ルートごとに異なるキャッシュルールを許可し、与えられた URL の新しいリクエストにサーバーがどのように応答すべきかを決定します。

現時点では、Nuxt アプリケーションのすべてのページ(または ルート)は、クライアントサイドまたはユニバーサルという同じレンダリングモードを使用する必要があります。しかし、さまざまなケースで、いくつかのページは構築時に生成され、他のページはクライアントサイドでレンダリングされる必要があります。たとえば、管理セクションを持つコンテンツウェブサイトを考えてみましょう。すべてのコンテンツページは主に静的で一度だけ生成されるべきですが、管理セクションは登録が必要で、より動的なアプリケーションのように動作します。

実装を議論し、コミュニティのフィードバックを収集するオープン RFC を読む

CDN エッジワーカーでのレンダリング

従来、サーバーサイドやユニバーサルレンダリングは、Node.js でなければできませんでした。Nuxt 3 は、CDN エッジワーカーのコードを直接レンダリングすることで、レイテンシー(データ通信の待ち時間)とコストを削減し、さらにレベルアップします。

Nitro は、Nuxt 3 を支える新しい サーバーエンジン です。Node.js、Deno、Workers などをクロスプラットフォームでサポートします。Nitro のデザインはプラットフォームにとらわれず、Nuxt アプリケーションをユーザーにより近いエッジでレンダリングし、レプリケーションとさらなる最適化を可能にします。

Nuxt 3 アプリケーションをサーバーレスプロバイダーにデプロイする

torish14torish14

自動インポート

Nuxt は、ヘルパー関数、Composables、Vue API を明示的にインポートしなくても、アプリケーション全体で使用できるように自動インポートします。ディレクトリ構造に基づいて、すべての Nuxt アプリケーションは、独自のコンポーネント、composables、プラグインのために自動インポートを使用することもできます。コンポーネント、composables、プラグインは、これらの関数を使用することができます。

古典的なグローバル宣言とは異なり、Nuxt は型付けと IDE の補完とヒントを保持し、プロダクションコードで実際に使用されるものだけを含めます。

Nuxt 自動インポート

Nuxt は、データの取得アプリのコンテキストランタイム設定 へのアクセス、状態 の管理、コンポーネントやプラグインの定義を行う関数やコンパイラを自動でインポートします。

<script setup>
  /* useAsyncData() と $fetch() は 自動インポートされます */
  const { data, refresh, pending } = await useAsyncData('/api/hello', () => $fetch('/api/hello'))
</script>

Vue 自動インポート

Vue 3 は、refcomputed といった Reactivity API や、Nuxt が自動でインポートするライフサイクルフックやヘルパーを公開しています。

<script setup>
  /* ref() and computed() are auto-imported */
  const count = ref(1)
  const double = computed(() => count.value * 2)
</script>

ディレクトリベースの自動インポート

Nuxt は定義されたディレクトリに作成されたファイルを直接自動インポートします。

  • Vue compoents のための components/
  • Vue composables のための composables/

明示的なインポート

Nuxt の自動インポートはすべて #imports というエイリアスで公開されており、必要に応じてこれを使用してインポートを明示することができます:

<script setup>
  import { ref, computed } from '#imports'

  const count = ref(1)
  const double = computed(() => count.value * 2)
</script>
torish14torish14

サーバーエンジン

Nuxt 3 は、"Nitro(ナイトロ)" と呼ばれる新しいサーバーエンジンを搭載しています。

このエンジンには多くの利点があります。

  • ✅ Node.js、ブラウザ、サービスワーカーなどのクロスプラットフォームのサポート
  • ✅ サーバーレスを即座にサポート
  • ✅ API ルートのサポート
  • ✅ 自動的なコード分割と非同期ロードされたチャンク
  • ✅ 静的サイト+サーバーレスサイトのハイブリッドモード
  • ✅ ホットモジュールリローディング機能付き開発サーバー

API レイヤー

サーバーの API のエンドポイントミドルウェアは、内部で h3 を使用している Nitro によって追加されます。

以下のような主要な機能があります:

  • ハンドラは、自動的に処理される JSON レスポンスに対して、オブジェクト/配列を直接返すことができる
  • ハンドラは待ち受けとなるプロミスを返すことができる(res.end()next() もサポートされている)
  • ボディパース、クッキー処理、リダイレクト、ヘッダーなどのヘルパー関数

詳しくは h3 ドキュメント をご覧ください。

API を直接呼び出す

Nitro では、グローバルに利用可能な $fetch ヘルパーを使用して、ルートを「直接」呼び出すことができます。これは、ブラウザ上で実行された場合はサーバーへの API 呼び出しを行いますが、サーバー上で実行された場合は単に関連する関数を呼び出すので、追加の API 呼び出しを節約することができます

$fetch API は、ohmyfetch を使用しており、主な機能は以下の通りです。

  • JSON レスポンスの自動パース(必要に応じて生のレスポンスにアクセス可能)
  • リクエストボディとパラメータは自動的に処理され、正しい Content-Type ヘッダーが追加されます。

$fetch 機能の詳細については、ohmyfetch を参照してください。

型付き API ルート

API ルート(またはミドルウェア)を使用する場合、res.end() を使用してレスポンスを送信するのではなく、値を返す限り、Nitro はこれらのルートに対する型付けを生成します。

これらの型には、$fetch()useFetch() を使用する際にアクセスすることができます。

スタンドアローン(単独で動作しているシステム)サーバー

Nitro は node_modules とは独立したスタンドアローンサーバー dist を生成します。

Nuxt 2 のサーバーはスタンドアローン(単独で動作しているシステム)ではなく、nuxt start の実行(nuxt-startnuxt ディストリビューションを使用)やカスタムプログラムによる使用など、nuxt core の一部が関与する必要があり、壊れやすくサーバーレスやサービスワーカーの環境には向いていないものでした。

この dist は、nuxt build を実行すると、.output ディレクトリに生成されます。

この出力は、ランタイムコードの両方と組み合わされ、Nuxt サーバーをあらゆる環境(実験的なブラウザサービスワーカーを含みます!)で実行し、静的ファイルを提供する、JAMstack のための真のハイブリッドフレームワークとなっています。さらに、ネイティブストレージレイヤーが実装されており、マルチソース、ドライバ、ローカルアセットをサポートしています。

torish14torish14

ネイティブ ES モジュール

Nuxt 3(および Bridge)は Native ES Modules を使用しています。
このガイドでは、ES モジュールとは何か、また Nuxt アプリ(または上流のライブラリ)を ESM に対応させる方法について説明します。

背景

CommonJS モジュール

CommonJS (CJS) は Node.js によって導入されたフォーマットで、分離された JavaScript モジュール間で機能を共有することができます (詳しくはこちら)。この構文にはすでに馴染みがあるかもしれません:

const a = require('./a')

module.exports.a = a

webpack や Rollup などのバンドラーはこの構文に対応しており、CommonJS で書かれたモジュールをブラウザで利用することができます。

ESM の構文

ESM vs CJS について話すとき、ほとんどの場合、モジュール を記述するための異なる構文について話しているのです。

import a from './a'

export { a }

ECMAScript Modules(ESM) が標準になる前に(10 年以上かかった!)、webpack のようなツールや TypeScript のような言語でさえ、いわゆる ESM 構文 をサポートするようになりました。しかし、実際の仕様とはいくつかの重要な違いがあります; ここでは 役に立つ説明 をしています。

'ネイティブ' ESM とは?

あなたは長い間、ESM 構文を使ってアプリを書いてきたかもしれません。Nuxt 2 では、あなたが書いたすべてのコードを適切なフォーマット(サーバは CJS、ブラウザは ESM)にコンパイルしています。

パッケージにインストールするモジュールを使用する場合は、少し事情が異なりました。サンプルライブラリは、CJS 版と ESM 版の両方を公開しており、どちらを使うか選択できるようになっています:

{
  "name": "sample-library",
  "main": "dist/sample-library.cjs.js",
  "module": "dist/sample-library.esm.js"
}

つまり、Nuxt 2 では、バンドル (webpack) はサーバビルド用に CJS ファイル ('main') を取り込み、クライアントビルド用に ESM ファイル ('module') を使用することになります。

しかし、最近の Node.js LTS リリースでは、Node.js の中で ネイティブの ESM モジュールを使用する ことができるようになりました。つまり、デフォルトではありませんが、Node.js 自体が ESM 構文を使って JavaScript を処理できるようになったのです。ESM 構文を有効にする最も一般的な方法は、次の 2 つです:

  • package.jsontype: 'module' を設定し、拡張子は .js を使用し続ける。
  • .mjs のファイル拡張子を使用する(推奨)

Nuxt Nitro では、このように .output/server/index.mjs ファイルを出力しています。これは、Node.js にこのファイルをネイティブ ES モジュールとして扱うように指示するものです。

Node.js の文脈で有効なインポートとは何ですか?

モジュールを require するのではなく import する場合、Node.js は異なる方法でモジュールを解決します。例えば、sample-library を import すると、Node.js は main ではなく、そのライブラリの package.json にある exportsmodule のエントリを探します。

これは、const b = await import('sample-library') のような動的インポートにも当てはまります。

Node は以下の種類のインポートをサポートしています(docs を参照)。

  1. .mjs で終わるファイル - ESM 構文を使用することが想定されています。
  2. .cjs で終わるファイル - CJS 構文を使用することが想定されています。
  3. .js で終わるファイル - package.jsontype: 'module' がない限り、CJS 構文を使用することが期待されます。

どのような問題があるのでしょうか?

長い間、モジュールの作者は ESM 構文のビルドを作成してきましたが、.esm.js.es.js といった規約を使用していました。これは今まで問題にはならなかったのですが、webpack のようなファイル拡張子を特に気にしない bundler によってのみ使用されてきたからです。

しかし、Node.js の ESM コンテキストで .esm.js ファイルを持つパッケージをインポートしようとすると、うまくいかず、次のようなエラーが表示されます:

(node:22145) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
/path/to/index.js:1

export default {}
^^^^^^

SyntaxError: Unexpected token 'export'
    at wrapSafe (internal/modules/cjs/loader.js:1001:16)
    at Module._compile (internal/modules/cjs/loader.js:1049:27)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
    ....
    at async Object.loadESM (internal/process/esm_loader.js:68:5)

また、Node.js が CJS とみなしている ESM-syntax ビルドからの名前付きインポートがある場合にも、このエラーが発生する可能性があります:

file:///path/to/index.mjs:5
import { named } from 'sample-library'
         ^^^^^
SyntaxError: Named export 'named' not found. The requested module 'sample-library' is a CommonJS module, which may not support all module.exports as named exports.

CommonJS modules can always be imported via the default export, for example using:

import pkg from 'sample-library'
const { named } = pkg

    at ModuleJob._instantiate (internal/modules/esm/module_job.js:120:21)
    at async ModuleJob.run (internal/modules/esm/module_job.js:165:5)
    at async Loader.import (internal/modules/esm/loader.js:177:24)
    at async Object.loadESM (internal/process/esm_loader.js:68:5)

ESM 問題のトラブルシューティング

これらのエラーが発生した場合、ほぼ間違いなく上流のライブラリに問題があります。上流のライブラリは Node でインポートできるように ライブラリを修正する 必要があります。

ライブラリのトラインスパイル

その間、build.transpile にこれらのライブラリを追加することで、Nuxt にこれらのライブラリをインポートしようとしないように指示することができます。

import { defineNuxtConfig } from 'nuxt3'

export default defineNuxtConfig({
  build: {
    transpile: ['sample-library']
  }
})

また、これらのライブラリによってインポートされる他のパッケージを追加する必要があることが分かるかもしれません。

ライブラリのエイリアス

また、場合によっては、CJS バージョンのライブラリに手動でエイリアスを設定する必要があることもあります。

import { defineNuxtConfig } from 'nuxt3'

export default defineNuxtConfig({
  alias: {
    'sample-library': ['sample-library/dist/sample-library.cjs.js']
  }
})

デフォルトのエクスポート

CommonJS 形式の依存関係では、module.exports または exports を使用して、デフォルトのエクスポートを提供することができます:

node_modules/cjs-pkg-index.js
module.exports = { test: 123 }
// or
exports.test = 123

require を使うこのような依存関係が必要な場合は、通常うまくいきます。

const pkg = require('cjs-pkg')

console.log(pkg) // { test: 123 }

Node.js のネイティブ ESM モード、esModuleInterop` を有効にした typescript、Webpack などのバンドラーでは、このようなライブラリをデフォルトでインポートできる互換性の仕組みが提供されています。この仕組みは、しばしば "interop require default" と呼ばれます:

import pkg from 'cjs-pkg'

console.log(pkg) // { test: 123 }

しかし、構文検出が複雑であったり、バンドル形式が異なるため、デフォルトの interop に失敗して、以下のような結果になる可能性があります。

import pkg from 'cjs-pkg'

console.log(pkg) // { default: { test: 123 } }

また、動的インポート構文(CJS ファイルと ESM ファイルの両方)を使用する場合、常にこのような状況が発生します:

ライブラリ作者のためのガイド

ESM の互換性の問題は、比較的簡単に解決できるのが良いところです。主に 2 つのオプションがあります。

  1. ESM ファイルの名前を変更し、末尾を .mjs にすることができます
    これは推奨される最もシンプルな方法です。ライブラリの依存関係やビルドシステムの問題を解決する必要があるかもしれませんが、ほとんどの場合、この方法で問題が解決されるはずです。また、CJS ファイルの名前を変更して、末尾を .cjs にすると、より明確になります。
  2. ライブラリ全体を ESM のみにすることもできます
    これは、package.jsontype: 'module' を設定し、ビルドされたライブラリが ESM 構文を使用することを保証することを意味します。しかし、依存関係の問題に直面する可能性があります - このアプローチは、あなたのライブラリは、ESM コンテキストでのみ消費できることを意味します。

移行

CJS から ESM への最初のステップは、require の使用法を import に変更することです:

Before
module.exports = ...

exports.hello = ...

or

After
export default ...

export const hello = ...
Before
const myLib = require('my-lib')

or

After
import myLib from 'my-lib'
// or
const myLib = await import('my-lib').then(lib => lib.default || lib)

ESM モジュールでは、CJS と異なり、requirerequire.resolve__filename__dirname グローバルは使用できないので、import()import.meta.filename に置き換えてください。

unjs/mllycreateCommonJS を使って、ESM に CJS 互換のコンテキストを作ることができます (あるいはインライン shim を使う)。

milly
import { createCommonJS } from 'mlly'

const { __dirname, __filename, require } = createCommonJS(import.meta.url)

or

manual
import { fileURLToPath } from 'url'
import { dirname } from 'path'
import { createRequire } from 'module'

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
const require = createRequire(import.meta.url)
Before
const someFile = require.resolve('./lib/foo.js')

or

After
import { resolvePath } from 'mlly'

const someFile = await resolvePath('my-lib', { url: import.meta.url })

ベストプラクティス

  • デフォルトのエクスポートではなく、名前付きのエクスポートを優先します。これは CJS のコンフリクトを減らすのに役立ちます。(デフォルトエクスポート のセクションを参照)
  • Node.js のビルトインや CommonJS、Node.js のみへの依存はできるだけ避け、Nitro のポリフィルを必要とせずにブラウザや Edge Worker で使用できるライブラリにすること。
  • 条件付きエクスポートで新しい エクスポート フィールドを使用する(続きを読む)。
{
  "exports": {
    ".": {
      "import": "./dist/mymodule.mjs"
    }
  }
}
torish14torish14

TypeScript

Nuxt 3 は完全に型付けされており、コーディング時に正確な型情報にアクセスできるよう、便利なショートカットを提供しています。

型チェック

Nuxt は、パフォーマンス上の理由から、nuxi devnuxi build を実行する際にデフォルトで型チェックを行いません。しかし、nuxi を使用して手動で型をチェックすることができます

yarn nuxi typecheck

自動生成される型

nuxi devnuxi build を実行すると、IDE の型サポート(と型チェック)のために、以下のファイルが生成されます。

.nuxt/nuxt.d.ts
このファイルには、使用しているすべてのモジュールの型と、Nuxt 3 が必要とする主要な型が含まれています。IDE はこれらのタイプを自動的に認識するはずです。

ファイル内のいくつかの参照は、buildDir (.nuxt)内にのみ生成されるファイルであるため、完全な型付けのためには、nuxi dev または nuxi build を実行する必要があります。

.nuxt/tsconfig.json
このファイルには、Nuxt や使用しているモジュールによって注入された解決済みのエイリアスを含む、プロジェクトに推奨される TypeScript の基本設定が含まれており、~/file#build/file といったエイリアスに対して、フルタイプのサポートとパスの自動補完を行うことができます。

この設定を拡張する方法については、こちらをご覧ください。

より厳密な型チェック

TypeScript は、プログラムの安全性と解析性を高めるために、ある種のチェック機能を備えています。

コードベースを TypeScript に変換し、使い慣れたら、これらのチェックを有効にすることで、より安全性を高めることができます。(続きを読む)

厳密な型チェックを有効にするためには、nuxt.config を更新する必要があります:

export default defineNuxtConfig({
  typescript: {
    strict: true
  }
})
torish14torish14

ドキュメント

データフェッチ

Nuxt は、アプリケーション内のデータ取得を処理するために、useFetch、useLazyFetch、useAsyncData、useLazyAsyncData を提供します。

useAsyncData

pages、components、plugins の中で、useAsyncData を使用すると、非同期に解決されるデータにアクセスすることができます。

使い方

const {
  data: Ref<DataT>,
  pending: Ref<boolean>,
  refresh: (force?: boolean) => Promise<void>,
  error?: any
} = useAsyncData(
  key: string,
  fn: () => Object,
  options?: { lazy: boolean, server: boolean }
)

パラメータ

  • key: データ取得がリクエスト間で適切に重複を回避できるようにするための一意のキー
  • fn 値を返す非同期関数。
  • options:
    • lazy: ナビゲーションをブロックするのではなく、ルートをロードした後に非同期関数を解決するかどうか (デフォルトは false)
    • default: 非同期関数が解決する前に、データのデフォルト値を設定するファクトリ関数 - lazy: true オプションと一緒に使うと特に便利です。
    • server: サーバサイドでデータを取得するかどうか (デフォルトは true)
    • transform: 解決後にfnの結果を変更するために使用できる関数
    • pick: fn結果からこの配列で指定されたキーのみをピックする。

useAsyncData は、以下のプロパティを持つオブジェクトを返す。

  • data: 渡された非同期関数の結果
  • pending: データがまだ取得されているかどうかを示すブール値
  • refresh: データを強制的に更新するために使用することができる関数です。
  • error: データ取得に失敗した場合のエラーオブジェクト

ボンネットの中では、lazy: false<Suspense> を使用して、データが取得される前にルートの読み込みをブロックしています。より快適なユーザーエクスペリエンスのために、lazy: true を使用し、代わりにロード状態を実装することを検討してください。

server/api/count.ts
let counter = 0
export default () => {
  counter++
  return JSON.stringify(counter)
}
app.vue
<script setup>
const { data } = await useAsyncData('count', () => $fetch('/api/count'))
</script>

<template>
  Page visits: {{ data }}
</template>

useLazyAsyncData

この composable は、lazy: true オプションを設定した useAsyncData と同じように動作します。言い換えれば、非同期関数はナビゲーションをブロックしません。つまり、データが null(または、カスタムのdefaultファクトリ関数で提供した任意の値)である状況を処理する必要があることを意味します。

useFetch

page、components、plugins 内で、useFetch を使用して、任意の URL から普遍的に取得することができます。

この composables は、useAsyncData$fetch の便利なラッパーを提供します。これは、URL とフェッチオプションに基づいてキーを自動的に生成し、API のレスポンスタイプも推測します。

使い方

const {
  data: Ref<DataT>,
  pending: Ref<boolean>,
  refresh: (force?: boolean) => Promise<void>,
  error?: any
} = useFetch(url: string, options?)

利用可能なオプション:

  • key: カスタムキーを指定する
  • ohmyfetch からのオプション
    • method: リクエストメソッド
    • params: クエリパラメータ
    • headers: リクエストヘッダー
    • baseURL:リクエストのベース URL
  • “useAsyncData` からのオプション
    • lazy
    • server
    • default
    • pick
    • transform

useFetch によって返されるオブジェクトは、useAsyncData によって返されるオブジェクトと同じプロパティを持ちます(上記参照)。

app.vue
<script setup>
const { data } = await useFetch('/api/count')
</script>

<template>
  Page visits: {{ data.count }}
</template>

useLazyFetch

この composable は、lazy: true オプションを設定した useFetch と同じように動作します。言い換えれば、非同期関数はナビゲーションをブロックしません。つまり、データが null(または、カスタムの default ファクトリ関数で提供した値)である状況を処理する必要があります。

Isomorphic(同型) fetch

ブラウザで fetch を呼び出すと、cookie などのユーザーヘッダが直接 API に送られます。しかし、サーバサイドレンダリングでは、fetch リクエストはサーバから発信されるため、ユーザのブラウザクッキーは含まれません。

useRequestHeaders を使って、サーバーサイドから API にアクセスし、クッキーをプロキシすることができます。

例:
以下の例では、同型の fetch 呼び出しにリクエストヘッダを追加し、API エンドポイントが、もともとユーザーによって送信されたのと同じ cookie ヘッダーにアクセスできるようにします。

<script setup>
const { data } = useFetch('/api/me', {
  headers: useRequestHeaders(['cookie'])
})
</script>

ベストプラクティス

これらの composables が返すデータは、ページのペイロード内に格納されます。つまり、返されたキーのうち、あなたのコンポーネントで使用されていないものはすべて、ペイロードに追加されることになります。

api/mountains/everest が次のようなオブジェクトを返すと想像してください:

{
  "title": "Mount Everest",
  "description": "Mount Everest is Earth's highest mountain above sea level, located in the Mahalangur Himal sub-range of the Himalayas. The China–Nepal border runs across its summit point",
  "height": "8,848 m",
  "countries": [
    "China",
    "Nepal"
  ],
  "continent": "Asia",
  "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/600px-Everest_kalapatthar.jpg"
}

もし、あなたのコンポーネントで titledescription だけを使うつもりなら、$fetch の結果か pick オプションでキーを選択することができます:

<script setup>
const { data: mountain } = await useFetch('/api/mountains/everest', { pick: ['title', 'description'] })
</script>

<template>
  <h1>{{ mountain.title }}</h1>
  <p>{{ mountain.description }}</p>
</template>

async setup を使用

async setup() を使用している場合、現在のコンポーネント・インスタンスは最初の await の後に失われます。(これは Vue 3 の制限です。) 複数の useFetch の呼び出しなど、複数の非同期処理を使用したい場合は、<script setup> を使用するか、setup の最後でまとめて await する必要があります。

<script>
export default defineComponent({
  async setup() {
    const [{ data: organization }, { data: repos }] = await Promise.all([
      useFetch(`https://api.github.com/orgs/nuxt`),
      useFetch(`https://api.github.com/orgs/nuxt/repos`)
    ])

    return {
      organization,
      repos
    }
  }
})
</script>

<template>
  <header>
    <h1>{{ organization.login }}</h1>
    <p>{{ organization.description }}</p>
  </header>
</template>

State

Nuxt は、コンポーネント間でリアクティブで SSR に優しい共有状態を作成するために useState composableを提供します。

useState は SSR に優しい ref の代替品です。その値は、サーバー側のレンダリング(クライアント側のハイドレーション中)後に保持され、一意のキーを使用してすべてのコンポーネント間で共有されます。

署名

useState<T>(key: string, init?: () => T): Ref<T>
  • key: データ取得がリクエスト間で適切に重複しないようにするための一意のキー
  • init: 状態が開始されていないときに初期値を提供する関数
  • T: (typescriptのみ)状態の種類を指定します
👉 `useState` は `setup` もしくはライフサイクルフックでのみ機能します。

ベストプラクティス

基本的な使い方

この例では、コンポーネントローカルのカウンタ状態を使用しています。useState('counter') を使用する他のコンポーネントは、同じリアクティブステートを共有します。

app.vue
<script setup>
const counter = useState('counter', () => Math.round(Math.random() * 1000))
</script>

<template>
  <div>
    Counter: {{ counter }}
    <button @click="counter++">
      +
    </button>
    <button @click="counter--">
      -
    </button>
  </div>
</template>

StackBlitz を開く

応用

この例では、HTTPリクエストヘッダからユーザーのデフォルトロケールを検出し、locale 状態を保持するコンポーザブルを使用しています。

StackBlitz を開く

state を共有する

自動インポートされる composables を使用することで、グローバルな型安全の状態を定義し、アプリ全体でインポートすることができます。

composables/state.ts
export const useCounter = () => useState<number>('counter', () => 0)
export const useColor = () => useState<string>('color', () => 'pink')
app.vue
<script setup>
const color = useColor() // Same as useState('color')
</script>

<template>
  <p>Current color: {{ color }}</p>
</template>

メタタグ

自分のサイトのメタタグは、いくつかの方法でカスタマイズすることができます。

useMeta Composable

setup 関数内で、メタタグに対応するキーを持つメタプロパティのオブジェクトで useMeta を呼び出すことができます: title, base, script, style, meta and link, 同様に htmlAttrsbodyAttrs です。あるいは、反応するメタデータ用のオブジェクトを返す関数を渡すこともできます

例:

export default {
  setup () {
    useMeta({
      meta: [
        { name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1' }
      ],
      bodyAttrs: {
        class: 'test'
      }
    })
  }
}

メタコンポーネント

Nuxt は <Title>, <Base>, <Script>, <Style>, <Meta>, <Link>, <Body>, <Html>, <Head> コンポーネントを提供し、コンポーネントのテンプレート内で直接メタデータと対話できるようにします。

これらのコンポーネント名はネイティブの HTML 要素に一致するため、テンプレート内で大文字で記述することが非常に重要です。

<Head><Body> はネストした meta タグを受け入れることができますが(美観上の理由から)、これは最終的なHTML の中でネストした meta タグがレンダリングされる場所に影響を与えません。

例:

app.vue
<template>
  <div>
    Hello World
    <Html :lang="dynamic > 50 ? 'en-GB' : 'en-US'">
      <Head>
        <Title>{{ dynamic }} title</Title>
        <Meta name="description" :content="`My page's ${dynamic} description`" />
        <Link rel="preload" href="/test.txt" as="script" />
        <Style type="text/css" :children="styleString" />
      </Head>
    </Html>

    <button class="blue" @click="dynamic = Math.random() * 100">
      Click me
    </button>
  </div>
</template>

<script>
export default {
  data: () => ({ dynamic: 49, styleString: 'body { background-color: green; }' })
}
</script>

NuxtApp

Nuxt 3 では、composables、components、plugins の中でランタイムアプリコンテキストにアクセスすることができます。

Nuxt 2 では、これは Nuxt コンテキストと呼ばれていました。

NuxtAppへのアクセス

composables、plugins、components 内では、useNuxtAppnuxtApp にアクセスすることができます。

import { useNuxtApp } from '#app'

function useMyComposable () {
  const nuxtApp = useNuxtApp()
  // access runtime nuxt app instance
}

プラグインも便宜上、第1引数として nuxtApp を受け取ります。プラグインについてもっと読む。

ヘルパーの提供

すべての composables とアプリケーションで使用できるヘルパーを提供することができます。これは通常、Nuxtプラグイン内で行われます。

const nuxtApp = useNuxtApp()
nuxtApp.provide('hello', (name) => `Hello ${name}!`)

console.log(nuxtApp.$hello('name')) // Prints "Hello name!"

Nuxt 2 のプラグインでは、これは inject function と呼ばれていました。

ランタイム設定

Nuxt は、アプリケーションおよび API ルート内のランタイムの設定を定義するための API を提供します。

ランタイムの設定を公開する

アプリの残りの部分に設定と環境変数を公開するには、privateRuntimeConfig または publicRuntimeConfig オプションを使って nuxt.config ファイルに実行時設定を定義する必要があります(アプリのクライアントサイド部分でアクセス可能にしたいのかどうかに基づいています)。

例:

nuxt.config.ts
export default defineNuxtConfig({
  publicRuntimeConfig: {
    API_BASE: '/api'
  },
  privateRuntimeConfig: {
    API_SECRET: '123'
  }
})

publicRuntimeConfigAPI_BASE を追加すると、Nuxt はページのペイロードに API_BASE を追加します。こうすることで、サーバとブラウザの両方で普遍的に API_BASE にアクセスすることができます。

環境変数

設定を提供する最も一般的な方法は、環境変数を使用することです。Nuxt CLI には dotenv のサポートが組み込まれています。

プロセスの環境変数に加えて、プロジェクトのルートディレクトリに .env ファイルがあれば、自動的に process.env に読み込まれ、nuxt.config ファイルとモジュール内でアクセスできるようになります。

**例:*:

BASE_URL=https://nuxtjs.org
API_SECRET=api_secret_token
nuxt.config.ts
export default defineNuxtConfig({
  publicRuntimeConfig: {
    BASE_URL: process.env.BASE_URL
  },
  privateRuntimeConfig: {
    API_SECRET: process.env.API_SECRET
  }
})

💡 Tip: 必要ではありませんが、同一のランタイムの設定名を環境変数として使用することで、プラットフォーム環境変数を使用して、実運用環境で簡単に上書きすることができます。

ランタイムの設定にアクセスする

Vue アプリ

Nuxt アプリの Vue パート内で、useRuntimeConfig() を呼び出してランタイムの設定にアクセスする必要があります。

Note: クライアントサイドとサーバーサイドで動作が異なります。

  • クライアント側では、publicRuntimeConfig のみが利用可能で、このオブジェクトは書き込み可能かつ反応可能です。
  • サーバー側では、publicRuntimeConfigprivateRuntimeConfig の両方がマージされ、オブジェクトはコンテキスト共有を避けるために読み取り専用になります。
<template>
  <div>
    <div>Token: {{ config.API_AUTH_TOKEN }}</div>
  </div>
</template>

<script setup>
const config = useRuntimeConfig()
</script>

🛑 Security note: API_AUTH_TOKEN がプライベートな設定の場合、上記の例は絶対に使用しないでください。privateRuntimeConfig を使用する場合でも、そのような設定を payload や html に公開しないように注意しなければなりません!

プラグイン

もし、任意の(カスタム)プラグイン内でランタイムの設定を使用したい場合は、defineNuxtPlugin 関数内でuseRuntimeConfig() を使用することができます。

例えば、

export default defineNuxtPlugin((nuxtApp) => {
  const config = useRuntimeConfig();

  const url = process.server ? config.serverUrl : config.clientUrl;
  
  // Do something with url & isServer.
});

API routes

API ルート内では、virtual #config から直接インポートすることで、ランタイムの設定にアクセスすることができます。

import config from '#config'

export default async () => {
  const result = await $fetch('https://my.api.com/test', {
    headers: {
      Authorization: `Bearer ${config.API_AUTH_TOKEN}`
    }
  })
  return result
}

ランタイムの設定を入力する

現在、ランタイムの設定を手動で入力することが可能です。

index.d.ts
declare module '@nuxt/schema' {
  interface PublicRuntimeConfig {
    testConfig: string
  }
  interface PrivateRuntimeConfig {
    token: string
  }
}
// It is always important to ensure you import/export something when augmenting a type
export {}

Cookies

Nuxt は、Cookie を読み書きするための SSR に適した composable を提供します。

使用方法

ページ、コンポーネント、プラグインの中で、useCookie を使用して、特定のクッキーにバインドされたリアクティブなリファレンスを作成することができます。

const cookie = useCookie(name, options)

以下の例では、counter という Cookie を作成しています。この Cookie が存在しない場合、初期値としてランダムな値が設定されます。counter 変数を更新するたびに、それに応じて Cookie も更新されます。

<template>
  <div>
    <h1> Counter: {{ counter || '-' }}</h1>
    <button @click="counter = null">
      reset
    </button>
    <button @click="counter--">
      -
    </button>
    <button @click="counter++">
      +
    </button>
  </div>
</template>

<script setup>
const counter = useCookie('counter')
counter.value = counter.value || Math.round(Math.random() * 1000)
</script>

Open on StackBlitz

オプション

Cookie composable は、Cookie の動作を変更するためのオプションをいくつか用意しています。

ほとんどのオプションは、cookie パッケージに直接渡されます。

maxAge / expires

maxAge は、Max-Age Set-Cookie 属性の値となる数値(秒)を指定します。指定された数値は切り捨てにより整数に変換されます。デフォルトでは、最大年齢は設定されていません。

expires は、 Expires Set-Cookie 属性の値として Date オブジェクトを指定します。デフォルトでは、有効期限は設定されていません。ほとんどのクライアントはこれを「非永続的なクッキー」とみなし、ウェブブラウザアプリケーションを終了するなどの条件でこれを削除します。

httpOnly

HttpOnly Set-Cookie 属性の boolean 値を指定します。真値の場合は HttpOnly 属性が設定され、そうでない場合は設定されません。デフォルトでは、HttpOnly 属性は設定されていません。

secure

secure は、Secure Set-Cookie 属性の boolean 値を指定します。真値の場合は、Secure 属性が設定され、それ以外の場合は設定されません。デフォルトでは、Secure 属性は設定されていません。

domain

domain は、Domain Set-Cookie 属性の値を指定します。デフォルトでは、ドメインは設定されておらず、ほとんどのクライアントは、現在のドメインにのみクッキーを適用することを考慮します。

path

path は、Path Set-Cookie 属性の値を指定します。デフォルトでは、このパスは "デフォルトパス "とみなされます。

sameSite

sameSite は、SameSite Set-Cookie 属性の boolean 値または string 値を指定します。

  • true を指定すると、SameSite 属性が Strict に設定され、同サイトが厳格に施行されます。
  • false を指定すると、SameSite 属性は設定されません。
  • 'lax' は、SameSite 属性を Lax に設定し、同サイトの強制を緩やかにします。
  • 'none' は、明示的なクロスサイトクッキーのために、SameSite 属性を None に設定します。
  • 'strict' は、SameSite 属性を厳密な同サイト強制のための Strict に設定します。

異なる実施レベルに関するより詳しい情報は、仕様書を参照してください。

encode

クッキーの値をエンコードするために使用する関数を指定します。クッキーの値は文字数が限られている(単純な文字列でなければならない)ので、この関数を使うと、値をクッキーの値に適した文字列にエンコードすることができます。

デフォルトのエンコーダーは、JSON.stringify + encodeURIComponent です。

decode

クッキーの値をデコードするために使用する関数を指定します。クッキーの値は文字数が限られている(単純な文字列でなければならない)ので、この関数を使うと、以前にエンコードしたクッキーの値を JavaScript の文字列や他のオブジェクトにデコードすることができます。

デフォルトのデコーダーは decodeURIComponent + destr です。

h3 パッケージの useCookiesetCookie を使用すると、サーバー API ルートにクッキーを設定することができます。

例:

import { useCookie, setCookie } from 'h3'

export default (req, res) => {
  // counter クッキーを読み取る
  let counter = useCookie(req, 'counter') || 0

  // 1 まで counter クッキーを増やす
  setCookie(res, 'counter', ++counter)

  // JSON レスポンスを送る
  return { counter }
}

SSR ユーティリティ

Nuxt は、ファーストクラスのサーバサイドレンダリングをサポートする composables とユーティリティを提供します。

useRequestHeaders

ページ、コンポーネント、プラグインの中で useRequestHeaders を使用すると、受信したリクエストヘッダーにアクセスすることができます。

// すべてのリクエストヘッダーを取得する
const headers = useRequestHeaders()

// cookie リクエストヘッダーのみを取得する
const headers = useRequestHeaders(['cookie'])
torish14torish14

Nuxt ディレクトリ

.nuxt/ ディレクトリは、開発中の Nuxt が Vue アプリケーションを生成するために使用されます。

このディレクトリは、あなたが、ディレクトリ構造に基づいて Nuxt が生成するファイルについてもっと学びたい場合に興味深いものです。

Output ディレクトリ

output/ ディレクトリは、Nuxt がアプリケーションを本番用にビルドする際に作成されます。

このディレクトリは、Nuxt アプリケーションを本番環境にデプロイする際に使用されるように作られています。詳しくは、デプロイメントのセクション をご覧ください。

Assets ディレクトリ

assets/ ディレクトリは、ビルドツール(Webpack または Vite)で処理されるすべての Web サイト資源を追加するために使用されます。

このディレクトリには、通常、次のような種類のファイルが含まれています:

  • スタイルシート(CSS、SASS など)
  • フォント
  • public/ ディレクトリから配信されない画像。

サーバーからアセットを提供したい場合は、public/ ディレクトリを参照することを推奨します。

Components ディレクトリ

components/ ディレクトリには、すべての Vue コンポーネントを配置し、pages や他のコンポーネントの中に取り込むことができます(詳細はこちら)。

Nuxt は components/ ディレクトリにあるコンポーネントを自動的にインポートします(使用しているモジュールによって登録されているコンポーネントも同様です)。

| components/
--| TheHeader.vue
--| TheFooter.vue
layouts/default.vue
<template>
  <div>
    <TheHeader />
    <slot />
    <TheFooter />
  </div>
</template>

コンポーネントの名前

以下のような、ネストされたディレクトリにコンポーネントがある場合:

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

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

<BaseFooButton />

動的なコンポーネント

Vue の <component :is="someComputedComponent"> 構文を使用したい場合は、Vue が提供する resolveComponent ヘルパーを使用する必要があります。

例えば:

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

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

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

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

動的なインポート

コンポーネントを動的にインポートする(コンポーネントの lazy-loading とも呼ばれる)ために必要なことは、コンポーネントの名前に Lazy というプレフィックスを追加することです。

layouts/default.vue
<template>
  <div>
    <TheHeader />
    <slot />
    <LazyTheFooter />
  </div>
</template>

これは、コンポーネントが常に必要とされるわけではない場合に特に有効です。Lazy プレフィックスを使用すると、適切なタイミングまでコンポーネントコードの読み込みを遅らせることができ、JavaScript のバンドルサイズを最適化するのに便利です。

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

<script>
export default {
  data() {
    return {
      show: false
    }
  }
}
</script>

<ClientOnly> コンポーネント

Nuxt は、意図的にクライアント側のみでコンポーネントをレンダリングするための <ClientOnly> コンポーネントを提供します。クライアントにのみコンポーネントをインポートするには、クライアントサイドのみのプラグインにコンポーネントを登録します。

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

クライアント側で <ClientOnly> がマウントされるまでのフォールバック(システムの一部に障害が発生したとき、システム全体の運転を停止させることなく、違った方法で処理を行うこと)として、スロットを使用します。

pages/example.vue
<template>
  <div>
    <Sidebar />
    <ClientOnly>
      <!-- このコンポーネントは、クライアントサイドでのみレンダリングされます -->
      <Comments />
      <template #fallback>
        <!-- これはサーバーサイドでレンダリングされます -->
        <p>Loading comments...</p>
      </template>
    </ClientOnly>
  </div>
</template>

ライブラリ作者

自動 tree shake(実行されないコードを削除すること)とコンポーネント登録ができる Vue コンポーネントライブラリの作成が超簡単です✨

components:dirs フックを使用すると、Nuxt モジュールにユーザー設定をすることなく、簡単にディレクトリリストを拡張することができます。

このようなディレクトリ構造をイメージしてください:

| node_modules/
---| awesome-ui/
------| components/
---------| Alert.vue
---------| Button.vue
------| nuxt.js
| pages/
---| index.vue
| nuxt.config.js

そして、awesome-ui/nuxt.jscomponents:dirs フックを使用することができます:

import { join } from 'pathe'
import { defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  hooks: {
    'components:dirs'(dirs) {
      // ./components ディレクトリをリストに追加する
      dirs.push({
        path: join(__dirname, 'components'),
        prefix: 'awesome'
      })
    }
  }
})

以上です!これで、あなたのプロジェクトでは、nuxt.config ファイルで UI ライブラリを Nuxt モジュールとしてインポートすることができます:

export default {
  buildModules: ['awesome-ui/nuxt']
}

... そして、モジュールコンポーネント(接頭辞が awesome-)を直接 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(Hot Module Replacement、画面の再描画無しに JS の変更をブラウザに適用してくれるもの) をサポートするようにします。

Composables ディレクトリ

Nuxt 3 は composables/ ディレクトリをサポートしており、自動インポート機能を使って Vue の composables をアプリケーションに自動的にインポートすることができます !

ファイルのスキャン方法

composables/ ディレクトリのトップレベルにあるファイル(またはサブディレクトリ内のインデックスファイル)だけが、composables のスキャン対象となります。

例:

composables
 | - useFoo.ts
 | - useBar
 | --- supportingFile.ts
 | --- index.ts

useFoo.tsuseBar/index.ts だけがインポートとして検索され、後者がデフォルトのエクスポートであれば、index ではなく useBar として登録されるでしょう。

例(名前付き export を使用)

composables/useFoo.ts
export const useFoo = () => {
  return useState('foo', () => 'bar')
}

例(default export を使用)

composables/use-foo.ts or composables/useFoo.ts
// useFoo()(拡張子なしのファイル名のキャメルケース)として利用可能になります
export default function () {
  return useState('foo', () => 'bar')
}

これで自動インポートができるようになりました:

app.vue
<template>
  <div>
    {{ foo }}
  </div>
</template>

<script setup>
const foo = useFoo()
</script>

Layouts ディレクトリ

Nuxt は、アプリケーション全体で使用できるカスタマイズ可能なレイアウトフレームワークを提供し、共通の UI またはコードパターンを再利用可能なレイアウトコンポーネントに抽出するために理想的です。

レイアウトは、layouts/ ディレクトリに置かれ、使用時に非同期インポートによって自動的に読み込まれます。layouts/default.vue を作成すると、アプリ内のすべてのページでこのレイアウトが使用されます。その他のレイアウトは、ページのメタデータの一部として layout プロパティを設定するか(~/pages 統合を使用している場合)、<NuxtLayout> コンポーネントを使って使用されます。

アプリケーションにレイアウトが 1 つしかない場合は、代わりに app.vue を使用することができます。

例: app.vue でレイアウトを有効にする

-| layouts/
---| custom.vue
-| app.vue

レイアウトファイルでは、<slot /> を使って、レイアウトのコンテンツが読み込まれる場所を定義する必要があります。例えば:

layouts/custom.vue
<template>
  <div>
    一部のレイアウトコンテンツを共有
    <slot />
  </div>
</template>

そのレイアウトを app.vue でどのように使うかを説明します。

app.vue
<template>
  <NuxtLayout name="custom">
    Hello world!
  </NuxtLayout>
</template>

例: ~/pages でレイアウトを設定する

-| layouts/
---| custom.vue
-| pages/
---| index.vue

このように、pages コンポーネントの中でレイアウトを設定することができます:

<script>
// これは、 `<script setup>` の内部でも同様に動作します
definePageMeta({
  layout: 'custom',
})
</script>

例: ~/pages による手動制御

たとえ ~/pages 統合を使っていても、<NuxtLayout> コンポーネント(アプリケーション全体でグローバルに利用可能)を使って layout: false を設定すれば、完全に制御することができます。

<template>
  <NuxtLayout name="custom">
    <template #header> Some header template content. </template>

    The rest of the page
  </NuxtLayout>
</template>

<script setup>
definePageMeta({
  layout: false,
})
</script>

例: レイアウトの変更

また、レイアウトに ref や computed プロパティを使用することもできます。

<template>
  <div>
    <button @click="enableCustomLayout">Update layout</button>
  </div>
</template>

<script setup>
const route = useRoute()
function enableCustomLayout () {
  route.meta.layout = "custom"
}
definePageMeta({
  layout: false,
})
</script>
torish14torish14

Middleware ディレクトリ

Nuxt は、アプリケーション全体で使用できるカスタマイズ可能なルートミドルウェアフレームワークを提供し、特定のルートにナビゲートする前に実行したいコードを抽出するのに適しています。

ルートミドルウェアは 3 種類あります:

  1. 匿名(またはインライン)ルートミドルウェアは、使用するページで直接定義されます。
  2. 名前付きルートミドルウェアは、middleware/ ディレクトリに置かれ、ページで使用される際に非同期インポートによって自動的に読み込まれます。
  3. グローバルルートミドルウェアは、middleware/ ディレクトリに置かれ(拡張子は .globalで)、ルートが変更されるたびに自動的に実行されるルートミドルウェアです。

最初の 2 種類のルートミドルウェア(匿名、名前付きルートミドルウェア)は、definePageMeta で定義することができます

フォーマット

ルートミドルウェアは、現在のルートと次のルートを引数として受け取るナビゲーションガードです。

export default defineNuxtRouteMiddleware((to, from) => {
  if (to.params.id === '1') {
    return abortNavigation()
  }
  return navigateTo('/')
})

Nuxt は、ミドルウェアから直接返すことができるグローバルに利用可能なヘルパーを 2 つ提供しています。

  1. navigateTo (route: string | Route) - プラグインやミドルウェアの中で、指定されたルートにリダイレクトします。また、クライアントサイドで直接呼び出すこともできて、ページナビゲーションを実行します。
  2. abortNavigation (err?: string | Error) - オプションのエラーメッセージとともに、ナビゲーションを中止します。

vue-router のドキュメント にあるナビゲーションガードとは異なり、3 番目の next() 引数は渡されず、リダイレクトやルートのキャンセルは、ミドルウェアから値を返すことによって処理されます。可能な戻り値は以下の通りです:

  • nothing - ナビゲーションをブロックせず、次のミドルウェア関数があればそれに移行するか、ルートナビゲーションを完了します。
  • navigateTo('/') または navigateTo({ path: '/' }) - 指定されたパスにリダイレクトします。
  • abortNavigation() - 現在のナビゲーションを停止します。
  • abortNavigation(error) - エラーで現在のナビゲーションを拒否します。

ミドルウェアを動的に追加する

プラグインなどのヘルパー関数 addRouteMiddleware() を使って、 グローバルまたは名前つきルートミドルウェアを手動で追加することができます。

export default defineNuxtPlugin(() => {
  addRouteMiddleware('global-test', () => {
    console.log('このグローバルルートミドルウェアはプラグインで追加され、ルート変更のたびに実行されます')
  }, { global: true })

  addRouteMiddleware('named-test', () => {
    console.log('この名前付きミドルウェアはプラグインで追加され、既存の同名のミドルウェアを上書きします')
  })
})

例: 名前付きルートミドルウェア

-| middleware/
---| auth.ts

ページファイル内で、このルートミドルウェアを参照することができます。

<script setup>
definePageMeta({
  middleware: ["auth"]
  // or middleware: 'auth'
})
</script>

さて、そのページへのナビゲーションが完了する前に、auth ルートミドルウェアが実行されます。

Node modules ディレクトリ

node_modules ディレクトリは、パッケージマネージャ(npm または yarn )によって作成され、プロジェクトの依存関係を保存します。

Pages ディレクトリ

Nuxt は、Vue Router を裏で使用して、Web アプリケーション内にルートを作成するためのファイルベースのルーティングを提供します。

使い方

pages は、Vue コンポーネントで、拡張子は vuejstss または tsx です。

pages/index.vue
<template>
  <h1>Index page</h1>
</template>

or

pages/index.ts
// https://vuejs.org/guide/extras/render-function.html
export default defineComponent({
  render () {
    return h('h1', 'Index page')
  }
})

or

pages/index.tsx
// https://vuejs.org/guide/extras/render-function.html#jsx-tsx
export default defineComponent({
  render () {
    return <h1>Index page</h1>
  }
})

pages/index.vue ファイルは、アプリケーションの / ルートにマップされます。

app.vue を使用している場合は、<NuxtPage /> コンポーネントを使用して、現在のページを表示することを確認してください。

app.vue
<template>
  <div>
    <!-- 全ページで共有されるマークアップ(例: NavBar) -->
    <NuxtPage />
  </div>
</template>

動的なルート

角括弧([])の中に何かを入れると、それが 動的なルート のパラメータになります。複数のパラメータを混在させたり、ファイル名やディレクトリ内の非ダイナミックテキストも混在させることができます。

-| pages/
---| index.vue
---| users-[group]/
-----| [id].vue

上記の例では、$route オブジェクトを介してコンポーネント内の group/id にアクセスすることができます:

pages/users-[group]/[id].vue
<template>
  <p>{{ $route.params.group }} - {{ $route.params.id }}</p>
</template>

users-admins/123 に移動するとレンダリングされます:

<p>admins - 123</p>

Composition API を使ってルートにアクセスしたい場合は、 グローバルな useRoute 関数を使えば、 Options API の this.$route と同じようにルートにアクセスすることができます。

<script setup>
const route = useRoute()

if (route.params.group === 'admins' && !route.params.id) {
  console.log('Warning! Make sure user is authenticated!')
}
</script>

Catch all ルート

catch-all ルート(Next.js にあるやつ)が必要な場合は、[...slug].vue のような名前のファイルを使用して作成します。これは、そのパスの下にある すべての ルートにマッチするので、非ダイナミック・テキストをサポートしません。

pages/[...slug].vue
<template>
  <p>{{ $route.params.slug }}</p>
</template>

/hello/world に移動するとレンダリングされます:

<p>["hello", "world"]</p>

ネストしたルート

<NuxtPage> を使用して、ネストしたルート を表示することができます。

例:

-| pages/
---| parent/
------| child.vue
---| parent.vue

このファイルツリーは、これらのルートを生成します:

[
  {
    path: '/parent',
    component: '~/pages/parent.vue',
    name: 'parent',
    children: [
      {
        path: 'child',
        component: '~/pages/parent/child.vue',
        name: 'parent-child'
      }
    ]
  }
]

child.vue コンポーネントを表示するには、pages/parent.vue の中に <NuxtPage> コンポーネントを挿入する必要があります:

pages/parent.vue
<template>
  <div>
    <h1>I am the parent view</h1>
    <NuxtPage :foobar="123" />
  </div>
</template>

子ルートキー

<NuxtPage> コンポーネントが再レンダリングされるタイミングをより細かく制御したい場合(例えば、遷移のため)、pageKey プロパティを通じて文字列または関数を渡すか、definePageMeta を通じて key 値を定義することが可能です:

pages/parent.vue
<template>
  <div>
    <h1>I am the parent view</h1>
    <NuxtPage :page-key="someKey" />
  </div>
</template>

もしくは、代わりとして:

pages/child.vue
<script setup>
definePageMeta({
  key: route => route.fullPath
})
</script>

ページメタデータ

アプリ内の各ルートに対してメタデータを定義したい場合があります。これは、definePageMeta マクロを使用して行うことができ、<script><script setup> の両方で機能します:

<script setup>
definePageMeta({
  title: 'My home page'
})
</script>

このデータは、アプリの残りの部分で route.meta オブジェクトからアクセスすることができます。

<script setup>
const route = useRoute()
console.log(route.meta.title) // 自分のホームページ
</script>

ネストされたルートを使用している場合、これらすべてのルートからのページメタデータは、単一のオブジェクトにマージされます。ルートメタの詳細については、vue-router のドキュメントを参照してください。

defineEmitsdefinePropsVue のドキュメント を参照)と同様に、definePageMetaコンパイラマクロ です。これはコンパイルされてしまうので、コンポーネント内で参照することはできません。その代わり、このマクロに渡されたメタデータは、コンポーネントの外に持ち出されます。したがって、ページメタオブジェクトはコンポーネント(またはコンポーネント上で定義された値)を参照することはできません。しかし、インポートされたバインディングを参照することはできます。

<script setup>
import { someData } from '~/utils/example'

const title = ref('')

definePageMeta({
  title,  // これはエラーになります
  someData
})
</script>
特別なメタデータ

もちろん、アプリ内で使用するメタデータは自由に定義していただいてかまいません。しかし、definePageMeta で定義されるメタデータの中には、特定の目的を持ったものがあります:

keepalive

Nuxt は definePageMetakeepalive: true を設定すると、自動的に Vue の <KeepAlive> コンポーネント でページをラップしてくれます。これは、例えば、動的な子ルートを持つ親ルートで、ルートが変わってもページの状態を保持したい場合に便利でしょう。また、<KeepAlive> に渡す props を設定することもできます(詳しくは こちら をご覧ください)。

key

ルートキーの項目を確認してください。

layout

ルートのレンダリングに使用するレイアウトを定義することができます。これは false (レイアウトを無効にする)、文字列、または何らかの方法で反応させたい場合は ref/computed のいずれかになります。詳細は、レイアウトディレクトリの項目をご覧ください。

middleware

このページを読み込む前に適用するミドルウェアを定義することができます。このミドルウェアは、一致する親子ルートで使用されている他のすべてのミドルウェアと統合されます。文字列、関数(global before guard パターン に従った匿名/インライン化されたミドルウェア関数)、または文字列/関数の配列が利用できます。詳細は、名前付きミドルウェア の項目をご覧ください。

layoutTransitionpageTransition

ページやレイアウトをラップする <transition> コンポーネントに遷移のプロパティを定義したり、false を渡してそのルートの <transition> ラッパーを無効にしたりすることが可能です。詳細は、トランジション の項目をご覧ください。

ナビゲーション

アプリケーションのページ間を移動するには、<NuxtLink> コンポーネントを使用する必要があります。

このコンポーネントは Nuxt に含まれているため、他のコンポーネントのようにインポートする必要はありません。

HTML の <a> タグと似ていますが、href="/about" の代わりに to="/about" を使用する点が異なります。vue-router を使ったことがあるなら、<NuxtLink><RouterLink> の代わりと考えることができます。

これは、pages フォルダーにある index.vue のページへのシンプルなリンクです:

<template>
  <NuxtLink to="/">Home page</NuxtLink>
</template>

また、<NuxtLink> コンポーネントは、すべての内部リンクに使用する必要があります。つまり、サイト内のページへのリンクはすべて <NuxtLink> を使用する必要があります。<a> タグは、すべての外部リンクに使用されます。つまり、他のサイトへのリンクは <a> タグを使用します。

<template>
  <div>
    <h1>Home page</h1>
    <NuxtLink to="/about">
      About (internal link that belongs to the Nuxt App)
    </NuxtLink>
    <a href="https://nuxtjs.org">External Link to another page</a>
  </div>
</template>

ルーターオプション

デフォルトの vue-router のオプションを設定することができます。

注意: historyroutes のオプションは、常に Nuxt によってオーバーライド(ある場所で定義された設定や属性などを、別の定義で上書きすること)されます。

app/router.options の使い方

ルーターオプションの指定には、この方法が推奨されます。

app/router.options.ts
import type { RouterOptions } from '@nuxt/schema'

// https://router.vuejs.org/api/#routeroptions
export default <RouterOptions>{
}

nuxt.config の使い方

注意: JSON シリアライズ(オプジェクト化されたデータを JSON 文字列に変換すること)可能なオプションのみ設定可能です。

  • linkActiveClass
  • linkExactActiveClass
  • end
  • sensitive
  • strice
nuxt.config.ts
export default defineNuxtConfig({
  router: {
    // https://router.vuejs.org/api/#routeroptions
    options: {}
  }
})

Plugins ディレクトリ

Nuxt は plugins ディレクトリにあるファイルを自動的に読み込んでロードします。ファイル名に .server または .client という接尾辞を使用すると、サーバサイドまたはクライアントサイドでのみプラグインを読み込むことができます。

どのファイルが登録されるか

plugins/ ディレクトリのトップレベルにあるファイル(またはサブディレクトリ内のインデックスファイル)だけがプラグインとして登録されます。

例えば:

plugins
 | - myPlugin.ts
 | - myOtherPlugin
 | --- supportingFile.ts
 | --- componentToRegister.vue
 | --- index.ts

myPlugin.tsmyOtherPlugin/index.ts のみが登録されることになります。

プラグインを作成する

プラグインに渡される唯一の引数は nuxtApp です。

export default defineNuxtPlugin(nuxtApp => {
  // nuxtApp でなにかする
})

自動的にヘルパーを提供する

NuxtApp インスタンスにヘルパーを提供したい場合、プラグインからヘルパーをprovide キーで返します。例えば:

export default defineNuxtPlugin(() => {
  return {
    provide: {
      hello: () => 'world'
    }
  }
})

別のファイルで、これを使うことができます:

<template>
  <div>
    {{ $hello() }}
  </div>
</template>

<script setup lang="ts">
// または、このように使うこともできます
const { $hello } = useNuxtApp()
</script>

タイピングプラグイン

プラグインからヘルパーを返すと、自動的に型付けされます。useNuxtApp() の戻り値やテンプレート内で型付けされているのがわかると思います。

高度なプラグイン

高度な使用例では、以下のように注入されたプロパティの型を宣言することができます:

index.d.ts
declare module '#app' {
  interface NuxtApp {
    $hello (msg: string): string
  }
}

declare module '@vue/runtime-core' {
  interface ComponentCustomProperties {
    $hello (msg: string): string
  }
}

export { }

Vue プラグイン

Google Analytics のタグを追加する vue-gtag のように、Vue のプラグインを使用したい場合は、Nuxt のプラグインを使用することができます。

これをもっと簡単にするための Open RFC(Request for Comments、インターネット技術の標準的な仕様を記した文書)があります!nuxt/framework#1175 を参照してください。

まず、必要なプラグインをインストールします。

yarn add --dev vue-gtag-next

それから、プラグインファイル plugins/vue-gtag.client.js を作成します。

import VueGtag from 'vue-gtag-next'

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.use(VueGtag, {
    property: {
      id: 'GA_MEASUREMENT_ID'
    }
  })
})

Public ディレクトリ

public/ ディレクトリは、サーバールートに直接提供され、名前を維持しなければならない(例: robots.txt)、またはおそらく変更されない(例: favicon.ico)公開ファイルを含んでいます。

Server ディレクトリ

Nuxt は、server/ ディレクトリを使用して、アプリケーションのあらゆるバックエンドロジックを作成します。HMR(Hot Module Replacement、画面の再描画なしに JS の変更をブラウザに適用する機能)をサポートし、強力な機能を備えています。

server/ ディレクトリには、プロジェクトで使用する API エンドポイントやサーバーミドルウェアが含まれます。

API ルート

Nuxt は、~/server/api ディレクトリにあるファイルを自動的に読み込んで、API エンドポイントを作成します。

各ファイルは、API リクエストを処理するデフォルトの関数をエクスポートする必要があります。これは、promise または JSON データを直接返すことができます(または、res.end() を使用します)。

Hello World
server/api/hello.ts
export default (res, req) => 'Hello World'

http://localhost:3000/api/hello で結果をご覧ください。

Async 関数
server/api/async.ts
export default async (req, res) => {
  await someAsyncFunction()

  return {
    someData: true
  }
}

: Node.js を使ったスタイル

server/api/node.ts
import type { IncomingMessage, ServerResponse } from 'http'

export default async (req: IncomingMessage, res: ServerResponse) => {
  res.statusCode = 200
  res.end('Works!')
}
リクエストデータへのアクセス
import { useBody, useCookies, useQuery } from 'h3'

export default async (req, res) => {
  const query = useQuery(req)
  const body = await useBody(req) // only for POST request
  const cookies = useCookies(req)
  
  return { query, body, cookies }
}

詳しくは、he methods をご覧ください。

サーバーミドルウェア

Nuxt は、~/server/middleware にあるファイルを自動的に読み込んで、プロジェクト用のサーバーミドルウェアを作成します。

これらのファイルは、独自のルートにマッピングされる API ルート とは異なり、すべてのリクエストで実行されます。これは、通常すべてのレスポンスに共通のヘッダーを追加したり、レスポンスを記録したり、リクエストチェーン(最初の API コールから返されたデータを使って、別のAPIコールを行うこと)であとで使えるように受信リクエストオブジェクトを変更したりするためです。

各ファイルは、リクエストを処理するためのデフォルトの関数をエクスポートする必要があります。

export default async (req, res) => {
  req.someValue = true
}

req/res オブジェクトにはなんの違いもないので、タイプするのは簡単です。

import type { IncomingMessage, ServerResponse } from 'http'

export default async (req: IncomingMessage, res: ServerResponse) => {
  req.someValue = true
}

アプリケーションのより深い部分にリクエストを渡すには、関数の内部で返すことができます。

export default async (req, res) => {
  const isNotHandledByThisMiddleware = req.url.includes('/some-unhandled-url-path/')
  if(isNotHandledByThisMiddleware) {
    return
  }

  // ここに実際のロジックを書く
}
torish14torish14

Gitignore ファイル

.gitignore ファイルは、git が無視すべき、意図的に追跡されていないファイルを指定します。詳しくは、git のドキュメントをご覧ください。

.gitignore ファイルには、少なくとも 以下の項目を記述しておくことを推奨します。

.gitignore
# Nuxt dev/build 出力
.output
.nuxt
# Node 依存関係
node_modules
# システムファイル
*.log

App ファイル

app.vue ファイルは、Nuxt 3 アプリケーションの主要なコンポーネントです。

最小限の使い方

Nuxt 3 では、pages/ ディレクトリはオプションです。存在しない場合、Nuxt は vue-router の依存関係を含めません。これは、ランディングページやルーティングを必要としないアプリケーションで作業するときに便利です。

app.vue
<template>
  <h1>Hello World!</h1>
</template>

pages ディレクトリの使い方

pages/ ディレクトリがある場合、現在のページを表示するには、<NuxtPage> コンポーネントを使用します:

app.vue
<template>
  <div>
    <NuxtLayout>
      <NuxtPage/>
    </NuxtLayout>
  </div>
</template>

あなたが、ページ間のページ周りの構造をカスタマイズする可能性があるなら、layout/ ディレクトリをチェックしてみてください。

Nuxt 設定ファイル

Nuxt は、拡張子が .js, .ts, .mjs のいずれかの nuxt.config ファイル 1 つで簡単に設定することができます。

import { defineNuxtConfig } from 'nuxt3'

export default defineNuxtConfig({
  // 自分の Nuxt の設定
})

すべての異なる設定プロパティを確認する。

alias

  • Type: object
  • Default
{
  "~~": "/<rootDir>",
  "@@": "/<rootDir>",
  "~": "/<rootDir>",
  "@": "/<rootDir>",
  "assets": "/<rootDir>/assets",
  "public": "/<rootDir>/public"
}
  • Version: 2, 3

JavaScript や CSS 内のカスタムディレクトリにアクセスするためのエイリアスを追加定義することで、DX を向上させることができます。

例:

import { resolve } from 'pathe'
export default {
  alias: {
    'images': resolve(__dirname, './assets/images'),
    'style': resolve(__dirname, './assets/style'),
    'data': resolve(__dirname, './assets/other/data')
  }
}

app

Nuxt アプリの構成

baseURL

  • Type: string
  • Default
"/"
  • Version: 2, 3

Nuxt アプリケーションの基本パス。

これは、環境変数 BASE_PATH を設定することで、実行時に設定することができます。

BASE_PATH=/prefix/ node .output/server/index.mjs

buildAssetsDir

buildDir

  • Type: string
  • Default
"/_nuxt/"
  • Version: 2, 3

構築されたサイトアセットのフォルダ名で、baseURL(設定されている場合は cdnURL)に対する相対パスです。これは構築時に設定されるため、実行時にカスタマイズする必要はありません。

cdnURL

  • Default
null
  • Version: 2, 3

公開フォルダを提供する絶対 URL(本番環境のみ)。

これは、環境変数 CDN_URL の設定により、実行時に別の値を設定することができます。

例:

CDN_URL=https://mycdn.org/ node .output/server/index.mjs

buildModules

  • Type: array
  • Version: 2, 3

開発・ビルド時にのみ必要とされるモジュール。

モジュールは Nuxt の拡張機能で、コア機能を拡張し、無限の統合機能を追加することができます。 各モジュールは文字列(パッケージを参照したり、ファイルへのパス)、最初の文字列がモジュール、2 番目のオブジェクトがオプションのタプル、またはインラインモジュール関数のいずれかです。Nuxt はモジュール配列の各アイテムを node require path (node_modules の中で) を使って解決しようとし、~ alias が使われている場合はプロジェクトの srcDir から解決されます。

例:

modules: [
  // Using package name
  '@nuxtjs/axios',
  // Relative to your project srcDir
  '~/modules/awesome.js',
  // Providing options
  ['@nuxtjs/google-analytics', { ua: 'X1234567' }],
  // Inline definition
  function () {}
]

css

  • Type: array
  • Version: 2, 3

グローバルに設定したい(すべてのページに含まれる)CSSファイル/モジュール/ライブラリを定義することができます。

Nuxt は拡張子から自動的にファイルの種類を推測し、適切なプリプロセッサを使用します。ただし、必要なローダーを使用する場合は、インストールする必要があります。

例:

css: [
  // Load a Node.js module directly (here it's a Sass file)
  'bulma',
  // CSS file in the project
  '@/assets/css/main.css',
  // SCSS file in the project
  '@/assets/css/main.scss'
]

dev

dir

extends

extensions

hooks

ignore

ignoreOptions

ignorePrefix

meta

modules

postcss

privateRuntimeConfig

publicRuntimConfig

rootDir

serverMiddleware

srcDir

ssr

vite

watchers

webpack

Nuxt ignore ファイル

.nuxtignore ファイルを使用すると、ビルド時にプロジェクトのルートディレクトリ(rootDir)にある layout、pages、components、composables、middleware のファイルを Nuxt が無視するように設定できます。.nuxtignore ファイルは .gitignore や .eslintignore ファイルと同じ仕様で、各行が無視すべきファイルを示す glob パターン(ワイルドカードなどでファイルパスを表記できるもの)になっています。

注意: nuxt.config ファイルで ignoreOptionsignorePrefixignore を設定することもできます。

.nuxtignore
# layout/foo.vue を無視する
layouts/foo.vue
# 最後に -ignore.vue と名の付く layout ファイルを無視する
layouts/*-ignore.vue

# pages/bar.vue を無視する
pages/bar.vue
# pages/ignore ディレクトリ下のファイルをすべて無視する
pages/ignore/*.vue

# middleware/foo/bar.js を除く middleware/foo ディレクトリ下のファイルをすべて無視する
middleware/foo/*.js
!middleware/foo/bar.js

詳細は、gitignore ドキュメントを確認してください。

Package ファイル

package.json ファイルはアプリケーションのすべての依存関係とスクリプトを含みます(詳細を確認する)。

TypeScript 設定ファイル

Nuxt は、Nuxt プロジェクトで使用している解決済みのエイリアスを含む .nuxt/tsconfig.json ファイル、および他の賢明なデフォルトを自動的に生成します。プロジェクトのルートに以下のような内容の tsconfig.json を作成することで、この恩恵を受けることができます。

必要に応じて、このファイルの内容をカスタマイズすることができます。ただし、パス をカスタマイズする必要がある場合、自動生成されたパスエイリアスを上書きしてしまうことに注意してください。その代わりに、必要なパスエイリアスは nuxt.config 内の alias プロパティに追加して、自動生成される tsconfig に取り込まれるようにすることをお勧めします。

torish14torish14

概要

Nuxt 3 は Nuxt 2 の完全な書き直しであり、また新しい一連の基礎技術に基づくものです。つまり、Nuxt 2 のアプリを Nuxt 3 に移行する際には大きな変化が生じますが、安定版リリースに向けて移行はより簡単になると思われます。

これらの重要な変更点の一部を紹介します:

  1. Composition API とscript setup のデフォルト化を含む Vue 2 から Vue 3 への移行
  2. webpack 4 と Babel から Vite または webpack 5 と esbuild への移行
  3. ランタイムの Nuxt 依存から、nitropack でコンパイルされた最小限のスタンドアローンサーバー(単独で動作しているシステム)に移行しました

構成

nuxt.config

Nuxt アプリケーションの出発点は、nuxt.config ファイルのままです。

📦 Nuxt の設定、`unjs/jiti` と `unjs/c12` を使用して読み込まれます。

移行

  1. 型付き設定スキーマを提供する新しい関数 defineNuxtConfig に移行する必要があります。
Nuxt 2
export default {
  // ...
}

or

Nuxt 3
import { defineNuxtConfig } from 'nuxt3'

export default defineNuxtConfig({
  // ...
})
  1. router.extendRoutes を使用していた場合、新しい pages/extend フックに移行できます:
Nuxt 2
export default {
  router: {
    extendRoutes (routes) {
      //
    }
  }
}

or

Nuxt 3
import { defineNuxtConfig } from 'nuxt3'

export default defineNuxtConfig({
  hooks: {
    'pages:extend' (routes) {
      //
    }
  }
})
ESM 構文

Nuxt 3 は、ESM ネイティブフレームワーク です。unjs/jiti は、nuxt.config フィアルを読み込む際に半互換性を提供しますが、このファイルでは、requiremodule.exports の使用は避けてください。

  1. module.exportsexport default に変更する
  2. const lib = require('lib')import lib from 'lib' に変更する
Async 構成

Nuxt の読み込み動作をより予測しやすくするために、非同期設定構文は非推奨となります。非同期操作には、Nuxt フックを使用することを検討してください。

Dotenv

Nuxt は、.env ファイルを読み込むためのサポートを内蔵しています。nuxt.config から直接インポートするのは避けてください。

モジュール

Nuxt と Nuxt モジュールは、ビルド時のみになりました。

移行

  1. buildModule をすべて module に移動させる
  2. モジュールの Nuxt 3 との互換性を確認する

TypeScript

Nuxt の TypeScript の統合を使用すると、アプリケーションの移行がより簡単になります。これは、アプリケーションを TypeScript で書く必要があるということではなく、Nuxt がエディタに自動的に型ヒントを提供するということです。

Nuxt の TypeScript サポートについては ドキュメント を参照してください。

移行

  1. 以下の内容で tsconfig.json を作成します
{
  "extends": "./.nuxt/tsconfig.json"
}
  1. npx nuxi prepare を実行して、.nuxt/tsconfig.json を生成します
  2. ドキュメント にある指示に従って Volar をインストールします

Vue の変更点

Vue のベストプラクティスとして推奨されているものに多くの変更があり、また Vue 2 と 3 の間には多くの破壊的な変更があります。

Vue 3 への移行ガイド と、特に 破壊的な変更点のリスト を読むことを推奨します。

現在、Nuxt 3 RC で Vue 3 の移行ビルドを使用することはできません。

Vuex

Nuxt は Vuex との統合を提供しなくなりました。代わりに、Vue の公式の推奨は、Nuxt モジュールを介して Nuxt サポートを内蔵している pinia を使用することです。pinia の詳細については、こちらをご覧ください

Vuex を使い続けたい場合は、以下の手順 で手動で Vuex 4 に移行することができます。

自動インポート

Nuxt 3 は、最小限の摩擦で済むアプローチを採用しており、可能な限りコンポーネントやコンポーザブルを自動でインポートすることができます。

自動インポートについてもっと読む

移行

  1. Nuxt 2 で @nuxt/components を使用していた場合、nuxt.configcomponents: true を削除してください。もし、もっと複雑な設定をしていたのであれば、コンポーネントのオプションが多少変更されていることに注意してください。詳しくは、components のドキュメント をご覧ください。

メタタグ

Nuxt 3 では、メタタグを管理する方法がいくつか用意されています。

  1. nuxt.config を通じて
  2. useMeta コンポーザブルを通じて
  3. グローバルメタコンポーネントを通じて

titlebasescriptstyemetalinkhtmlAttrsbodyAttrs をカスタマイズすることが可能です。

メタタグについてもっと読む。

移行

  1. nuxt.config で、headmeta に名前変更してください。この共有された meta の設定を、代わりに app.vue に移行することを検討してください。(オブジェクトは、重複排除のための hid キーを持たなくなったことに注意してください)
  2. コンポーネントでは、head オプションの名前を meta に変更します。コンポーネントの状態にアクセスする必要がある場合は、useMeta を使用するように移行する必要があります。また、内蔵のメタコンポーネントの使用も検討してください。

例: useMeta

Nuxt 2
<script>
export default {
  data: () => ({
    title: 'My App',
    description: 'My App Description'
  })
  head () {
    return {
      title: this.title,
      meta: [{
        hid: 'description',
        name: 'description',
        content: this.description
      }]
    }
  }
}
</script>

or

Nuxt 3
<script setup>
const title = ref('My App')
const description = ref('My App Description')

// This will be reactive even you change title/description above
useMeta({
  title,
  meta: [{
    name: 'description',
    content: description
  }]
})
</script>

例: 内蔵のメタコンポーネント

Nuxt 3 には、同じタスクを実行するために使用できるメタコンポーネントも用意されています。これらのコンポーネントは HTML タグに似ていますが、Nuxt によって提供され、同様の機能を備えています。

Nuxt 2
<script>
export default {
  head () {
    return {
      title: 'My App',
      meta: [{
        hid: 'description',
        name: 'description',
        content: 'My App Description'
      }]
    }
  }
}
</script>

or

Nuxt 3
<template>
  <div>
    <Head>
      <Title>My App</Title>
      <Meta name="description" content="My app description"/>
    </Head>
    <!-- -->
  </div>
</template>  

プラグインとミドルウェア

プラグイン

プラグインは異なるフォーマットを持ち、1 つの引数(nuxtApp) を取るようになりました。詳しくは、ドキュメント をご覧ください。

Nuxt 2
export default (ctx, inject) => {
  inject('injected', () => 'my injected function')
})

or

Nuxt 3
export default defineNuxtPlugin(nuxtApp => {
  // `nuxtApp.$injected` が使用可能になりました
  nuxtApp.provide('injected', () => 'my injected function')

  // 代わりにこのフォーマットは、自動型サポート付きです
  return {
    provide: {
      injected: () => 'my injected function'
    }
  }
})

移行

  1. defineNuxtPlugin ヘルパー関数を使用するようにプラグインを移行します
  2. plugins フォルダにある nuxt.cofig プラグイン配列のエントリーをすべて削除してください。このディレクトリのトップレベルにあるすべてのファイル(および任意のサブディレクトリにあるインデックスファイル)は自動的に登録されます。モードをクライアントまたはサーバーに設定する代わりに、ファイル名でそれを示すことができます。例えば、~/plugins/my-plugin.client.ts はクライアントサイドでのみ読み込まれます。

ルートミドルウェア

ルートミドルウェアは、別の形式を持ちます。

Nuxt 2
export default function ({ store, redirect }) {
  // ユーザーの認証がされていない場合
  if (!store.state.authenticated) {
    return redirect('/login')
  }
}

or

Nuxt 3
export default defineNuxtRouteMiddleware((to, from) => {
  const auth = useState('auth')
  if (!auth.value.authenticated) {
    return navigateTo('/login')
  }
})

Nuxt 2 と同様、~/middleware フォルダに配置されたルートミドルウェアが自動的に登録されます。そのあと、コンポーネントで名前を付けて指定することができます。ただし、これはコンポーネントのオプションとしてではなく、definePageMeta で行われます。

navigateTo は、いくつかのルートヘルパー関数のひとつで、詳細は ルートミドルウェアに関するドキュメント をご覧ください。

移行

  1. defineNuxtRouteMiddleware ヘルパー関数を使用するようにルートミドルウェアを移行してください。
  2. グローバルミドルウェア(nuxt.config など)は、例えば ~/middleware/auth.global.ts のように .global という拡張子をつけて ~/middleware フォルダに配置することができます。
torish14torish14

pages とレイアウト

app.vue

Nuxt 3 は、~/app.vue を介してアプリへのセントラルエントリーポイントを提供します。もし、ソースディレクトリに app.vue ファイルがなければ、Nuxt は独自のデフォルトバージョンを使用します。

このファイルは、アプリの起動時に一度だけ実行する必要のあるカスタムコードや、アプリの各ページに存在するコンポーネントを置くのに最適な場所です。例えば、レイアウトが 1 つしかない場合は、代わりに app.vue に移動させることができます。

app.vue についてもっと読む

移行

  1. app.vue ファイルを作成し、アプリのトップレベルで一度だけ実行する必要があるロジックを含めることを検討してください。ここで、その例を確認することができます

レイアウト

アプリで複数のページにレイアウトを使用している場合は、わずかな変更で済みます。

Nuxt 2 では、<Nuxt> コンポーネントはレイアウト内で現在のページをレンダリングするために使用されます。Nuxt 3 では、レイアウトは代わりにスロットを使用するため、そのコンポーネントを <slot /> に置き換える必要があります。これにより、名前付きスロットやスコープ付きスロットを使った高度なユースケースも可能になります。レイアウトについてもっと読む

また、definePageMeta コンパイラマクロを使用して、ページで使用されるレイアウトを定義する方法を変更する必要があります。レイアウトはケバブケースになります。
そのため、layouts/customLayout.vue は、ページ内で参照されると custom-layout になります。

移行

  1. <Nuxt /><slot /> に置き換える。
  2. definePageMeta を使って、ページで使用するレイアウトを選択します。
  3. ~/layouts/_error.vue~/error.vue に移動します。エラー処理のドキュメント を参照してください。

例: ~/layouts/custom.vue

layouts/cusom.vue
  <template>
    <div id="app-layout">
      <main>
-       <Nuxt />
+       <slot />
      </main>
    </div>
  </template>
pages/index.vue
  <script>
+ definePageMeta({ layout: 'custom' })
  export default {
-   layout: 'custom'
  }
  </script>

ページ

Nuxt 3 には、ソースディレクトリに pages/ ディレクトリが存在することをトリガーとして、vue-router の統合がオプションで用意されています。もし、1 ページしかないのであれば、app.vue に移動してビルドを軽くすることを検討してもよいでしょう。

動的なルート

Nuxt 3 の動的なルートの定義形式は Nuxt 2 と若干異なるため、pages/ 内のファイル名を一部変更する必要があるかもしれません。

  1. 以前は動的経路のパラメータを定義するのに _id を使用していましたが、現在は [id] を使用します。
  2. 以前は _.vue を使ってキャッチオールルートを定義していましたが、現在は [...slug].vue を使っています。

ネストされたルート

Nuxt 2 では、<Nuxt><NuxtChild> を使用して、(親と子のコンポーネントを持つ)ネストされたルートを定義していました。Nuxt 3 では、これらは単一の <NuxtPage> コンポーネントに置き換えられました。

Page key と keep-alive props

<Nuxt> にカスタム ページ キーまたは keep-alive props を渡していた場合、definePageMeta を使用してこれらのオプションを設定できるようになりました。

詳しくは、Nuxt コンポーネントフックの移行 を参照してください。

ページとレイアウトの遷移

ページやレイアウトの遷移をコンポーネントオプションで直接定義していた場合、definePageMeta を使用して遷移を設定する必要があります。

pages/ の詳細についてはこちらをご覧ください。

移行

  1. 動的なパラメータを持つページの名前を、新しい形式に合うように変更します。
  2. <Nuxt><NuxtChild><NuxtPage> に更新してください。
  3. Composition API を使用している場合は、this.$routethis.$routeruseRouteuseRouter というコンポーザブルを使用するように移行してください。

例: 動的なルート

Nuxt 2
- URL: /users
- Page: /pages/users/index.vue

- URL: /users/some-user-name
- Page: /pages/users/_user.vue
- Usage: params.user

- URL: /users/some-user-name/edit
- Page: /pages/users/_user/edit.vue
- Usage: params.user

- URL: /users/anything-else
- Page: /pages/users/_.vue
- Usage: params.pathMatch
Nuxt 3
- URL: /users
- Page: /pages/users/index.vue

- URL: /users/some-user-name
- Page: /pages/users/[user].vue
- Usage: params.user

- URL: /users/some-user-name/edit
- Page: /pages/users/[user]/edit.vue
- Usage: params.user

- URL: /users/anything-else
- Page: /pages/users/[...slug].vue
- Usage: params.slug

例: ネストされたルートと definePageMeta

Nuxt 2
<template>
  <div>
    <NuxtChild keep-alive :keep-alive-props="{ exclude: ['modal'] }" :nuxt-child-key="$route.slug" />
  </div>
</template>

<script>
export default {
  transition: 'page' // or { name: 'page' }
}
</script>
Nuxt 3
<template>
  <div>
    <NuxtPage />
  </div>
</template>

<script setup>
// このコンパイラマクロは、<script> と <script setup> のどちらでも動作します
definePageMeta({
  // 文字列または計算されたプロパティを渡すこともできます
  key: route => route.slug,
  transition: {
    name: 'page',
  },
  keepAlive: {
    exclude: ['modal']
  },
})
</script>

ほとんどの構文と機能は、グローバルな NuxtLink コンポーネントと同じです。ショートカットの <NLink> 形式を使用していた場合は、<NuxtLink> を使用するように更新する必要があります。

<NuxtLink> は、外部リンクを含むすべてのリンクのドロップイン置き換えになりました。このコンポーネントの詳細と、これを拡張して独自のリンク・コンポーネントを提供する方法については、ドキュメント を参照してください。

プログラマティックなナビゲーション

Nuxt 2 から Nuxt 3 に移行する場合、ユーザーをプログラム的にナビゲートする方法を更新する必要があります。Nuxt 2 では、this.$router を使用して基礎となる Vue Router にアクセスすることができました。Nuxt 3 では、ルートとパラメータを Vue Router に渡すことができる navigateTo() ユーティリティメソッドを使用することができます。

注意: navigateTo で常に 待ち受ける か、関数から返すことでその結果を連鎖させるようにしてください。

Nuxt 2
<script>
export default {
  methods: {
    navigate(){
      this.$router.push({
        path: '/search',
        query: {
          name: 'first name',
          type: '1'
        }
      })
    }
  }
}
</script>
Nuxt 3
<script setup>
const router = useRouter();

function navigate(){
  return navigateTo({
    path: '/search',
    query: {
      name: 'first name',
      type: '1'
    }
  })
}
</script>

コンポーネントオプション

asyncDatafetch コンポーネントのオプション

Nuxt 3 は、API からデータをフェッチする ための新しいオプションを提供します。

アイソモーフィックフェッチ

Nuxt 2 では、@nuxtjs/axios または @nuxt/http を使用してデータをフェッチするか、ポリフィルドのグローバルフェッチのみを使用することができます。

Nuxt 3 では、Fetch API または unjs/ohmyfetch を使用する $fetch メソッドと同じ API を持つグローバルに利用可能な フェッチ メソッドを使用することができます。これには以下のような利点があります:

  1. サーバー上で実行されている場合は、直接 API を呼び出す ことを「スマートに」処理し、クライアント上で実行されている場合は、あなたの API をクライアントサイドで呼び出すことを処理することができます。(サードパーティの API を呼び出すことも可能です)。
  2. さらに、レスポンスの自動解析やデータの文字列化など、便利な機能も備えています。

直接 API を呼び出したりデータを取得したりする 方法について詳しく知れます。

コンポーザブルの使用

Nuxt 3 には、データを取得するための新しい複合変数、useAsyncDatauseFetch があります。これらにはそれぞれ「遅延」バージョン(useLazyAsyncDatauseLazyFetch)があり、クライアントサイドのナビゲーションをブロックしないようにすることができます。

Nuxt 2 では、以下のような構文でコンポーネントのデータをフェッチします:

export default {
  async asyncData({ params, $http }) {
    const post = await $http.$get(`https://api.nuxtjs.dev/posts/${params.id}`)
    return { post }
  },
  // or alternatively
  fetch () {
    this.post = await $http.$get(`https://api.nuxtjs.dev/posts/${params.id}`)
  }
}

メソッドやテンプレートの中で、コンポーネントが提供する他のデータを使用するのと同じように、post 変数を使用することができます。

Nuxt 3 では、setup() メソッドまたは <script setup> タグで composables を使用してこのデータフェッチを行うことができます:

<script setup>
  // Define params wherever, through `defineProps()`, `useRoute()`, etc.
  const { data: post, refresh } = await useAsyncData('post', () => $fetch(`https://api.nuxtjs.dev/posts/${params.id}`) )
  // Or instead - useFetch is a convenience wrapper around useAsyncData when you're just performing a simple fetch
  const { data: post, refresh } = await useFetch(`https://api.nuxtjs.dev/posts/${paramsÌ.id}`)
</script>

Nuxt 3 テンプレート内で post を使用したり、refresh を呼び出してデータを更新することができるようになりました。

マイグレーション

  1. page/component 内の asyncData フックを useAsyncData または useFetch に置き換えてください。
  2. コンポーネント内の fetch フックを useAsyncData または useFetch に置き換えてください。

meta tag migration を参照してください。

key

definePageMeta コンパイラマクロ内でキーを定義できるようになりました。

pages/index.vue
- <script>
- export default {
-   key: 'index'
-   // or a method
-   // key: route => route.fullPath
- }
+ <script setup>
+ definePageMeta({
+   key: 'index'
+   // or a method
+   // key: route => route.fullPath
+ })
  </script>

移行

  1. コンポーネントオプションから definePageMetakey を移行する。

layout

レイアウトの移行 を参照してください。

loading

この機能は Nuxt 3 ではまだサポートされていません。

middleware

ミドルウェアの移行 を参照してください。

scrollToTop

この機能は Nuxt 3 ではまだサポートされていません。vue-router のデフォルトのスクロール動作を上書きしたい場合は、 ~/app/router.options.ts で上書きできます(詳しくは ドキュメント をご覧ください)。

transition

レイアウトの移行 を参照してください。

validate

Nuxt 3 では validate フックがなくなりました。代わりに、カスタムミドルウェア関数を作成するか、ページの設定関数で直接エラーを投げることができる。

pages/usrs/[id].vue
- <script>
- export default {
-   async validate({ params, query, store }) {
-     return true // if valid
-   }
- }
+ <script setup>
+ definePageMeta({
+   middleware: [
+     async function (to, from) {
+       const nuxtApp = useNuxtApp()
+       if (!valid) {
+         return abortNavigation('Page not found')
+       }
+     }
+   ]
+ })
  </script>

watchQuery

これは Nuxt 3 ではサポートされていません。代わりに、データのリフェッチをトリガするために直接ウォッチャーを使用することができます。

pages/users/[id].vue
<script setup>
const route = useRoute()
const { data, refresh } = await useFetch('/api/user')
watch(() => route.query, () => refresh())
</script>

ランタイム設定

Nuxt 3 アプリ内で環境変数を参照したい場合は、ランタイム設定を使用する必要があります。

コンポーネント内でこれらの変数を参照する場合、セットアップメソッド(または Nuxt プラグイン)内で useRuntimeConfig コンポーザブルを使用する必要があります。アプリの server/ 部分では、インポートなしで useRuntimeConfig を使用することができます。

ランタイム設定についてもっと読む

移行

  1. アプリで使用する環境変数を nuxt.config ファイルの runtimeConfig プロパティに追加します。
  2. アプリの Vue 部分全体で、process.envuseRuntimeConfig に移行します。

nuxt.config.ts
import { defineNuxtConfig } from 'nuxt'

export default defineNuxtConfig({
  runtimeConfig: {
    secretKey: '', // variable that can only be accessed on the server side
    public: {
      BASE_URL: process.env.BASE_URL || 'https://nuxtjs.org' // variable that can also be accessed on the client side
    }
  },
})
pages/index.vue
<script setup>
  const config = useRuntimeConfig().public
  // instead of process.env.BASE_URL you will now access config.BASE_URL
</script>
server/api/hello.ts
const config = useRuntimeConfig().public

export default (req, res) => {
  // you can now access config.BASE_URL
  return {
    baseURL: config.BASE_URL
  }
}

ビルドツール

デフォルトでは、以下のビルドツールを使用します。

  • Vite または webpack
  • Rollup
  • PostCSS
  • esbuild

このため、nuxt.config にある以前のビルド設定のほとんどは、カスタム babel 設定を含めて無視されるようになりました。

もし Nuxt のビルドツールのどれかを設定する必要がある場合は、新しいトップレベルの vite, webpack, postcss キーを使って、nuxt.config で設定することができます。

さらに、Nuxt は TypeScript をサポートしています。詳細はこちら

ステップ

  1. nuxt/typescript-build@nuxt/typescript-runtime を依存関係やモジュールから削除します。
  2. babel の未使用の依存関係をプロジェクトから削除します。
  3. 明示的な core-js の依存関係を削除します。
  4. requireimport に移行する。

サーバー

ビルドされた Nuxt 3 アプリケーションでは、ランタイムの Nuxt 依存はありません。これは、あなたのサイトが高いパフォーマンスと超スリムになることを意味します。しかし、それは同時にランタイム Nuxt サーバーフックにフックすることができなくなることも意味します。

Nitro サーバーエンジンの詳細については、こちらをご覧ください

ステップ

  1. nuxt.config にある render キーを削除します。
  2. server/api~/server/middleware にあるファイルは自動的に登録されるので、serverMiddleware 配列から削除してください。
  3. serverMiddleware 配列の他のアイテムは、インライン関数ではなく、ファイルや npm パッケージを直接指定するように更新してください。
  4. server:vue-renderer: などのサーバーフックを追加している場合は、これらを削除して、ランタイムフックやプラグインの nitropack サポートを待つ必要があります。

モジュール

モジュールの互換性

Nuxt 3 は @nuxt/kit 自働ラッパーを使用した Nuxt 2 モジュールの基本的な後方互換性レイヤーを備えています。しかし、通常、モジュールを Nuxt 3 と互換性を持たせるための手順があり、クロスバージョン互換のために Nuxt Bridge の使用が必要な場合もあります。

そこで、@nuxt/kit を用いて Nuxt 3 対応モジュールを作成するための 専用ガイド を作成しました。現在のところ、これに従ってモジュールを書き換えるのが最適な移行方法です。このガイドの残りの部分は、モジュールの全面的な書き換えを避け、Nuxt 3 と互換性のあるモジュールを作成するための準備手順を含んでいます。

プラグインの互換性

Nuxt 3 のプラグインは Nuxt 2 と完全な後方互換性がありません。

Vue との互換性

Composition API を使用するプラグインやコンポーネントは、Vue 2 または Vue 3 を排他的にサポートする必要があります。

vue-demi を使用することで、Nuxt 2 と 3 の両方と互換性があるはずです。

モジュールの移行

Nuxt 3 ユーザーがあなたのモジュールを追加すると、@nuxt/kit から互換性のあるモジュールコンテナレイヤーが 自動的に注入される ので、あなたのコードが以下のガイドラインに従っている限り、そのまま動作し続けるはずです。

nuxt/bridge でテストする

nuxt/bridge への移行は、Nuxt 3 をサポートするための最初の、そして最も重要なステップです。

モジュールにフィクスチャやサンプルがある場合は、@nuxt/bridge パッケージをその設定に追加してください ( を参照してください)。

CommonJS から ESM への移行

Nuxt 3 は TypeScript と ECMAScript Modules をネイティブにサポートしています。詳細とアップグレードについては、Native ES Modules を参照してください。

プラグインがデフォルトでエクスポートするようにする

Vue のグローバルプラグインなど、export default を持たない Nuxt プラグインを注入する場合は、export default () => { } を末尾に追加することを確認してください。

Before
// ~/plugins/vuelidate.js
import Vue from 'vue'
import Vuelidate from 'vuelidate'

Vue.use(Vuelidate)
After
// ~/plugins/vuelidate.js
import Vue from 'vue'
import Vuelidate from 'vuelidate'

Vue.use(Vuelidate)

export default () => { }

ランタイムモジュールの回避

Nuxt 3 では、Nuxt はビルド時のみの依存関係になりました。これは、モジュールがNuxt ランタイムにフックすることを試みてはならないことを意味します。

modules の代わりに)buildModules に追加するだけでも、あなたのモジュールは動作するはずです。例えば:

  • Nuxt モジュール内で process.env を更新して、Nuxt プラグインで読み込むことは避けてください。代わりに runtimeConfig を使用してください。
  • (*) vue-renderer:* のようなランタイムフックに依存した制作は避ける。
  • (*) serverMiddleware をモジュール内部で import して追加することは避けてください。代わりに、ファイルパスを参照して追加し、モジュールのコンテキストに依存しないようにします。

(*) ただし、nuxt dev の目的のみで、if (nuxt.options.dev) { } でガードされている場合は別です。

TypeScript を使用する(オプション)

必須ではありませんが、Nuxt のエコシステムのほとんどが TypeScript を使用するように移行しているので、移行を検討することを強くお勧めします。

torish14torish14

Node.js サーバー

Node.js のサーバープリセットは、Nitro を使用して、任意の Node ホスティングにデプロイすることを発見してください。

  • ✅ 何も指定されていない場合、または自動検出された場合の デフォルトの出力形式
  • ✅ 最適なコールドスタートのタイミングでリクエストをレンダリングするために必要なチャンクのみをロードします
  • ✅ Nuxt アプリを任意の Node.js ホスティングにデプロイするのに便利です

エントリーポイント

Node サーバープリセットでnuxt buildを実行すると、すぐに実行できるNodeサーバを起動するエントリポイントが生成されます。

node .output/server/index.mjs

$ node .output/server/index.mjs
Listening on http://localhost:3000

実行時にデフォルトを設定する

このプリセットは、以下の実行時環境変数を尊重します。

  • NITRO_PORT または PORT (デフォルトは 3000)
  • NITRO_HOST または HOST (デフォルトは '0.0.0.0')
  • NITRO_SSL_CERTNITRO_SSL_KEY - 両方とも存在する場合、サーバーを HTTPS モードで起動します。Nitro サーバは、nginx や Cloudflare などの SSL を終了するリバースプロキシで動作させる必要があります。

PM2 を使う

pm2 を使用するには、ecosystem.config.js を使用します:

ecosystem.config.js
module.exports = {
  apps: [
    {
      name: 'NuxtAppName',
      exec_mode: 'cluster',
      instances: 'max',
      script: './.output/server/index.mjs'
    }
  ]
}

詳細

Static ホスティング

Nuxt アプリケーションを任意の静的ホスティングサービスにデプロイするには、2 つの方法があります。

  • 静的サイト生成(Static Site Generation: SSG)は、ビルド時にアプリケーションのすべてのルートを事前にレンダリングします。また、すべてのページに対して、Nuxt はクローラーを使用して対応する HTML ファイルを生成します。
  • ssr: false を使用すると、純粋なクライアントサイド出力が生成されます。

プリレンダリング

nuxi generate コマンドを使用してアプリケーションをビルドします。HTML ファイルは .output/public ディレクトリに生成されます。

npx nuxi generate

クライアントサイドのみのレンダリング

ルートをプリレンダリングしたくない場合、静的ホスティングを使用する別の方法は、nuxt.config ファイルで ssr プロパティを false に設定することです。そうすると、nuxi build コマンドは、古典的なクライアントサイド Vue.js アプリケーションのように index.html エントリーポイントを出力します。

nuxt.config.ts|js
defineNuxtConfig({
  ssr: false
})

応用

Nitro がビルド中に取得しプリレンダリングするルートを手動で指定することができます。

nuxt.config.ts|js
defineNuxtConfig({
  nitro: {
    prerender: {
      routes: ['/user/1', '/user/2']
    }
  }
})

デプロイメントプリセット

Node.js サーバーと静的ホスティングサービスに加えて、Nuxt 3 プロジェクトは、十分にテストされたいくつかのプリセットと最小限の設定でデプロイすることが可能です。

Nuxt の設定 を使用して、使用するプリセットを明示的に設定することができます:

nuxt.config.js|ts
export default {
  nitro: {
    preset: 'node-server'
  }
}

または、nuxt build を実行する際に、直接 NITRO_PRESET 環境変数を使用します:

NITRO_PRESET=node-server nuxt build

🔎 可能なすべてのデプロイメントプリセットとプロバイダについて、Nitro デプロイメント を確認します。

サポートしているホスティングプロバイダ

Nuxt 3 は、最小限の設定で複数のクラウドプロバイダーに導入することができます:

  • ☁️ AWS
  • 🅰️ Azure
  • 🌩️ CloudFlare
  • ☁️ Degital Ocean
  • ⚡️ Firebase
  • ☁️ heroku
  • ☁️ layer0
  • 💠 Netlify
  • ☁️ Render
  • ☁️ Stormkit
  • 🔺 Vercel
torish14torish14

Nuxt はどのように動いているのか?

Nuxt は、Web アプリケーションを構築するための最小限の、しかし高度にカスタマイズ可能なフレームワークです。このガイドは、Nuxt の内部をよりよく理解し、Nuxt の上に新しいソリューションやモジュール統合を開発するのに役立ちます。

Nuxt のインターフェース

nuxi dev で開発モード、または nuxi build で本番アプリケーションをビルドして nuxt を起動すると、内部で nuxt と呼ばれる共通のコンテキストが作成されます。これは、nuxt.config ファイルでマージされた正規化されたオプション、いくつかの内部状態、そして unjs/hookable で駆動する強力なフッキングシステム を持ち、異なるコンポーネントが互いに通信することを可能にします。これは Builder Core と考えることができます。

このコンテキストは、nuxt/kit コンポーザブルでグローバルに利用可能です。そのため、1 プロセスあたり 1 つの Nuxt のインスタンスしか実行できません。

Nuxt のインターフェースを拡張し、ビルドプロセスの異なるステージにフックするには、Nuxt Modules を使用します。

詳しくは ソースコード を見てください。

framework/packages/nuxt/src/core/nuxt.ts
import { resolve } from 'pathe'
import { createHooks } from 'hookable'
import type { Nuxt, NuxtOptions, NuxtConfig, ModuleContainer, NuxtHooks } from '@nuxt/schema'
import { loadNuxtConfig, LoadNuxtOptions, nuxtCtx, installModule, addComponent, addVitePlugin, addWebpackPlugin, tryResolveModule } from '@nuxt/kit'
// Temporary until finding better placement
/* eslint-disable import/no-restricted-paths */
import pagesModule from '../pages/module'
import metaModule from '../head/module'
import componentsModule from '../components/module'
import autoImportsModule from '../auto-imports/module'
/* eslint-enable */
import { distDir, pkgDir } from '../dirs'
import { version } from '../../package.json'
import { ImportProtectionPlugin, vueAppPatterns } from './plugins/import-protection'
import { UnctxTransformPlugin } from './plugins/unctx'
import { addModuleTranspiles } from './modules'
import { initNitro } from './nitro'

export function createNuxt (options: NuxtOptions): Nuxt {
  const hooks = createHooks<NuxtHooks>()

  const nuxt: Nuxt = {
    _version: version,
    options,
    hooks,
    callHook: hooks.callHook,
    addHooks: hooks.addHooks,
    hook: hooks.hook,
    ready: () => initNuxt(nuxt),
    close: () => Promise.resolve(hooks.callHook('close', nuxt)),
    vfs: {}
  }

  return nuxt
}

async function initNuxt (nuxt: Nuxt) {
  // Register user hooks
  nuxt.hooks.addHooks(nuxt.options.hooks)

  // Set nuxt instance for useNuxt
  nuxtCtx.set(nuxt)
  nuxt.hook('close', () => nuxtCtx.unset())

  // Add nuxt types
  nuxt.hook('prepare:types', (opts) => {
    opts.references.push({ types: 'nuxt' })
    opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/plugins.d.ts') })
    // Add vue shim
    if (nuxt.options.typescript.shim) {
      opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/vue-shim.d.ts') })
    }
    // Add module augmentations directly to NuxtConfig
    opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/schema.d.ts') })
  })

  // Add import protection
  const config = {
    rootDir: nuxt.options.rootDir,
    patterns: vueAppPatterns(nuxt)
  }
  addVitePlugin(ImportProtectionPlugin.vite(config))
  addWebpackPlugin(ImportProtectionPlugin.webpack(config))

  // Add unctx transform
  addVitePlugin(UnctxTransformPlugin(nuxt).vite({ sourcemap: nuxt.options.sourcemap }))
  addWebpackPlugin(UnctxTransformPlugin(nuxt).webpack({ sourcemap: nuxt.options.sourcemap }))

  // Init user modules
  await nuxt.callHook('modules:before', { nuxt } as ModuleContainer)
  const modulesToInstall = [
    ...nuxt.options.buildModules,
    ...nuxt.options.modules,
    ...nuxt.options._modules
  ]

  // Add <NuxtWelcome>
  addComponent({
    name: 'NuxtWelcome',
    filePath: tryResolveModule('@nuxt/ui-templates/templates/welcome.vue')
  })

  addComponent({
    name: 'NuxtLayout',
    filePath: resolve(nuxt.options.appDir, 'components/layout')
  })

  // Add <NuxtErrorBoundary>
  addComponent({
    name: 'NuxtErrorBoundary',
    filePath: resolve(nuxt.options.appDir, 'components/nuxt-error-boundary')
  })

  // Add <ClientOnly>
  addComponent({
    name: 'ClientOnly',
    filePath: resolve(nuxt.options.appDir, 'components/client-only')
  })

  // Add <ServerPlaceholder>
  addComponent({
    name: 'ServerPlaceholder',
    filePath: resolve(nuxt.options.appDir, 'components/server-placeholder')
  })

  // Add <NuxtLink>
  addComponent({
    name: 'NuxtLink',
    filePath: resolve(nuxt.options.appDir, 'components/nuxt-link')
  })

  for (const m of modulesToInstall) {
    if (Array.isArray(m)) {
      await installModule(m[0], m[1])
    } else {
      await installModule(m, {})
    }
  }

  await nuxt.callHook('modules:done', { nuxt } as ModuleContainer)

  await addModuleTranspiles()

  // Init nitro
  await initNitro(nuxt)

  await nuxt.callHook('ready', nuxt)
}

export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
  const options = await loadNuxtConfig(opts)

  // Temporary until finding better placement for each
  options.appDir = options.alias['#app'] = resolve(distDir, 'app')
  options._majorVersion = 3
  options._modules.push(pagesModule, metaModule, componentsModule, autoImportsModule)
  options.modulesDir.push(resolve(pkgDir, 'node_modules'))
  options.build.transpile.push('@nuxt/ui-templates')
  options.alias['vue-demi'] = resolve(options.appDir, 'compat/vue-demi')
  options.alias['@vue/composition-api'] = resolve(options.appDir, 'compat/capi')
  if (options.telemetry !== false && !process.env.NUXT_TELEMETRY_DISABLED) {
    options._modules.push('@nuxt/telemetry')
  }

  const nuxt = createNuxt(options)

  if (opts.ready !== false) {
    await nuxt.ready()
  }

  return nuxt
}

export function defineNuxtConfig (config: NuxtConfig): NuxtConfig {
  return config
}

// For a convenience import together with `defineNuxtConfig`
export type { NuxtConfig }

NuxtApp インターフェース

ブラウザやサーバでページをレンダリングするとき、nuxtApp と呼ばれる共有コンテキストが作成されます。このコンテキストは、vue インスタンス、ランタイムフック、そして ssrContext やハイドレーションのためのペイロードなどの内部ステートを保持します。ランタイムコア と考えることができます。

このコンテキストは、nuxt プラグイン内の useNuxtApp()<script setup>、vue のコンポーザブルを使ってアクセスすることができます。ユーザー間でコンテキストを共有しないように、ブラウザではグローバルな利用が可能ですが、サーバーでは利用できません。

nuxtApp インターフェースを拡張し、異なるステージやアクセスコンテキストにフックするには、Nuxt Plugins を使うことができます。

このインターフェイスの詳細については Nuxt App を参照してください。

nuxtApp は以下のプロパティを持ちます。

注意: これは内部的なインターフェイスであり、安定版リリースまでいくつかのプロパティが変更される可能性があります。

const nuxtApp = {
  vueApp, // the global Vue application: https://vuejs.org/api/application.html#application-api

  // These let you call and add runtime NuxtApp hooks
  // https://github.com/nuxt/framework/blob/main/packages/nuxt/src/app/nuxt.ts#L18
  hooks,
  hook,
  callHook,

  // Only accessible on server-side
  ssrContext: {
    url,
    req,
    res,
    runtimeConfig,
    noSSR,
  },

  // This will be stringified and passed from server to client
  payload: {
    serverRendered: true,
    data: {},
    state: {}
  }

  provide: (name: string, value: any) => void
}

詳しくは、ソースコードをご覧ください。

ランタイムコンテキストとビルドコンテキスト

Nuxt は Node.js を使ってプロジェクトをビルドとバンドルをしますが、ランタイム側のコンテキストも持っています。

どちらの領域も拡張可能ですが、そのランタイムコンテキストはビルド時とは分離されています。そのため、状態やコード、ランタイム設定以外のコンテキストを共有することは想定されていません。

nuxt.configNuxt Modules はビルドコンテキストを拡張するために使用でき、Nuxt Plugins はランタイムを拡張するために使用できます。

本番用のアプリケーションをビルドするとき、nuxi buildnuxt.configNuxt modules とは無関係に .output ディレクトリにスタンドアローンなビルドを生成します。

ライフサイクルフック

Nuxt は、unjs/hookable で提供されるフックを使って、ほとんどすべての面を拡張できる強力なフッキングシステムを提供します。

Nuxt Hooks(ビルド時)

これらのフックは、Nuxt モジュール とビルドコンテキストで利用可能です。

nuxt.config での使い方

nuxt.config
export default defineNuxtConfig({
  hooks: {
    'close': () => { }
  }
})

Nuxt モジュールでの使い方

nuxt.config
import { defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    nuxt.hook('close', async () => { })
  })
})

App Hooks (ランタイム)

アプリフックは、主に Nuxt Plugin がレンダリングのライフサイクルにフックするために使用されますが、Vue のコンポーザブルでも使用できます。

Plugins での使い方

plugins/test.ts
export default defineNuxtPlugin((nuxtApp) => {
    nuxtApp.hook('page:start', () => {
        /* your code goes here */
     })
})

モジュール開発者ガイド

Nuxt は、Web アプリケーションを開発するための統合とベストプラクティスのプリセットを使用して、ゼロコンフィグ体験を提供します。強力な設定とフックシステムにより、Nuxt Framework のほぼすべての側面をカスタマイズすることが可能で、カスタマイズに関しては無限に可能な統合を追加することができます。Nuxt の仕組みについては、Nuxt internals のセクションで詳しく説明されています。

Nuxt は、Nuxt Modules と呼ばれる強力な API を公開しています。Nuxt モジュールは、nuxi dev を使用して開発モードで nuxt を起動するとき、または nuxi build を使用して本番用にプロジェクトを構築するときに順次実行されるシンプルな非同期関数です。Nuxt Modules を使うことで、Nuxt プロジェクト自体に不必要な定型文を追加することなく、カスタムソリューションをカプセル化し、適切にテストし、npm パッケージとして共有することができます。Nuxt モジュールは、Nuxt ビルダーのライフサイクルイベントにフックし、ランタイムアプリのテンプレートを提供し、設定を更新し、またはニーズに基づいた他のカスタムアクションを実行することができます。

クイックスタート

せっかちな人のために、module-buildermodule starter template を使ってすぐに始めることができます:

npx nuxi init -t module my-module

スターターテンプレートとモジュールスターターは、Nuxt モジュールを作成する際の標準的なパスです。

次のステップ:

  1. お好みの IDE で my-module を開いてください (Visual Studio Code をお勧めします)
  2. パッケージマネージャを使用して依存関係をインストールします(Yarn を推奨します)。
  3. npm run dev:prepare を使ってローカルファイルが生成されていることを確認します。
  4. npm run dev を使ってプレイグラウンドを開始します。
  5. Nuxtモジュールの詳細については、このドキュメントに従ってください。

モジュールの構造

Nuxt モジュールはインラインのユーザーオプションと nuxt の引数を受け付けるシンプルな関数です。

残りのロジックをどのように処理するかはモジュール作者であるあなた次第です。

Nuxt 3 から、モジュールはすべての Nuxt Kit ユーティリティの恩恵を受けることができるようになりました。

modules/example.ts
// modules/module.mjs
export default async (inlineOptions, nuxt) => {
  // ここに好きなものは何でもできます..
  console.log(inlineOptions.token) // `123`
  console.log(nuxt.options.dev) // `true` or `false`
  nuxt.hook('ready', async nuxt => {
    console.log('Nuxt is ready')
  })
}
nuxt.config
export default defineNuxtConfig({
  modules: [
    // パッケージ名を使用する(推奨される使い方)
    '@nuxtjs/example',

    // ローカルのモジュールを読み込む
    './modules/example',

    // インラインオプションを使用してモジュールを追加する
    ['./modules/example', { token: '123' }]

    // インラインモジュールを定義する
    async (inlineOptions, nuxt) => { }
  ]
})

Nuxt モジュールの定義

Nuxt モジュールの作成には、面倒で一般的なタスクが含まれます。Nuxt Kit は、defineNuxtModule を使用して Nuxt モジュールを定義するための便利で標準的な API を提供します:

import { defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  meta: {
    // 通常、モジュールの npm パッケージ名
    name: '@nuxtjs/example',
    // モジュールオプションを保持する `nuxt.config` のキー
    configKey: 'sample',
    // 互換性の制約
    compatibility: {
      // サポートされている nuxt バージョンの Semver バージョン
      nuxt: '^3.0.0'
    }
  },
  // モジュールのデフォルトの構成オプション
  defaults: {},
  hooks: {},
  async setup(moduleOptions, nuxt) {
    // -- ここにモジュールロジックを追加します --
  }
})

defineNuxtModule の結果は、(inlineOptions, nuxt) サインを持つラッパー関数です。これは、デフォルトと他の必要なステップを適用し、呼ばれたときに setup 関数を呼び出します。

defineNuxtModule特徴:

  • ✅ モジュールオプションを自動的にマージするための defaultsmeta.configKey のサポート
  • ✅ 型ヒントと自動型推論
  • ✅ 基本的な Nuxt 2 互換性のためのシム追加
  • meta.name または meta.configKey から計算されたユニークなキーを使用して、モジュールが一度だけインストールされるようにする。
  • ✅ Nuxt フックの自動登録
  • ✅ モジュールメタに基づく互換性問題の自動チェック
  • ✅ Nuxt の内部利用のために getOptionsgetMeta を公開する。
  • ✅ 最新バージョンの @nuxt/kit から defineNuxtModule を使用している限り、後方および上位互換性を保証する
  • ✅ モジュールビルダーツールとの統合

ベストプラクティス

非同期モジュール

Nuxt モジュールは非同期処理を行うことができます。例えば、ある API を取得したり、非同期関数を呼び出す必要があるモジュールを開発したいと思うかもしれません。

公開されたインターフェースには常に接頭辞を付ける

Nuxt モジュールは、他のモジュールや内部との衝突を避けるために、公開された設定、プラグイン、API、Composable、コンポーネントに対して明示的な接頭辞を提供する必要があります。

理想的には、あなたのモジュール名をプレフィックスとして付けるべきです(あなたのモジュールが nuxt-foo と呼ばれる場合、<Foo>useBar() ではなく、<FooButton>useFooBar() を公開してください)。

TypeScript フレンドリーにする

Nuxt 3 は、最高の開発体験を提供するために、ファーストクラスの TypeScript インテグレーションを備えています。

型を公開し、typescript を使用してモジュールを開発することは、typescript を直接使用しない場合でも、ユーザーに利益をもたらすことができます。

CommonJS の構文を避ける

Nuxt 3 では、ネイティブ ESM に依存しています。詳しくは、Native ES Modules をお読みください。

モジュールのエコシステム

Nuxt は、Nuxt モジュールと統合の健全で豊かなエコシステムを持つ傾向があります。ここにいくつかのベストプラクティスを示しますので、参加してコントリビュートしてください!

モジュールの使い方を文書化する

モジュールの使い方を readme ファイルに記述することを検討してください:

  • なぜこのモジュールを使うのか?
  • このモジュールの使い方は?
  • このモジュールは何をするのか?

インテグレーションサイトやドキュメントへのリンクは、常に良いアイデアです。

npm パッケージには nuxt- 接頭辞を使う

あなたのモジュールを発見しやすくするために、npm パッケージの名前に nuxt- 接頭辞を使ってください。これは常にアイデアを起草して試すのに最適な出発点です!

modules.nuxtjs.org にモジュールをリストアップする

作業中のモジュールがあり、modules.nuxtjs.org に掲載されたいですか?nuxt/modules リポジトリで issue を開いてください。Nuxt チームは、登録前にベストプラクティスを適用するための手助けをします。

特定の Nuxt バージョンで宣伝しないでください。

Nuxt 3、Nuxt Kit、その他の新しいツールは、前方および後方互換性の両方を考慮して作られています。

エコシステムの断片化を避けるために「X for Nuxt 3」ではなく「X for Nuxt」を使用し、Nuxt のバージョン制約を設定するために meta.compatibility を使用することを推奨します。

nuxt-community に参加する

あなたのモジュールを nuxt-community に移動することで、常に誰かが助けてくれますし、この方法で一つの完璧なソリューションを作るために力を合わせることができます。

もし既に公開され動作しているモジュールがあり、それを nuxt-community に移したい場合は、nuxt/modulesissue を開いてください。

Nuxt プラグインを提供する

一般的に、モジュールは実行時ロジックを追加するために1つ以上の実行プラグインを提供します。

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

export default defineNuxtModule<ModuleOptions>({
  setup (options, nuxt) {
    // Create resolver to resolve relative paths
    const { resolve } = createResolver(import.meta.url)

    addPlugin(resolve('./runtime/plugin'))
  }
})

CSS ライブラリを追加する

モジュールがCSSライブラリを提供する場合、重複を避けるために、ユーザーがすでにライブラリを含んでいるかどうかを確認し、モジュールにCSSライブラリを無効にするオプションを追加するようにしてください。

import { defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    nuxt.options.css.push('font-awesome/css/font-awesome.css')
  }
})

クリーンアップモジュール

もしあなたのモジュールがハンドルを開いたり、ウォッチャーを開始したら、nuxt のライフサイクルが終了したときにそれをクローズする必要があります。これには close フックを使います:

import { defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    nuxt.hook('close', async nuxt => {
      // Your custom code here
    })
  }
})

Nuxt キット

Nuxt Kitは、Nuxt HooksやNuxt Builder Coreとの対話やNuxtモジュールの開発を超簡単にするための、コンポーザブルなユーティリティを提供します。

使い方

依存関係のインストール

最新の nuxt キットは、package.jsondependencies セクションに追加することでインストールできます。ただし、@nuxt/kit パッケージが既に nuxt によってインストールされている場合でも、常に明示的にインストールすることを検討してください。

package.json
{
  "dependencies": {
    "@nuxt/kit": "npm:@nuxt/kit-edge@latest"
  }
}

キットユーティリティをインポートする

test.mjs
import { useNuxt } from '@nuxt/kit'

Nuxt kit は esm 専用パッケージ なので、require('@nuxt/kit') することができません。回避策として、CommonJS のコンテキストで使用するために、動的インポートを使用することができます:

test.cjs
// これは動作しません!
// const kit = require('@nuxt/kit')
async function main() {
  const kit = await import('@nuxt/kit')
}
main()
torish14torish14

ES モジュール

Nuxt 3(および Bridge)はNative ES Modulesを使用しています。

このガイドでは、ES モジュールとは何か、そして Nuxt アプリ(または、アップストリームライブラリ)を ESM と互換性を持たせる方法について説明します。

背景

CommonJS モジュール

CommonJS (CJS) は Node.js によって導入されたフォーマットで、分離された JavaScript モジュール間で機能を共有することができます(詳しくは こちらをご覧ください)。この構文については、すでにご存知の方もいらっしゃるかもしれません。

const a = require('./a')

module.exports.a = a

webpack や Rollup などのバンドラーはこの構文に対応しており、CommonJS で書かれたモジュールをブラウザで利用することができます。

ESM の構文

ほとんどの場合、ESM と CJS について話すとき、彼らは モジュール を書くための異なる構文について話しています。

import a from './a'

export { a }

ECMAScript Modules (ESM) が標準になる前に(10 年以上かかった!)、webpack などのツールや TypeScript などの言語でも、いわゆる ESM 構文 がサポートされ始めました。しかし、実際の仕様とはいくつかの重要な違いがあります;ここでは、役に立つ説明をしています

ネイティブ ESMとは?

長い間、ESM 構文を使ってアプリを書いてきたかもしれません。Nuxt 2 では、あなたが書いたすべてのコードを適切な形式(サーバは CJS、ブラウザは ESM)にコンパイルしています。

パッケージにインストールするモジュールを使用する場合、状況は少し異なりました。サンプルライブラリは、CJS と ESM の両方のバージョンを公開し、どちらを使うか選択できるようになっています:

{
  "name": "sample-library",
  "main": "dist/sample-library.cjs.js",
  "module": "dist/sample-library.esm.js"
}

つまり、Nuxt 2 では、バンドル(webpack)はサーバビルド用に CJS ファイル ('main') を取り込み、クライアントビルド用に ESM ファイル ('module')を使用することになります。

しかし、最近の Node.js LTS リリースでは、Node.js 内で ネイティブの ESM モジュールを使用する ことができるようになりました。つまり、デフォルトでは行いませんが、Node.js 自体が ESM 構文を使用して JavaScript を処理することができます。ESM 構文を有効にする最も一般的な方法は、次の 2 つです:

  • package.jsontype: 'module' を設定し、拡張子 .js を使い続ける。
  • .mjs ファイル拡張子を使用する(推奨)。

Nuxt Nitro では、.output/server/index.mjs というファイルを出力しています。これは、Node.js にこのファイルをネイティブ ES モジュールとして扱うように指示するものです。

Node.js の文脈で有効な import とは何ですか?

モジュールを require するのではなく、import すると、Node.js は異なる方法でモジュールを解決します。例えば、sample-library を import すると、Node.js は main ではなく、そのライブラリの package.json にある exportsmodule のエントリーを探します。

これは const b = await import('sample-library') のような動的なインポートにも当てはまります。

Node は以下の種類のインポートをサポートしています(ドキュメント を参照):

  1. .mjs で終わるファイル - これらは ESM 構文を使うことが期待されます。
  2. .cjs で終わるファイル - CJS 構文を使用することが想定されています。
  3. .js で終わるファイル - package.jsontype: 'module' がない限り、CJS シンタックスを使用することが期待されます。

どのような問題があるのでしょうか?

長い間、モジュール作者は ESM 構文のビルドを作成してきましたが、.esm.js.es.js などのコンベンションを使用しており、package.jsonmodule フィールドに追加してきました。これは今まで問題にはならなかったのですが、webpack のようなファイル拡張子を特に気にしない bundler によってのみ使用されてきたからです。

しかし、Node.js の ESM コンテキストで .esm.js ファイルを持つパッケージを import しようとすると、うまくいかず、次のようなエラーが発生します:

(node:22145) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
/path/to/index.js:1

export default {}
^^^^^^

SyntaxError: Unexpected token 'export'
    at wrapSafe (internal/modules/cjs/loader.js:1001:16)
    at Module._compile (internal/modules/cjs/loader.js:1049:27)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
    ....
    at async Object.loadESM (internal/process/esm_loader.js:68:5)

また、Node.js が CJS とみなしている ESM-syntax ビルドからの名前付きインポートがある場合にも、このエラーが発生する可能性があります:

file:///path/to/index.mjs:5
import { named } from 'sample-library'
         ^^^^^
SyntaxError: Named export 'named' not found. The requested module 'sample-library' is a CommonJS module, which may not support all module.exports as named exports.

CommonJS modules can always be imported via the default export, for example using:

import pkg from 'sample-library';
const { named } = pkg;

    at ModuleJob._instantiate (internal/modules/esm/module_job.js:120:21)
    at async ModuleJob.run (internal/modules/esm/module_job.js:165:5)
    at async Loader.import (internal/modules/esm/loader.js:177:24)
    at async Object.loadESM (internal/process/esm_loader.js:68:5)

ESM の問題のトラブルシューティング

これらのエラーが発生した場合、ほぼ間違いなく上流のライブラリに問題があります。Node からのインポートをサポートするために ライブラリを修正する 必要があります。

ライブラリのトランスパイル

とりあえず、これらのライブラリを build.transpile に追加することで、Nuxt がこれらのライブラリをインポートしないように指示することができます:

import { defineNuxtConfig } from 'nuxt'

export default defineNuxtConfig({
  build: {
    transpile: ['sample-library']
  }
})

また、これらのライブラリによってインポートされる他のパッケージを追加する必要があることが分かるかもしれません。

ライブラリのエイリアス

場合によっては、ライブラリのエイリアスを CJS バージョンに手動で変更する必要があることもあります:

import { defineNuxtConfig } from 'nuxt'

export default defineNuxtConfig({
  alias: {
    'sample-library': 'sample-library/dist/sample-library.cjs.js'
  }
})

NuxtApp

テスト

エッジチャンネル

torish14torish14

コミュニティ

助けを得る

あるとき、あなたは助けを必要とする問題があることに気づくかもしれません。でも、心配しないでください。私たちは開発者の友好的なコミュニティであり、手助けするのが大好きです。

"どのように〇〇したらいいかわからない。"

このドキュメントを一通り読んで、できるはずだと思ったのに、方法がはっきりしない。一番良いのは、GitHub Discussion を開設することです。

簡単だと思う質問でも、恥ずかしがらずに聞いてみてください - 私たちはみんなそこにいたのです! ❤️

あなたが出会うすべての人は、お金をもらっているからではなく、気にかけているからこそ助けてくれているのです。最も親切なことは、彼らがあなたを助けやすいようにすることです。以下にいくつかのアイデアを紹介します。

  • 直面している問題だけでなく、あなたの目的が何であるかを説明する。"フォームの入力にアクセスできるようにする必要があるので、サーバーとクライアントの間で id を一致させようとしています"。
  • まずドキュメントを読み、お気に入りの検索エンジンを使ったことを確認してください。"私は、'nuxt script setup' とググりましたが、コードサンプルをどこにも見つけられませんでした。" のような言い方で人々に知らせましょう。
  • あなたが試したことを説明してください。あなたがどのような解決策を試したのか、そしてその理由を伝えましょう。そうすることで、人々のアドバイスがよりあなたの状況に適したものになることがよくあります。
  • コードを共有する。エラーメッセージやスクリーンショットを見ただけでは、おそらくほかの人はあなたを助けられないでしょう。しかし、あなたのコードをコピー & ペーストできる形式で、できれば CodeSandbox のような最小限の複製という形で共有すれば、すべてが変わります。

そして最後に、ただ質問をすることです。質問をするために許可を得る必要はありませんし、誰かがあなたの「こんにちは」に返信するのを待つ必要もありません。なぜなら、ほかの人は手助けをする前に質問全体が来るのを待っているので、反応が得られないかも知れません。

"バグがあるのでは?"

何かがドキュメントに書いてある通りに動いていない。それがバグかどうかもわからない。公開されている issuesdiscussions に目を通したが、何も見つからない。(クローズされた issue がある場合は、新しい issue を作成してください)

バグを報告する方法をご覧になることをお勧めします。Nuxt 3 はまだ活発に開発されており、すべての問題はより良いものにするために役立ちます。

torish14torish14

バグの報告

バグを完全になくすことはできません。オープンソースにおける最も価値ある役割の 1 つは、時間をかけてバグを報告することです。たとえあなたが根本的なコードを修正できなくても、バグをうまく報告することで、コードベースにもう少し精通したほかのだれかがパターンを発見したり、迅速な修正を行ったりすることができます。

ここでは、いくつかの重要なステップを紹介します。

それは本当にバグですか?

何か助けを求めているのか、それとも Nuxt 自体にバグがあると思うのか、よく考えてみてください。もし前者であれば、私たちはあなたを助けたいと思いますが、バグを報告するのではなく、助けを求めることが最善の方法です。

issues を検索する

まず、公開されている issuesdiscussions を検索してください。同じようなバグを見つけたら、重複して作成するよりも、既存のスレッドにコメントする方がずっとよいでしょう。

最小限の再現を作成する

プロジェクトの他の部分とは別に、最小限の方法で、バグを確実に再現できることが重要です。そうすることで、問題の原因を絞り込むことができ、原因を突き止めるだけでなく、解決策を試すことも可能になります。

Nuxt 3 または Nuxt Bridge の sandbox で開始し、発生しているバグを再現するために必要な最小限のコードを追加します。

Nuxt 3:

Nuxt Bridge:

Vue 3:

問題を再現したら、(バグを再現しつつ)再現したコードからできる限り削除してください。再現をできるだけ少なくするために費やした時間は、その問題を解決しようとする人にとって大きな違いとなります。

原因の解明

Nuxt プロジェクトでは、nuxt モジュールから他の JavaScript ライブラリまで、多くの可動部品があります。最も関連性が高く、具体的な場所でバグを報告するようにしてください。それはおそらく、問題を引き起こしている Nuxt モジュールか、Nuxt が依存している上流のライブラリでしょう。

torish14torish14

貢献する

Nuxt はコミュニティプロジェクトで、あらゆる貢献を待ち望んでいます!❤️
Nuxt のエコシステムに貢献できる方法はさまざまです。

ドキュメントの改善

ドキュメントは、Nuxt の最も重要な部分の 1 つです。私たちは直感的なフレームワークを目指しており、そのためには開発者体験とドキュメントの両方を完璧なものにすることが重要なポイントになります。👌

もし、ドキュメントやエラーメッセージを改善できる箇所を見つけたら、ぜひ PR をお願いします!

issues を分類し、discussions に参加する

私たちの issues ボードdiscussions をご覧ください。他のユーザーを助けたり、回避策を共有したり、複製を作成したり、あるいはバグを少し調べて発見を共有したりと - これらはすべて、大きな違いを生み出します。

バグを修正する、機能を追加する

バグを修正したり機能を追加したりする前に、そのバグについて記述した issues があることを確認します。特に新機能の場合は、作業を始める前にプロジェクトリーダーからフィードバックをもらう絶好の機会です。

開発環境のセットアップ

  1. Nuxt 3 のリポジトリを自分の GitHub アカウントにフォークし、それをローカルのデバイスにクローンします。
  2. yarn を実行して依存関係をインストールします。

依存関係を追加する場合は、yarn add を使用してください。yarn.lock ファイルは、すべての Nuxt 依存性のための真実の源です。

  1. パッシブ開発の活性化のために yarn stub を実行します。
  2. 作業可能なブランチをチェックアウトし、変更をコミットします。
git checkout -b my-new-branch

PR の作業中、変更した内容が正しく動作しているかどうかを確認したくなることがあると思います。

そのためには、playground/ にあるサンプルアプリを修正し、yarn play で実行することができます。ブランチにコミットしないように注意しましょう。しかし、PR の説明文にサンプルコードを追加しておくと便利です。これはレビュアーや他の Nuxt ユーザーがあなたが作った機能を深く理解するのに役立ちます。

テスト

すべての新しい機能には、(可能であれば)対応するユニットテストが必要です。このリポジトリにある test フォルダは現在進行中ですが、すでにあるものの例に従って新しいテストを作成するよう、最善を尽くしてください。

PR を作成したり、ready-to-review としてマークする前に、ローカルで yarn test を実行して、すべてのテストがパスしていることを確認してください。

コード整形

すでにお気づきかもしれませんが、私たちは ESLint を使用してコーディング規約を強制しています。変更をコミットする前に yarn lint を実行して、コードスタイルが正しいかどうか確認してください。もしそうでなければ、yarn lint --fix を使ってスタイルの変更のほとんどを修正することができます。もしエラーが残っている場合は、手動で修正する必要があります。

ドキュメント

新しい機能を追加したり、リファクタリングやその他の方法で Nuxt の動作を変更する場合、おそらくその変更を文書化したいと思うことでしょう。ドキュメントへの変更はすべて同じ PR に含めてください。最初のコミットでドキュメントを書き上げる必要はありません(ただし、プルリクエストが十分に成熟したらすぐに書き上げるようにしてください)。

最後のチェックリスト

PR を提出する際には、簡単なテンプレートがありますので、そちらに記入してください。チェックリストの該当する「答え」に全てチェックを入れてください。

特に、PR のタイトルが Conventional Commits のガイドラインに従っていることを確認し、課題の説明で関連する課題(機能リクエストまたはバグレポート)にリンクしてください。

torish14torish14

ロードマップ

Nuxt は常に進化しており、新しい機能やモジュールが随時追加されています。このページでは、現在予定されているリリースの状況やスケジュールを掲載しています。

📢 アナウンス

  • Nuxt 3 ベータ
  • Nuxt 3 リリース候補

📅 リリーススケジュール

リリース 予定日 説明
nuxt@3.0.0-rc.1 2022 年 3 月 Nuxt v3 初リリース候補
nuxt@2.16 2022 年 3 月 Nuxt v2 将来の互換性のための累積的な更新
nuxt@3.0.0 2022 年 6 月 Nuxt v3 安定リリース

最近のリリース

現在、Nuxt 2 が安定版とされています。

Nuxt 2.x の最新アップデートは nuxt-edge npm パッケージ経由で入手でき、将来の互換性のために Nuxt 3 の最初のリリース候補と一緒にリリースされる予定です。Nuxt 3 は現在開発中で、nuxt3 npm パッケージとして利用でき、ベータテスト用に自動リリースされています。

リリース npm ステータス 最終リリース ドキュメント リポジトリ
Nuxt 2.x nuxt メンテナンス npm v2.15.8 2.x ドキュメント nuxt/nuxt.js#2.x
Nuxt 2.x(edge*) nuxt-edge メンテナンス npm v2.16.0-27358576.777a4b7f 2.x ドキュメント nuxt/nuxt.js
Nuxt 3.x(edge*) nuxt 3 開発中 npm v3.0.0-27470397.9ebea90 3.x ドキュメント nuxt/framework

(*)Edge のリリースは、各コミットがテストに合格した後に自動化されます

🛣️ ロードマップ

ロードマップでは、Nuxt 3 に搭載される主な機能を紹介しています。

💡 今後の機能やアイデアについては、DiscussionsRFC をチェックしてください。

マイルストーン 予定日 ノート 説明
Nitro パック Q1 2022 nuxt/framework#3161 Nuxt 3 用の安定したフル機能のサーバーエンジン
生成とキャッシュ Q1 2022 nuxt/framework#3161 ルートキャッシングルール、フルスタティック生成
テスト Utils Q1 2022 nuxt/framework#3198 Nuxt 3 と新しいモジュールをテストするために nuxt/test-utils を書き直しました
Nuxt 3 RC Q1 2022 nuxt/framework#3447 Nuxt 3 初リリース候補
Extending Q1 2022 nuxt/framework#3222 ネイティブエクステンションとテーマのサポート
Content v2 Q1 2022 - Nuxt 3 をサポートするために nuxt/content を書き直しました
Devtools Q2 2022 - Nuxt のための統合された devtools エクスペリエンス
Auth Q2 2022 - nuxt-community/auth-module を Nuxt 3 対応に書き直したものです
SEO & PWA Q2 2022 nuxt/framework#1823 nuxt-community/pwa-module からの移行は、組み込みの SEO ユーティリティとサービスワーカーをサポートするためです。
翻訳 Q2/Q3 2022 nuxt/framework#4(private) Nuxt 3 ドキュメントの安定した翻訳プロセスのための共同プロジェクトです。現在、アイデアとドキュメントツールのサポート(リモートソースによるコンテンツ v2)を申請中です。

コアモジュール

Nuxt フレームワークの他に、エコシステムに欠かせないモジュールがあります。それらのステータスは以下に更新されます。

モジュール ステータス Nuxt サポート リポジトリ 説明
Content 1.x メンテナンス 2.x nuxt/content メンテナンスのみ
Content 2.x 進行中の作業 3.x nuxt/content-next プライベートベータ
Auth 進行中の作業 3.x nuxt/auth プライベートベータ
Image アクティブ 2.x nuxt/image Nuxt 3 サポート予定
Telementry アクティブ 2.x nuxt/telementry Nuxt 3 サポート予定
torish14torish14

API

useAsyncData

pages、コンポーネント、およびプラグイン内で、useAsyncData を使用して、非同期で解決されるデータにアクセスできます。

Signature
function useAsyncData(
  key: string,
  handler: (nuxtApp?: NuxtApp) => Promise<DataT>,
  options?: AsyncDataOptions
): Promise<DataT>

type AsyncDataOptions = {
  server?: boolean
  lazy?: boolean
  default?: () => DataT | Ref<DataT>
  transform?: (input: DataT) => DataT
  pick?: string[]
  watch?: WatchSource[]
  initialCache?: boolean
}

type DataT = {
  data: Ref<DataT>
  pending: Ref<boolean>
  refresh: () => Promise<void>
  error: Ref<any>
}

Params

  • key: データフェッチをリクエスト間で適切に重複排除できるようにするための一意のキー
  • handler: 値を返す非同期関数
  • オプション:
    • lazy: ナビゲーションをブロックする代わりに、ルートのロード後に非同期関数を解決するかどうか(デフォルトは false
    • default: 非同期関数が解決する前にデータのデフォルト値を設定するファクトリ関数 - 特に lazy: true オプションで役立ちます
    • server: サーバー上のデータをフェッチするかどうか(デフォルトは true
    • transform: 解決後に handler 関数の結果を変更するために使用できる関数
    • pick: handler 関数の結果からこの配列内の指定されたキーのみを選択します
    • watch: リアクティブソースを監視して自動更新します
    • initialCache: false に設定すると、初期フェッチのペイロードキャッシュをスキップします(デフォルトは true

内部的には、lazy: false<Suspense> を使用して、データがフェッチされる前にルートのロードをブロックします。より迅速なユーザーエクスペリエンスのために、lazy: true を使用し、代わりに読み込み状態を実装することを検討してください。

戻り値

  • data: 渡された非同期関数の結果
  • pending: データがまだフェッチされているかどうかを示すブール値
  • refresh: ハンドラー関数によって返されたデータを更新するために使用できる関数
  • error: データのフェッチに失敗した場合のエラーオブジェクト

デフォルトでは、Nuxt は 更新 が終了するまで待機してから再度実行できます。パラメータとして true を渡すと、その待機はスキップされます。

const { data, pending, error, refresh } = await useAsyncData(
  'mountains',
  () => $fetch('https://api.nuxtjs.dev/mountains')
)

useCookie

useError

useFetch

useHead

useHydration

useLazyAsyncData

useLazyFetch

useNuxtApp

useRequestHeaders

useRoute

useRouter

useState

torish14torish14

$fetch

abortNavigation

addRouteMiddleware

clearError

defineNuxtComponent

defineNuxtRouteMiddleware

definePageMeta

refreshNuxtData

throwError

torish14torish14

ライフサイクルフック

アプリフック(ランタイム)

利用可能なすべてのフックについては、アプリのソースコード を確認してください。

フック 引数 説明
app:created vueApp vueApp の初期インスタンス作成時
app:beforeMount vueApp app:created と同じ
app:mounted vueApp Vue アプリが初期化され、ブラウザにマウントされた時
app:rendered - SSR レンダリングが行われた時
app:suspense:resolve appComponent Suspense 解決イベント
page:start pageComponent Suspense 解決イベント
page:finish pageComponent Suspense 解決イベント
meta:register metaRenderers (内部)
vue:setup - (内部)

Nuxt フック(ビルドタイム)

利用可能なすべてのフックについては、スキーマのソースコード を確認してください。

torish14torish14

Kit ユーティリティ

ユーティリティ

モジュール

kit/src/module/container.ts
import { parse, relative } from 'pathe'
import type { Nuxt, NuxtPluginTemplate, NuxtTemplate, ModuleContainer } from '@nuxt/schema'
import { logger } from '../logger'
import { chainFn } from '../internal/task'
import { addTemplate } from '../template'
import { addServerMiddleware } from '../server'
import { isNuxt2 } from '../compatibility'
import { addPluginTemplate } from '../plugin'
import { useNuxt } from '../context'
import { installModule } from './install'

const MODULE_CONTAINER_KEY = '__module_container__'

export function useModuleContainer (nuxt: Nuxt = useNuxt()): ModuleContainer {
  if (nuxt[MODULE_CONTAINER_KEY]) {
    return nuxt[MODULE_CONTAINER_KEY]
  }

  async function requireModule (moduleOpts) {
    let src, inlineOptions
    if (typeof moduleOpts === 'string') {
      src = moduleOpts
    } else if (Array.isArray(moduleOpts)) {
      [src, inlineOptions] = moduleOpts
    } else if (typeof moduleOpts === 'object') {
      if (moduleOpts.src || moduleOpts.handler) {
        src = moduleOpts.src || moduleOpts.handler
        inlineOptions = moduleOpts.options
      } else {
        src = moduleOpts
      }
    } else {
      src = moduleOpts
    }
    await installModule(src, inlineOptions)
  }

  nuxt[MODULE_CONTAINER_KEY] = <ModuleContainer>{
    nuxt,
    options: nuxt.options,

    ready () { return Promise.resolve() },
    addVendor () {},

    requireModule,
    addModule: requireModule,

    addServerMiddleware,

    addTemplate (template: string | NuxtTemplate) {
      if (typeof template === 'string') {
        template = { src: template }
      }
      if (template.write === undefined) {
        template.write = true
      }
      return addTemplate(template)
    },

    addPlugin (pluginTemplate: NuxtPluginTemplate): NuxtPluginTemplate {
      return addPluginTemplate(pluginTemplate)
    },

    addLayout (tmpl: NuxtTemplate, name: string) {
      const { filename, src } = addTemplate(tmpl)
      const layoutName = name || parse(src).name
      const layout = nuxt.options.layouts[layoutName]

      if (layout) {
        logger.warn(`Duplicate layout registration, "${layoutName}" has been registered as "${layout}"`)
      }
      nuxt.options.layouts[layoutName] = `./${filename}`
      if (name === 'error') {
        this.addErrorLayout(filename)
      }
    },

    addErrorLayout (dst: string) {
      const relativeBuildDir = relative(nuxt.options.rootDir, nuxt.options.buildDir)
      nuxt.options.ErrorPage = `~/${relativeBuildDir}/${dst}`
    },

    extendBuild (fn) {
      // @ts-ignore
      nuxt.options.build.extend = chainFn(nuxt.options.build.extend, fn)

      if (!isNuxt2(nuxt)) {
        console.warn('[kit] [compat] Using `extendBuild` in Nuxt 3 has no effect. Instead call extendWebpackConfig and extendViteConfig.')
      }
    },

    extendRoutes (fn) {
      if (isNuxt2(nuxt)) {
        nuxt.options.router.extendRoutes = chainFn(nuxt.options.router.extendRoutes, fn)
      } else {
        nuxt.hook('pages:extend', async (pages, ...args) => {
          const maybeRoutes = await fn(pages, ...args)
          if (maybeRoutes) {
            console.warn('[kit] [compat] Using `extendRoutes` in Nuxt 3 needs to directly modify first argument instead of returning updated routes. Skipping extended routes.')
          }
        })
      }
    }
  }

  return nuxt[MODULE_CONTAINER_KEY]
}
kit/src/module/define.ts
import { promises as fsp } from 'node:fs'
import defu from 'defu'
import { applyDefaults } from 'untyped'
import { dirname } from 'pathe'
import type { Nuxt, NuxtTemplate, NuxtModule, ModuleOptions, ModuleDefinition } from '@nuxt/schema'
import { logger } from '../logger'
import { useNuxt, nuxtCtx, tryUseNuxt } from '../context'
import { isNuxt2, checkNuxtCompatibility } from '../compatibility'
import { templateUtils, compileTemplate } from '../internal/template'

/**
 * Define a Nuxt module, automatically merging defaults with user provided options, installing
 * any hooks that are provided, and calling an optional setup function for full control.
 */
export function defineNuxtModule<OptionsT extends ModuleOptions> (definition: ModuleDefinition<OptionsT>): NuxtModule<OptionsT> {
  // Legacy format. TODO: Remove in RC
  if (typeof definition === 'function') {
    // @ts-ignore
    definition = definition(useNuxt())
    logger.warn('Module definition as function is deprecated and will be removed in the future versions', definition)
  }

  // Normalize definition and meta
  if (!definition.meta) { definition.meta = {} }
  if (!definition.meta.configKey) {
    // @ts-ignore TODO: Remove non-meta fallbacks in RC
    definition.meta.name = definition.meta.name || definition.name
    // @ts-ignore
    definition.meta.configKey = definition.meta.configKey || definition.configKey || definition.meta.name
  }

  // Resolves module options from inline options, [configKey] in nuxt.config, defaults and schema
  function getOptions (inlineOptions?: OptionsT, nuxt: Nuxt = useNuxt()) {
    const configKey = definition.meta.configKey || definition.meta.name
    const _defaults = definition.defaults instanceof Function ? definition.defaults(nuxt) : definition.defaults
    let _options = defu(inlineOptions, nuxt.options[configKey], _defaults) as OptionsT
    if (definition.schema) {
      _options = applyDefaults(definition.schema, _options) as OptionsT
    }
    return Promise.resolve(_options)
  }

  // Module format is always a simple function
  async function normalizedModule (inlineOptions: OptionsT, nuxt: Nuxt) {
    if (!nuxt) {
      nuxt = tryUseNuxt() || this.nuxt /* invoked by nuxt 2 */
    }

    // Avoid duplicate installs
    const uniqueKey = definition.meta.name || definition.meta.configKey
    if (uniqueKey) {
      nuxt.options._requiredModules = nuxt.options._requiredModules || {}
      if (nuxt.options._requiredModules[uniqueKey]) {
        // TODO: Notify user if inline options is provided since will be ignored!
        return
      }
      nuxt.options._requiredModules[uniqueKey] = true
    }

    // Check compatibility constraints
    if (definition.meta.compatibility) {
      const issues = await checkNuxtCompatibility(definition.meta.compatibility, nuxt)
      if (issues.length) {
        logger.warn(`Module \`${definition.meta.name}\` is disabled due to incompatibility issues:\n${issues.toString()}`)
        return
      }
    }

    // Prepare
    nuxt2Shims(nuxt)

    // Resolve module and options
    const _options = await getOptions(inlineOptions, nuxt)

    // Register hooks
    if (definition.hooks) {
      nuxt.hooks.addHooks(definition.hooks)
    }

    // Call setup
    await definition.setup?.call(null, _options, nuxt)
  }

  // Define getters for options and meta
  normalizedModule.getMeta = () => Promise.resolve(definition.meta)
  normalizedModule.getOptions = getOptions

  return normalizedModule as NuxtModule<OptionsT>
}

// -- Nuxt 2 compatibility shims --
const NUXT2_SHIMS_KEY = '__nuxt2_shims_key__'
function nuxt2Shims (nuxt: Nuxt) {
  // Avoid duplicate install and only apply to Nuxt2
  if (!isNuxt2(nuxt) || nuxt[NUXT2_SHIMS_KEY]) { return }
  nuxt[NUXT2_SHIMS_KEY] = true

  // Allow using nuxt.hooks
  // @ts-ignore Nuxt 2 extends hookable
  nuxt.hooks = nuxt

  // Allow using useNuxt()
  if (!nuxtCtx.use()) {
    nuxtCtx.set(nuxt)
    nuxt.hook('close', () => nuxtCtx.unset())
  }

  // Support virtual templates with getContents() by writing them to .nuxt directory
  let virtualTemplates: NuxtTemplate[]
  nuxt.hook('builder:prepared', (_builder, buildOptions) => {
    virtualTemplates = buildOptions.templates.filter(t => t.getContents)
    for (const template of virtualTemplates) {
      buildOptions.templates.splice(buildOptions.templates.indexOf(template), 1)
    }
  })
  nuxt.hook('build:templates', async (templates) => {
    const context = {
      nuxt,
      utils: templateUtils,
      app: {
        dir: nuxt.options.srcDir,
        extensions: nuxt.options.extensions,
        plugins: nuxt.options.plugins,
        templates: [
          ...templates.templatesFiles,
          ...virtualTemplates
        ],
        templateVars: templates.templateVars
      }
    }
    for await (const template of virtualTemplates) {
      const contents = await compileTemplate({ ...template, src: '' }, context)
      await fsp.mkdir(dirname(template.dst), { recursive: true })
      await fsp.writeFile(template.dst, contents)
    }
  })
}
kit/src/module/install.ts
import type { Nuxt, NuxtModule } from '@nuxt/schema'
import { useNuxt } from '../context'
import { resolveModule, requireModule, importModule } from '../internal/cjs'
import { resolveAlias } from '../resolve'
import { useModuleContainer } from './container'

/** Installs a module on a Nuxt instance. */
export async function installModule (moduleToInstall: string | NuxtModule, _inlineOptions?: any, _nuxt?: Nuxt) {
  const nuxt = useNuxt()
  const { nuxtModule, inlineOptions } = await normalizeModule(moduleToInstall, _inlineOptions)

  // Call module
  await nuxtModule.call(useModuleContainer(), inlineOptions, nuxt)

  nuxt.options._installedModules = nuxt.options._installedModules || []
  nuxt.options._installedModules.push({
    meta: await nuxtModule.getMeta?.(),
    entryPath: typeof moduleToInstall === 'string' ? resolveAlias(moduleToInstall) : undefined
  })
}

// --- Internal ---

async function normalizeModule (nuxtModule: string | NuxtModule, inlineOptions?: any) {
  const nuxt = useNuxt()

  // Detect if `installModule` used with older signuture (nuxt, nuxtModule)
  // TODO: Remove in RC
  // @ts-ignore
  if (nuxtModule?._version || nuxtModule?.version || nuxtModule?.constructor?.version || '') {
    [nuxtModule, inlineOptions] = [inlineOptions, {}]
    console.warn(new Error('`installModule` is being called with old signature!'))
  }

  // Import if input is string
  if (typeof nuxtModule === 'string') {
    const _src = resolveModule(resolveAlias(nuxtModule), { paths: nuxt.options.modulesDir })
    // TODO: also check with type: 'module' in closest `package.json`
    const isESM = _src.endsWith('.mjs')
    nuxtModule = isESM ? await importModule(_src) : requireModule(_src)
  }

  // Throw error if input is not a function
  if (typeof nuxtModule !== 'function') {
    throw new TypeError('Nuxt module should be a function: ' + nuxtModule)
  }

  return { nuxtModule, inlineOptions } as { nuxtModule: NuxtModule<any>, inlineOptions: undefined | Record<string, any> }
}
  • installModule(module, inlineOptions)

プログラムの使用法

kit/src/loader/config.ts
import { resolve } from 'pathe'
import { applyDefaults } from 'untyped'
import { loadConfig, LoadConfigOptions } from 'c12'
import type { NuxtOptions, NuxtConfig } from '@nuxt/schema'
import { NuxtConfigSchema } from '@nuxt/schema'

export interface LoadNuxtConfigOptions extends LoadConfigOptions<NuxtConfig> {}

export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<NuxtOptions> {
  const { config: nuxtConfig, configFile, layers, cwd } = await loadConfig({
    name: 'nuxt',
    configFile: 'nuxt.config',
    rcFile: '.nuxtrc',
    dotenv: true,
    globalRc: true,
    ...opts
  })

  // Fill config
  nuxtConfig.rootDir = nuxtConfig.rootDir || cwd
  nuxtConfig._nuxtConfigFile = configFile
  nuxtConfig._nuxtConfigFiles = [configFile]

  // Resolve `rootDir` & `srcDir` of layers
  for (const layer of layers) {
    layer.config.rootDir = layer.config.rootDir ?? layer.cwd
    layer.config.srcDir = resolve(layer.config.rootDir, layer.config.srcDir)
  }

  // Filter layers
  nuxtConfig._layers = layers.filter(layer => layer.configFile && !layer.configFile.endsWith('.nuxtrc'))

  // Resolve and apply defaults
  return applyDefaults(NuxtConfigSchema, nuxtConfig) as NuxtOptions
}
kit/src/loader/nuxt.ts
import { readPackageJSON, resolvePackageJSON } from 'pkg-types'
import type { Nuxt } from '@nuxt/schema'
import { importModule, tryImportModule, RequireModuleOptions } from '../internal/cjs'
import type { LoadNuxtConfigOptions } from './config'

export interface LoadNuxtOptions extends LoadNuxtConfigOptions {
  /** Load nuxt with development mode */
  dev?: boolean

  /** Use lazy initialization of nuxt if set to false */
  ready?: boolean

  /** @deprecated Use cwd option */
  rootDir?: LoadNuxtConfigOptions['cwd']

  /** @deprecated use overrides option */
  config?: LoadNuxtConfigOptions['overrides']
}

export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
  // Backward compatibility
  opts.cwd = opts.cwd || opts.rootDir
  opts.overrides = opts.overrides || opts.config || {}

  const resolveOpts: RequireModuleOptions = { paths: opts.cwd }

  // Apply dev as config override
  opts.overrides.dev = !!opts.dev

  const nearestNuxtPkg = await Promise.all(['nuxt3', 'nuxt', 'nuxt-edge']
    .map(pkg => resolvePackageJSON(pkg, { url: opts.cwd }).catch(() => null)))
    .then(r => r.filter(Boolean).sort((a, b) => b.length - a.length)[0])
  if (!nearestNuxtPkg) {
    throw new Error(`Cannot find any nuxt version from ${opts.cwd}`)
  }
  const pkg = await readPackageJSON(nearestNuxtPkg)
  const majorVersion = parseInt((pkg.version || '').split('.')[0])

  // Nuxt 3
  if (majorVersion === 3) {
    const { loadNuxt } = await importModule((pkg as any)._name || pkg.name, resolveOpts)
    const nuxt = await loadNuxt(opts)
    return nuxt
  }

  // Nuxt 2
  const { loadNuxt } = await tryImportModule('nuxt-edge', resolveOpts) || await importModule('nuxt', resolveOpts)
  const nuxt = await loadNuxt({
    rootDir: opts.cwd,
    for: opts.dev ? 'dev' : 'build',
    configOverrides: opts.overrides,
    ready: opts.ready,
    envConfig: opts.dotenv // TODO: Backward format convertion
  })

  return nuxt as Nuxt
}

export async function buildNuxt (nuxt: Nuxt): Promise<any> {
  const resolveOpts: RequireModuleOptions = { paths: nuxt.options.rootDir }

  // Nuxt 3
  if (nuxt.options._majorVersion === 3) {
    const { build } = await tryImportModule('nuxt3', resolveOpts) || await importModule('nuxt', resolveOpts)
    return build(nuxt)
  }

  // Nuxt 2
  const { build } = await tryImportModule('nuxt-edge', resolveOpts) || await importModule('nuxt', resolveOpts)
  return build(nuxt)
}
  • loadNuxt(loadOptions)
  • buildNuxt(nuxt)
  • loadNuxtConfig(loadOptions)

互換性

kit/src/compatibility.ts
import satisfies from 'semver/functions/satisfies.js' // npm/node-semver#381
import type { Nuxt, NuxtCompatibility, NuxtCompatibilityIssues } from '@nuxt/schema'
import { useNuxt } from './context'

/**
 * Check version constraints and return incompatibility issues as an array
 */
export async function checkNuxtCompatibility (constraints: NuxtCompatibility, nuxt: Nuxt = useNuxt()): Promise<NuxtCompatibilityIssues> {
  const issues: NuxtCompatibilityIssues = []

  // Nuxt version check
  if (constraints.nuxt) {
    const nuxtVersion = getNuxtVersion(nuxt)
    const nuxtSemanticVersion = nuxtVersion.split('-').shift()
    if (!satisfies(nuxtSemanticVersion, constraints.nuxt)) {
      issues.push({
        name: 'nuxt',
        message: `Nuxt version \`${constraints.nuxt}\` is required but currently using \`${nuxtVersion}\``
      })
    }
  }

  // Bridge compatibility check
  if (isNuxt2(nuxt)) {
    const bridgeRequirement = constraints?.bridge
    const hasBridge = !!(nuxt.options as any).bridge
    if (bridgeRequirement === true && !hasBridge) {
      issues.push({
        name: 'bridge',
        message: 'Nuxt bridge is required'
      })
    } else if (bridgeRequirement === false && hasBridge) {
      issues.push({
        name: 'bridge',
        message: 'Nuxt bridge is not supported'
      })
    }
  }

  // Allow extending compatibility checks
  await nuxt.callHook('kit:compatibility', constraints, issues)

  // Issues formatter
  issues.toString = () =>
    issues.map(issue => ` - [${issue.name}] ${issue.message}`).join('\n')

  return issues
}

/**
 * Check version constraints and throw a detailed error if has any, otherwise returns true
 */
export async function assertNuxtCompatibility (constraints: NuxtCompatibility, nuxt: Nuxt = useNuxt()): Promise<true> {
  const issues = await checkNuxtCompatibility(constraints, nuxt)
  if (issues.length) {
    throw new Error('Nuxt compatibility issues found:\n' + issues.toString())
  }
  return true
}

/**
 * Check version constraints and return true if passed, otherwise returns false
 */
export async function hasNuxtCompatibility (constraints: NuxtCompatibility, nuxt: Nuxt = useNuxt()): Promise<boolean> {
  const issues = await checkNuxtCompatibility(constraints, nuxt)
  return !issues.length
}

/**
 * Check if current nuxt instance is version 2 legacy
 */
export function isNuxt2 (nuxt: Nuxt = useNuxt()) {
  return getNuxtVersion(nuxt).startsWith('2.')
}

/**
 * Check if current nuxt instance is version 3
 */
export function isNuxt3 (nuxt: Nuxt = useNuxt()) {
  return getNuxtVersion(nuxt).startsWith('3.')
}

/**
 * Get nuxt version
 */
export function getNuxtVersion (nuxt: Nuxt | any = useNuxt() /* TODO: LegacyNuxt */) {
  const version = (nuxt?._version || nuxt?.version || nuxt?.constructor?.version || '').replace(/^v/g, '')
  if (!version) {
    throw new Error('Cannot determine nuxt version! Is currect instance passed?')
  }
  return version
}
  • checkNuxtCompatibility(constraints)
  • assertNuxtCompatibility(constraints)
  • hasNuxtCompatibility(constraints)
  • isNuxt2()
  • isNuxt3()
  • getNuxtVersion()
torish14torish14

コンポーネント

kit/src/components.ts
import { pascalCase, kebabCase } from 'scule'
import type { ComponentsDir, Component } from '@nuxt/schema'
import { genDynamicImport } from 'knitwork'
import { useNuxt } from './context'
import { assertNuxtCompatibility } from './compatibility'

/**
 * Register a directory to be scanned for components and imported only when used.
 *
 * Requires Nuxt 2.13+
 */
export async function addComponentsDir (dir: ComponentsDir) {
  const nuxt = useNuxt()
  await assertNuxtCompatibility({ nuxt: '>=2.13' }, nuxt)
  nuxt.options.components = nuxt.options.components || []
  nuxt.hook('components:dirs', (dirs) => { dirs.push(dir) })
}

export type AddComponentOptions = { name: string, filePath: string } & Partial<Exclude<Component,
'shortPath' | 'async' | 'level' | 'import' | 'asyncImport'
>>

/**
 * Register a directory to be scanned for components and imported only when used.
 *
 * Requires Nuxt 2.13+
 */
export async function addComponent (opts: AddComponentOptions) {
  const nuxt = useNuxt()
  await assertNuxtCompatibility({ nuxt: '>=2.13' }, nuxt)
  nuxt.options.components = nuxt.options.components || []

  // Apply defaults
  const component: Component = {
    export: opts.export || 'default',
    chunkName: 'components/' + kebabCase(opts.name),
    global: opts.global ?? false,
    kebabName: kebabCase(opts.name || ''),
    pascalName: pascalCase(opts.name || ''),
    prefetch: false,
    preload: false,
    mode: 'all',

    // Nuxt 2 support
    shortPath: opts.filePath,
    async: false,
    level: 0,
    asyncImport: `${genDynamicImport(opts.filePath)}.then(r => r['${opts.export || 'default'}'])`,
    import: `require(${JSON.stringify(opts.filePath)})['${opts.export || 'default'}']`,

    ...opts
  }

  nuxt.hook('components:extend', (components: Component[]) => {
    const existingComponent = components.find(c => c.pascalName === component.pascalName || c.kebabName === component.kebabName)
    if (existingComponent) {
      const name = existingComponent.pascalName || existingComponent.kebabName
      console.warn(`Overriding ${name} component.`)
      Object.assign(existingComponent, component)
    } else {
      components.push(component)
    }
  })
}
  • addComponentsDir(dir)
  • addComponent(componentObject)

コンテキスト

kit/src/context.ts
import { getContext } from 'unctx'
import type { Nuxt } from '@nuxt/schema'

/** Direct access to the Nuxt context - see https://github.com/unjs/unctx. */
export const nuxtCtx = getContext<Nuxt>('nuxt')

// TODO: Use use/tryUse from unctx. https://github.com/unjs/unctx/issues/6

/**
 * Get access to Nuxt instance.
 *
 * Throws an error if Nuxt instance is unavailable.
 *
 * @example
 * ```js
 * const nuxt = useNuxt()
 * ```
 */
export function useNuxt (): Nuxt {
  const instance = nuxtCtx.use()
  if (!instance) {
    throw new Error('Nuxt instance is unavailable!')
  }
  return instance
}

/**
 * Get access to Nuxt instance.
 *
 * Returns null if Nuxt instance is unavailable.
 *
 * @example
 * ```js
 * const nuxt = tryUseNuxt()
 * if (nuxt) {
 *  // Do something
 * }
 * ```
 */
export function tryUseNuxt (): Nuxt | null {
  return nuxtCtx.use()
}
  • useNuxt()

プラグイン

kit/src/plugin.ts
import { normalize } from 'pathe'
import type { NuxtPlugin, NuxtPluginTemplate } from '@nuxt/schema'
import { useNuxt } from './context'
import { addTemplate } from './template'

/**
 * Normalize a nuxt plugin object
 */
export function normalizePlugin (plugin: NuxtPlugin | string): NuxtPlugin {
  // Normalize src
  if (typeof plugin === 'string') {
    plugin = { src: plugin }
  } else {
    plugin = { ...plugin }
  }

  if (!plugin.src) {
    throw new Error('Invalid plugin. src option is required: ' + JSON.stringify(plugin))
  }

  // Normalize full path to plugin
  plugin.src = normalize(plugin.src)

  // Normalize mode
  if (plugin.ssr) {
    plugin.mode = 'server'
  }
  if (!plugin.mode) {
    const [, mode = 'all'] = plugin.src.match(/\.(server|client)(\.\w+)*$/) || []
    plugin.mode = mode as 'all' | 'client' | 'server'
  }

  return plugin
}

/**
 * Registers a nuxt plugin and to the plugins array.
 *
 * Note: You can use mode or .client and .server modifiers with fileName option
 * to use plugin only in client or server side.
 *
 * Note: By default plugin is prepended to the plugins array. You can use second argument to append (push) instead.
 *
 * @example
 * ```js
 * addPlugin({
 *   src: path.resolve(__dirname, 'templates/foo.js'),
 *   filename: 'foo.server.js' // [optional] only include in server bundle
 * })
 * ```
 */
export interface AddPluginOptions { append?: boolean }
export function addPlugin (_plugin: NuxtPlugin | string, opts: AddPluginOptions = {}) {
  const nuxt = useNuxt()

  // Normalize plugin
  const plugin = normalizePlugin(_plugin)

  // Remove any existing plugin with the same src
  nuxt.options.plugins = nuxt.options.plugins.filter(p => normalizePlugin(p).src !== plugin.src)

  // Prepend to array by default to be before user provided plugins since is usually used by modules
  nuxt.options.plugins[opts.append ? 'push' : 'unshift'](plugin)

  return plugin
}

/**
 * Adds a template and registers as a nuxt plugin.
 */
export function addPluginTemplate (plugin: NuxtPluginTemplate | string, opts: AddPluginOptions = {}): NuxtPlugin {
  const normalizedPlugin: NuxtPlugin = typeof plugin === 'string'
    ? { src: plugin }
    // Update plugin src to template destination
    : { ...plugin, src: addTemplate(plugin).dst }

  return addPlugin(normalizedPlugin, opts)
}
  • addPlugin(pluginOptions, { append? })
  • addPluginTemplate(pluginOptions, { append? })

テンプレート

kit/src/template.ts
import { existsSync } from 'node:fs'
import { basename, parse, resolve } from 'pathe'
import hash from 'hash-sum'
import type { NuxtTemplate } from '@nuxt/schema'
import { useNuxt } from './context'

/**
 * Renders given template using lodash template during build into the project buildDir
 */
export function addTemplate (_template: NuxtTemplate | string) {
  const nuxt = useNuxt()

  // Noprmalize template
  const template = normalizeTemplate(_template)

  // Remove any existing template with the same filename
  nuxt.options.build.templates = nuxt.options.build.templates
    .filter(p => normalizeTemplate(p).filename !== template.filename)

  // Add to templates array
  nuxt.options.build.templates.push(template)

  return template
}

/**
 * Normalize a nuxt template object
 */
export function normalizeTemplate (template: NuxtTemplate | string): NuxtTemplate {
  if (!template) {
    throw new Error('Invalid template: ' + JSON.stringify(template))
  }

  // Normalize
  if (typeof template === 'string') {
    template = { src: template }
  } else {
    template = { ...template }
  }

  // Use src if provided
  if (template.src) {
    if (!existsSync(template.src)) {
      throw new Error('Template not found: ' + template.src)
    }
    if (!template.filename) {
      const srcPath = parse(template.src)
      template.filename = template.fileName ||
        `${basename(srcPath.dir)}.${srcPath.name}.${hash(template.src)}${srcPath.ext}`
    }
  }

  if (!template.src && !template.getContents) {
    throw new Error('Invalid template. Either getContents or src options should be provided: ' + JSON.stringify(template))
  }

  if (!template.filename) {
    throw new Error('Invalid template. Either filename should be provided: ' + JSON.stringify(template))
  }

  // Always write declaration files
  if (template.filename.endsWith('.d.ts')) {
    template.write = true
  }

  // Resolve dst
  if (!template.dst) {
    const nuxt = useNuxt()
    template.dst = resolve(nuxt.options.buildDir, template.filename)
  }

  return template
}
  • addTemplate(templateOptions)
torish14torish14

サーバー

kit/src/server.ts
import type { Middleware } from 'h3'
import { useNuxt } from './context'

export interface ServerMiddleware {
  route?: string,
  handler: Middleware | string
}

/** Adds a new server middleware to the end of the server middleware array. */
export function addServerMiddleware (middleware: ServerMiddleware) {
  useNuxt().options.serverMiddleware.push(middleware)
}
  • addServerMiddleware(serverMiddleware)

Resolving

kit/src/resolve.ts
import { promises as fsp, existsSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { basename, dirname, resolve, join, normalize, isAbsolute } from 'pathe'
import { globby } from 'globby'
import { tryUseNuxt, useNuxt } from './context'
import { tryResolveModule } from './internal/cjs'
import { isIgnored } from './ignore'

export interface ResolvePathOptions {
  /** Base for resolving paths from. Default is Nuxt rootDir. */
  cwd?: string

  /** An object of aliases. Default is Nuxt configured aliases. */
  alias?: Record<string, string>

  /** The file extensions to try. Default is Nuxt configured extensions. */
  extensions?: string[]
}

/**
 * Resolve full path to a file or directory respecting Nuxt alias and extensions options
 *
 * If path could not be resolved, normalized input path will be returned
 */
export async function resolvePath (path: string, opts: ResolvePathOptions = {}): Promise<string> {
  // Always normalize input
  const _path = path
  path = normalize(path)

  // Fast return if the path exists
  if (isAbsolute(path) && existsSync(path)) {
    return path
  }

  // Use current nuxt options
  const nuxt = useNuxt()
  const cwd = opts.cwd || (nuxt ? nuxt.options.rootDir : process.cwd())
  const extensions = opts.extensions || (nuxt ? nuxt.options.extensions : ['.ts', '.mjs', '.cjs', '.json'])
  const modulesDir = nuxt ? nuxt.options.modulesDir : []

  // Resolve aliases
  path = resolveAlias(path)

  // Resolve relative to cwd
  if (!isAbsolute(path)) {
    path = resolve(cwd, path)
  }

  // Check if resolvedPath is a file
  let isDirectory = false
  if (existsSync(path)) {
    isDirectory = (await fsp.lstat(path)).isDirectory()
    if (!isDirectory) {
      return path
    }
  }

  // Check possible extensions
  for (const ext of extensions) {
    // path.[ext]
    const pathWithExt = path + ext
    if (existsSync(pathWithExt)) {
      return pathWithExt
    }
    // path/index.[ext]
    const pathWithIndex = join(path, 'index' + ext)
    if (isDirectory && existsSync(pathWithIndex)) {
      return pathWithIndex
    }
  }

  // Try to resolve as module id
  const resolveModulePath = tryResolveModule(_path, { paths: [cwd, ...modulesDir] })
  if (resolveModulePath) {
    return resolveModulePath
  }

  // Return normalized input
  return path
}

/**
 * Try to resolve first existing file in paths
 */
export async function findPath (paths: string|string[], opts?: ResolvePathOptions, pathType: 'file' | 'dir' = 'file'): Promise<string|null> {
  if (!Array.isArray(paths)) {
    paths = [paths]
  }
  for (const path of paths) {
    const rPath = await resolvePath(path, opts)
    if (await existsSensitive(rPath)) {
      const isDirectory = (await fsp.lstat(rPath)).isDirectory()
      if (!pathType || (pathType === 'file' && !isDirectory) || (pathType === 'dir' && isDirectory)) {
        return rPath
      }
    }
  }
  return null
}

/**
 * Resolve path aliases respecting Nuxt alias options
 */
export function resolveAlias (path: string, alias?: Record<string, string>): string {
  if (!alias) {
    alias = tryUseNuxt()?.options.alias || {}
  }
  for (const key in alias) {
    if (key === '@' && !path.startsWith('@/')) { continue } // Don't resolve @foo/bar
    if (path.startsWith(key)) {
      path = alias[key] + path.slice(key.length)
    }
  }
  return path
}

export interface Resolver {
  resolve(...path): string
  resolvePath(path: string, opts?: ResolvePathOptions): Promise<string>
}

/**
 * Create a relative resolver
 */
export function createResolver (base: string | URL): Resolver {
  if (!base) {
    throw new Error('`base` argument is missing for createResolver(base)!')
  }

  base = base.toString()
  if (base.startsWith('file://')) {
    base = dirname(fileURLToPath(base))
  }

  return {
    resolve: (...path) => resolve(base as string, ...path),
    resolvePath: (path, opts) => resolvePath(path, { cwd: base as string, ...opts })
  }
}

// --- Internal ---

async function existsSensitive (path: string) {
  if (!existsSync(path)) { return false }
  const dirFiles = await fsp.readdir(dirname(path))
  return dirFiles.includes(basename(path))
}

export async function resolveFiles (path: string, pattern: string | string[]) {
  const files = await globby(pattern, { cwd: path, followSymbolicLinks: true })
  return files.filter(p => !isIgnored(p)).map(p => resolve(path, p))
}
  • resolvePath (path, resolveOptions?)
  • resolveAlias (path, aliases?)
  • findPath (paths, resolveOptions?)
  • createResolver (base)

ビルダー

kit/src/build.ts
import type { WebpackPluginInstance, Configuration as WebpackConfig } from 'webpack'
import type { Plugin as VitePlugin, UserConfig as ViteConfig } from 'vite'
import { useNuxt } from './context'

export interface ExtendConfigOptions {
  /**
   * Install plugin on dev
   *
   * @default true
   */
   dev?: boolean
   /**
    * Install plugin on build
    *
    * @default true
    */
   build?: boolean
}

export interface ExtendWebpackConfigOptions extends ExtendConfigOptions {
  /**
   * Install plugin on server side
   *
   * @default true
   */
  server?: boolean
  /**
   * Install plugin on client side
   *
   * @default true
   */
  client?: boolean
  /**
   * Install plugin on modern build
   *
   * @default true
   * @deprecated Nuxt 2 only
   */
  modern?: boolean
}

export interface ExtendViteConfigOptions extends ExtendConfigOptions {}

/**
 * Extend Webpack config
 *
 * The fallback function might be called multiple times
 * when applying to both client and server builds.
 */
export function extendWebpackConfig (
  fn: ((config: WebpackConfig)=> void),
  options: ExtendWebpackConfigOptions = {}
) {
  const nuxt = useNuxt()

  if (options.dev === false && nuxt.options.dev) {
    return
  }
  if (options.build === false && nuxt.options.build) {
    return
  }

  nuxt.hook('webpack:config', (configs: WebpackConfig[]) => {
    if (options.server !== false) {
      const config = configs.find(i => i.name === 'server')
      if (config) {
        fn(config)
      }
    }
    if (options.client !== false) {
      const config = configs.find(i => i.name === 'client')
      if (config) {
        fn(config)
      }
    }
    // Nuxt 2 backwards compatibility
    if (options.modern !== false) {
      const config = configs.find(i => i.name === 'modern')
      if (config) {
        fn(config)
      }
    }
  })
}

/**
 * Extend Vite config
 */
export function extendViteConfig (
  fn: ((config: ViteConfig) => void),
  options: ExtendViteConfigOptions = {}
) {
  const nuxt = useNuxt()

  if (options.dev === false && nuxt.options.dev) {
    return
  }
  if (options.build === false && nuxt.options.build) {
    return
  }

  nuxt.hook('vite:extend', ({ config }) => fn(config))
}

/**
 * Append Webpack plugin to the config.
 */
export function addWebpackPlugin (plugin: WebpackPluginInstance, options?: ExtendWebpackConfigOptions) {
  extendWebpackConfig((config) => {
    config.plugins = config.plugins || []
    config.plugins.push(plugin)
  }, options)
}

/**
 * Append Vite plugin to the config.
 */
export function addVitePlugin (plugin: VitePlugin, options?: ExtendViteConfigOptions) {
  extendViteConfig((config) => {
    config.plugins = config.plugins || []
    config.plugins.push(plugin)
  }, options)
}
  • extendWebpackConfig(callback, options?)
  • extendViteConfig(callback, options?)
  • addWebpackPlugin(webpackPlugin, options?)
  • addVitePlugin(vitePlugin, options?)
torish14torish14

nuxi add

Nuxtアプリケーションにエンティティを足場として組み込むことができます。

npx nuxi add [--cwd] [--force] <TEMPLATE> <NAME>
Option デフォルト 説明
TEMPLATE - 生成されるファイルのテンプレートを指定します。
NAME - 生成されるファイルの名前を指定します。
--cwd . 対象アプリケーションのディレクトリです。
--force false ファイルがすでに存在する場合、強制的に上書きします。

npx nuxi add component TheHeader

add コマンドは、新しい要素を生成します。

  • component: npx nuxi add component TheHeader
  • composable: npx nuxi add composable foo
  • レイアウト: npx nuxi add layout custom
  • プラグイン: npx nuxi add plugin analytics
  • ページ: npx nuxi add page about または npx nuxi add page "category/[id]"
  • ミドルウェア: npx nuxi add middleware auth
  • api: npx nuxi add api hello (CLIで/server/api以下にファイルが生成されます)

nuxi analyze

``

nuxi build

``

nuxi dev

``

nuxi info

``

nuxi init

``

nuxi preview

``

nuxi typecheck

``

nuxi upgrade

``

torish14torish14

Hello World

最小限の Nuxt 3 アプリケーションは、app.vue と nuxt.config.js のファイルだけで済みます。

app.vue
<script setup>
const version = 2 + 1
</script>

<template>
  <div class="hello">
    Hello Nuxt {{ version }}!
  </div>
</template>

<style scoped>
.hello {
  font-family: Arial, Helvetica, sans-serif;
  font-size: 3rem;
  padding: 10rem;
}
</style>

エラーハンドリング

この例では、pages、プラグイン、コンポーネント、ミドルウェアという異なるコンテキストでのエラー処理方法を示しています。

app.vue
<script setup lang="ts">
import { throwError } from '#app'
const route = useRoute()
if ('setup' in route.query) {
  throw new Error('error in setup')
}
if ('mounted' in route.query) {
  onMounted(() => {
    throw new Error('error in mounted')
  })
}
function triggerError () {
  throw new Error('manually triggered error')
}
</script>

<template>
  <NuxtExampleLayout example="app/error-handling">
    <template #nav>
      <nav class="flex align-center gap-4 p-4">
        <NuxtLink to="/" class="n-link-base">
          Home
        </NuxtLink>
        <NuxtLink to="/other" class="n-link-base">
          Other
        </NuxtLink>
        <NuxtLink to="/404" class="n-link-base">
          404
        </NuxtLink>
        <NuxtLink to="/?middleware" class="n-link-base">
          Middleware
        </NuxtLink>
        <button class="n-link-base" @click="throwError">
          Trigger fatal error
        </button>
        <button class="n-link-base" @click="triggerError">
          Trigger non-fatal error
        </button>
      </nav>
    </template>

    <template #footer>
      <div class="text-center p-4 op-50">
        Current route: <code>{{ route.path }}</code>
      </div>
    </template>
  </NuxtExampleLayout>
</template>
error.vue
<template>
  <div class="relative font-sans" n="green6">
    <div class="container max-w-200 mx-auto py-10 px-4">
      <h1>{{ error.message }}</h1>
      There was an error 😱

      <br>
      <button @click="handleError">
        Clear error
      </button>
      <br>
      <NuxtLink to="/404">
        Trigger another error
      </NuxtLink>
      <br>
      <NuxtLink to="/">
        Navigate home
      </NuxtLink>
    </div>
  </div>
</template>

<script setup lang="ts">
defineProps({
  error: Object
})

const handleError = () => clearError({ redirect: '/' })
</script>
components/FaultyComponent.vue
<script setup>
const hasIssue = ref(true)

const fixIssue = (error) => {
  hasIssue.value = false
  error.value = null
}

</script>

<template>
  <NuxtErrorBoundary>
    <throw-error v-if="hasIssue" />
    <div v-else>
      Component is working ^_^
    </div>

    <template #error="{ error }">
      Component failed to Render -_-
      <button @click="fixIssue(error)">
        (fix the issue)
      </button>
    </template>
  </NuxtErrorBoundary>
</template>
components/ThrowError.vue
<script setup>
throw new Error('Deliberate error by <ThrowError>')
</script>

<template>
  <div>Should never see this</div>
</template>
middleware/error.global.ts
export default defineNuxtRouteMiddleware((to) => {
  if ('middleware' in to.query) {
    return throwError('error in middleware')
  }
})
plugins/error.ts
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hook('vue:error', (..._args) => {
    console.log('vue:error')
    // if (process.client) {
    //   console.log(..._args)
    // }
  })
  nuxtApp.hook('app:error', (..._args) => {
    console.log('app:error')
    // if (process.client) {
    //   console.log(..._args)
    // }
  })
  nuxtApp.vueApp.config.errorHandler = (..._args) => {
    console.log('global error handler')
    // if (process.client) {
    //   console.log(..._args)
    // }
  }
})
server/middleware/error.ts
import { useQuery, defineMiddleware } from 'h3'

export default defineMiddleware((req, res, next) => {
  if ('api' in useQuery(req)) {
    throw new Error('Server middleware error')
  }
  next()
})

プラグイン

この例では、plugins/ ディレクトリを使用して、プラグインを自動登録する方法を示します。

app.vue
<template>
  <NuxtExampleLayout example="app/plugins">
    <div>{{ $myPlugin() }}</div>
  </NuxtExampleLayout>
</template>
plugins/my-plugin.ts
export default defineNuxtPlugin((/* nuxtApp */) => {
  return {
    provide: {
      myPlugin: () => 'String generated from my auto-imported plugin!'
    }
  }
})

テレポート

Vue 3 には、<Teleport> コンポーネントがあり、Vue アプリケーションの外、DOM の別の場所にコンテンツをレンダリングします。

この例では、クライアントサイドおよびサーバーサイドのレンダリングで、<Teleport> を使用する方法を示します。

app.vue
<template>
  <NuxtExampleLayout example="app/teleport">
    <div>
      <!-- SSR Teleport -->
      <Teleport to="body">
        SSR Teleport
      </Teleport>

      <!-- Client Teleport -->
      <ClientOnly>
        <Teleport to="body">
          <div>
            Hello from a client-side teleport!
          </div>
        </Teleport>
      </ClientOnly>

      <!-- Modal Example -->
      <MyModal />
    </div>
  </NuxtExampleLayout>
</template>
components/MyModal.vue
<script>
export default {
  data () {
    return {
      open: false
    }
  }
}
</script>

<template>
  <NButton @click="open = true">
    Open Modal
  </NButton>
  <Teleport to="body">
    <NCard v-if="open" class="modal p4">
      <p>Hello from the modal!</p>
      <NButton @click="open = false">
        Close
      </NButton>
    </NCard>
  </Teleport>
</template>

<style scoped>
.modal {
  position: fixed;
  z-index: 999;
  top: 20%;
  left: 50%;
  width: 300px;
  margin-left: -150px;
}
</style>

コンポーネント

components/ ディレクトリにあるコンポーネントは自動的にインポートされ、テンプレートで自動的に使用することができます。他のディレクトリを設定して、コンポーネントの自動インポートをサポートすることができます。

app.vue
<template>
  <NuxtExampleLayout example="auto-imports/components">
    <h1 class="text-xl opacity-50">
      Auto Imported Components
    </h1>
    <div>
      <!-- Auto Imported Components -->
      <HelloWorld class="text-2xl" />
      <Nuxt3 class="text-2xl" />
      <ParentFolderHello class="mt-6" />
      <NuxtWithPrefix class="mt-6" />
    </div>
  </NuxtExampleLayout>
</template>
components/HelloWorld.vue
<template>
  <div>
    This is HelloWorld component!
  </div>
</template>
components/Nuxt3.vue
<template>
  <b style="color: #00C58E">
    From Nuxt 3
  </b>
</template>
components/parent-folder/Hello.vue
<template>
  <NCard class="flex flex-col gap-1 p-4">
    Components in sub folders
    <code class="op50">`components/parent-folder/hello.vue`</code>
    can be auto imported with folder name as the prefix:
    <code class="text-lime6">&lt;ParentFolderHello/&gt;</code>
  </NCard>
</template>
other-components-folder/with-prefix.vue
<template>
  <NCard class="flex flex-col gap-1 p-4">
    <code>nuxt.config.ts</code> can specify other directories like
    <code class="op50">`other-components-folder/`</code>
    to import components from and specify prefixes.
  </NCard>
</template>

コンポーザブル

この例は、composables/ ディレクトリを使用して composables を自動インポートする方法を示しています。コンポーネントファイルがデフォルトのエクスポートを提供する場合、コンポーザブルの名前はファイルの名前にマップされます。名前付きエクスポートはそのまま使用できます。

app.vue
<template>
  <NuxtExampleLayout example="auto-imports/composables">
    <p>Named export <code>useA</code> : {{ a }}</p>
    <p>Named export <code>useB</code> : {{ b }}</p>
    <p>Named export <code>useC</code> : {{ c }}</p>
    <p>Named export <code>useD</code> : {{ d }}</p>
    <p>Default export <code>useFoo</code> : {{ foo }}</p>
  </NuxtExampleLayout>
</template>

<script setup>
const a = useA()
const b = useB()
const c = useC()
const d = useD()
const foo = useFoo()
</script>
composables/use-foo.ts
import { useState } from '#app'

export function useA () {
  return 'a'
}

function useB () {
  return 'b'
}

function _useC () {
  return 'c'
}

export const useD = () => {
  return 'd'
}

export { useB, _useC as useC }

export default function () {
  return useState('foo', () => 'bar')
}
torish14torish14

useAsyncData

この例は、useAsyncData を使用して API エンドポイントからデータをフェッチする方法を示しています。

app.vue
<script setup>
const showMountain = ref(false)

const refreshing = ref(false)
const refreshAll = async () => {
  refreshing.value = true
  try {
    await refreshNuxtData()
  } finally {
    refreshing.value = false
  }
}
</script>

<template>
  <NuxtExampleLayout example="composables/use-async-data">
    <div>
      <div class="flex justify-center gap-2">
        <NButton @click="showMountain = !showMountain">
          {{ showMountain ? 'Hide' : 'Show' }} Mountain
        </NButton>
        <NButton :disabled="refreshing" @click="refreshAll">
          Refetch All Data
        </NButton>
      </div>

      <div class="flex justify-center gap-2">
        <CounterExample />
      </div>
      <div class="flex justify-center gap-2">
        <MountainExample v-if="showMountain" />
      </div>
    </div>
  </NuxtExampleLayout>
</template>
components/CounterExample.vue
<script setup>
const ctr = ref(0)
const { data, pending, refresh } = await useAsyncData('/api/hello', () => $fetch(`/api/hello/${ctr.value}`), { watch: [ctr] })

</script>

<template>
  <div>
    {{ data }}
    <div class="flex justify-center gap-2">
      <NButton :disabled="pending" @click="ctr++">
        +
      </NButton>
      <NButton :disabled="pending" @click="refresh"></NButton>
    </div>
  </div>
</template>
components/MountainExample.vue
<script setup>
const { data: mountain } = await useFetch(
  'https://api.nuxtjs.dev/mountains/mount-everest'
)
</script>

<template>
  <pre class="text-sm text-left overflow-auto">{{ mountain }}</pre>
</template>
server/api/hello.js
export default req => `Hello world (${req.url.substr(1)}) (Generated at ${new Date().toGMTString()})`

useCookie

この例は、useCookie API を使用して、クライアントとサーバーの両方が使用できる少量のデータを永続化する方法を示しています。

app.vue
<script setup lang="ts">
const user = useCookie<{ name: string }>('user')
const logins = useCookie<number>('logins')

const name = ref('')

const login = () => {
  logins.value = (logins.value || 0) + 1
  user.value = { name: name.value }
}

const logout = () => {
  user.value = null
}
</script>

<template>
  <NuxtExampleLayout class="h-50" example="composables/use-cookie">
    <template v-if="user">
      <h1 class="text-3xl mb-3">
        Welcome, {{ user.name }}! 👋
      </h1>
      <div>
        <NTip n="green6" icon="carbon:idea" class="inline-flex">
          You have logged in <b>{{ logins }} times</b>!
        </NTip>
      </div>
      <div class="mt-3">
        <NButton n="red" icon="carbon:logout" @click="logout">
          Log out
        </NButton>
      </div>
    </template>
    <template v-else>
      <h1 class="text-3xl mb-3">
        Login
      </h1>
      <NTextInput v-model="name" n="lg" class="w-100 m-auto" placeholder="Enter your name..." @keypress.enter="login()" />
      <div class="mt-3">
        <NButton icon="carbon:user" :disabled="!name" @click="login">
          Log in
        </NButton>
      </div>
    </template>
  </NuxtExampleLayout>
</template>

useFetch

この例は、useFetch を使用して API エンドポイントからデータをフェッチする方法を示しています。

app.vue
<script setup>
const count = ref(1)
const { data } = await useFetch(() => `/api/hello/${count.value}`, { params: { token: 123 } })
</script>

<template>
  <NuxtExampleLayout example="composables/use-fetch">
    <div>
      Fetch result:
      <pre class="text-left"><code>{{ data }}</code></pre>
      <NButton @click="count++">
        +
      </NButton>
    </div>
  </NuxtExampleLayout>
</template>
server/api/hello.ts
import { useQuery } from 'h3'
import { parseURL } from 'ufo'

export default req => ({
  path: '/api/hello' + parseURL(req.url).pathname,
  query: useQuery(req)
})

useHead

この例は、useHead および Nuxt の組み込みコンポーネントを使用して、メタデータをページの先頭にバインドする方法を示しています。

app.vue
<template>
  <NuxtExampleLayout example="composables/use-head">
    <div
      class="bg-gray-400/10 border-2 border-dashed border-gray-400/50 rounded-xl py-8 px-2 op-80"
    >
      There are renderless <code>&lt;Html&gt;</code>, <code>&lt;Meta&gt;</code>, <code>&lt;Title&gt;</code> components
      <br>that can magically bind the meta using Vue template.
    </div>

    <Html :lang="String(dynamic)">
      <Head>
        <Title>Luck number: {{ dynamic }}</Title>
        <Meta name="description" :content="`My page's ${dynamic} description`" />
        <Link rel="preload" href="/test.txt" as="script" />
      </Head>
    </Html>

    <div class="mt-5">
      <NButton @click="dynamic = Math.round(Math.random() * 100)">
        Click me and see the dynamic title
      </NButton>
    </div>
  </NuxtExampleLayout>
</template>

<script>
export default {
  setup () {
    useHead({
      titleTemplate: '%s - useHead example',
      bodyAttrs: {
        class: 'test'
      }
    })
    return { dynamic: ref(49) }
  },
  head: {
    title: 'Another title'
  }
}
</script>

useState

useState は、SSR に適した ref の代替品です。その値はサーバー側のレンダリング後に保持され、一意のキーを使用してすべてのコンポーネント間で共有されます。

app.vue
<script setup>
const counter = useState('counter', () => Math.round(Math.random() * 1000))
</script>

<template>
  <NuxtExampleLayout example="composables/use-state">
    <div>Counter: {{ counter }}</div>
    <div>
      <NButton class="font-mono" @click="counter++">
        +
      </NButton>
      <NButton class="font-mono" @click="counter--">
        -
      </NButton>
    </div>
  </NuxtExampleLayout>
</template>

レイアウト

この例は、デフォルトレイアウトとカスタムレイアウトを定義する方法を示しています。

pages/custom.vue
<template>
  <div class="p-4">
    Custom layout defined with <code>definePageMeta</code>
    <br>
    <NuxtLink to="/">
      Back to home
    </NuxtLink>
  </div>
</template>

<script>
definePageMeta({
  layout: 'custom'
})
</script>
pages/default.vue
<template>
  <div>
    <p>Content inside <code>default</code> layout</p>
    <br>
    <NuxtLink to="/">
      Back to home
    </NuxtLink>
  </div>
</template>
pages/dynamic.vue
<template>
  <div class="p-4">
    Custom layout defined dynamically with the <code>NuxtLayout</code> component
    <br>
    <NuxtLayout :name="layout">
      Default slot
      <br>
      <button class="border p-1 rounded" @click="layout ? layout = null : layout = 'custom'">
        Switch layout
      </button>

      <template #header>
        Header slot
      </template>
    </NuxtLayout>
    <br>
    <NuxtLink to="/">
      Back to home
    </NuxtLink>
  </div>
</template>

<script>
definePageMeta({
  layout: false
})
export default {
  data: () => ({
    layout: 'custom'
  })
}
</script>
pages/index.vue
<template>
  <NuxtExampleLayout example="routing/layouts">
    <template #nav>
      <nav class="flex align-center gap-4 p-4">
        <NuxtLink to="/default">
          Default layout
        </NuxtLink>
        <NuxtLink to="/custom">
          Custom layout
        </NuxtLink>
        <NuxtLink to="/dynamic">
          Dynamic layout
        </NuxtLink>
      </nav>
    </template>
  </NuxtExampleLayout>
</template>
layouts/custom.vue
<template>
  <div>
    <strong>Custom layout</strong>
    Header slot:
    <slot name="header">
      Default slot content for <code>Custom</code> layout
    </slot>
    <slot />
  </div>
</template>

<script>
export default {
  created () {
    console.log('layout created')
  }
}
</script>
layouts/default.vue
<template>
  <div>
    Default layout
    <slot />
  </div>
</template>
layouts/other.vue
<template>
  <div>
    Other layout
    <slot />
  </div>
</template>

ミドルウェア

この例は、middleware/ ディレクトリまたはプラグインを使用してルートミドルウェアを追加する方法と、それらをグローバルにまたはページごとに使用する方法を示しています。

app.vue
<script setup lang="ts">
const route = useRoute()
</script>

<template>
  <NuxtExampleLayout example="routing/middleware">
    <NuxtPage />

    <template #nav>
      <nav class="flex align-center gap-4 p-4">
        <NuxtLink to="/" class="n-link-base">
          Home
        </NuxtLink>
        <NuxtLink to="/forbidden" class="n-link-base">
          Forbidden
        </NuxtLink>
        <NuxtLink to="/redirect" class="n-link-base">
          Redirect
        </NuxtLink>
      </nav>
    </template>

    <template #footer>
      <div class="text-center p-4 op-50">
        Current route: <code>{{ route.path }}</code>
      </div>
    </template>
  </NuxtExampleLayout>
</template>
pages/forbidden.vue
<template>
  <div>
    Forbidden
  </div>
</template>

<script setup>
definePageMeta({
  // This is an example of inline middleware
  middleware: () => {
    console.log('Strictly forbidden.')
    return false
  }
})
</script>
pages/index.vue
<template>
  <div>
    Home
  </div>
</template>
pages/redirect.vue
<template>
  <div>
    You should never see this page
  </div>
</template>

<script setup>

definePageMeta({
  middleware: 'redirect-me'
})
</script>
pages/secret.vue
<template>
  <div>
    You've landed on a page that wasn't in the menu!
  </div>
</template>

<script setup>
definePageMeta({
  middleware: 'named-test'
})
</script>
middleware/always-run.global.ts
export default defineNuxtRouteMiddleware(() => {
  console.log('running global middleware')
})
middleware/redirect-me.ts
export default defineNuxtRouteMiddleware((to) => {
  const { $config } = useNuxtApp()
  if ($config) {
    console.log('Accessed runtime config within middleware.')
  }
  console.log('Heading to', to.path, 'but I think we should go somewhere else...')
  return '/secret'
})
plugins/add.ts
export default defineNuxtPlugin(() => {
  addRouteMiddleware('global-test', () => {
    console.log('this global middleware was added in a plugin')
  }, { global: true })

  addRouteMiddleware('named-test', () => {
    console.log('this named middleware was added in a plugin')
  })
})

<NuxtLink>

この例は、<NuxtLink> を使用するさまざまな方法を示しています。

app.vue
<template>
  <NuxtExampleLayout example="routing/nuxt-link" class="example">
    <NuxtPage />
  </NuxtExampleLayout>
</template>

<style>
.example a {
  font-family: Arial, Helvetica, sans-serif;
  padding: 1rem 10rem;
  display: block;
}
</style>
pages/about.vue
<template>
  <NuxtLink to="/">
    Index page
  </NuxtLink>
</template>
pages/index.vue
<template>
  <div>
    <NuxtLink to="/about">
      About page
    </NuxtLink>
    <NuxtLink to="https://nuxtjs.org">
      Nuxt website
    </NuxtLink>
    <NuxtLink to="https://twitter.com/nuxt_js" target="_blank">
      Nuxt Twitter with a blank target
    </NuxtLink>
    <NuxtLink to="https://discord.nuxtjs.org" target="_blank" rel="noopener">
      Nuxt Discord with a blank target and custom rel value
    </NuxtLink>
    <NuxtLink to="https://github.com/nuxt" no-rel>
      Nuxt GitHub without rel attribute
    </NuxtLink>
    <MyNuxtLink to="https://nuxtjs.org">
      Nuxt website with a custom link component with no default rel attribute
    </MyNuxtLink>
    <MyNuxtLink to="/">
      Index page with a custom link component with a custom active class
    </MyNuxtLink>
    <NuxtLink>Link without href and to</NuxtLink>
  </div>
</template>
components/MyNuxtLink.js
export default defineNuxtLink({
  componentName: 'MyNuxtLink',
  externalRelAttribute: '',
  activeClass: 'active',
  exactActiveClass: 'exact-active'
})

Pages

この例は、pages/ ディレクトリを使用してアプリケーションルートを作成する方法を示しています。

app.vue
<script setup lang="ts">
const route = useRoute()
</script>

<template>
  <NuxtExampleLayout example="routing/pages">
    <NuxtPage />

    <template #nav>
      <nav class="flex align-center gap-4 p-4">
        <NuxtLink to="/" class="n-link-base">
          Home
        </NuxtLink>
        <NuxtLink to="/about" class="n-link-base">
          About
        </NuxtLink>
        <NuxtLink to="/parent" class="n-link-base">
          Parent (index)
        </NuxtLink>
        <NuxtLink to="/parent/b" class="n-link-base">
          Parent (b)
        </NuxtLink>
        <button class="n-link-base" @click="$router.push(`/parent/reload-${(Math.random() * 100).toFixed()}`)">
          Keyed child
        </button>
        <button class="n-link-base" @click="$router.push(`/parent/static-${(Math.random() * 100).toFixed()}`)">
          Non-keyed child
        </button>
      </nav>
    </template>

    <template #footer>
      <div class="text-center p-4 op-50">
        Current route: <code>{{ route.path }}</code>
      </div>
    </template>
  </NuxtExampleLayout>
</template>
pages/catchall/[...id].vue
<template>
  <div>
    test-{{ $route.params.id }}
  </div>
</template>
pages/parent/b.vue
<template>
  <div>
    Parent/b
  </div>
</template>
pages/parent/index.vue
<template>
  <div>
    Parent/index
  </div>
</template>
pages/parent/reload-[id].vue
<template>
  <div>
    Child reloaded: {{ reloads }}
  </div>
</template>

<script setup>
const reloads = useState('reload', () => 0)
onMounted(() => { reloads.value++ })
</script>
pages/parent/static-[id].vue
<template>
  <div>
    Child reloaded: {{ reloads }}
  </div>
</template>

<script setup>
const reloads = useState('static', () => 0)
onMounted(() => { reloads.value++ })
definePageMeta({
  key: 'static'
})
</script>
pages/about.vue
<template>
  <div>
    About
  </div>
</template>
pages/index.vue
<template>
  <div>
    Home
  </div>
</template>
pages/parent.vue
<template>
  <div>
    Parent
    <NuxtPage />
  </div>
</template>

ユニバーサルルーター

この例は、pages/ および vue-router に依存しない Nuxt ユニバーサルルーティングユーティリティを示しています。

app.vue
<script setup lang="ts">
const route = useRoute()
const timer = useState('timer', () => 0)
</script>

<template>
  <NuxtExampleLayout example="routing/universal-router">
    A page...
    <br>

    <template v-if="timer">
      Processing navigation in 0.{{ timer }}s
    </template>

    <template #nav>
      <nav class="flex align-center gap-4 p-4">
        <NuxtLink to="/" class="n-link-base">
          Home
        </NuxtLink>
        <NuxtLink to="/forbidden" class="n-link-base">
          Forbidden
        </NuxtLink>
        <NuxtLink to="/redirect" class="n-link-base">
          Redirect
        </NuxtLink>
      </nav>
    </template>

    <template #footer>
      <div class="text-center p-4 op-50">
        Current route: <code>{{ route.path }}</code>
      </div>
    </template>
  </NuxtExampleLayout>
</template>
plugins/add.ts
export default defineNuxtPlugin(() => {
  const timer = useState('timer', () => 0)

  if (process.client) {
    addRouteMiddleware(async () => {
      console.log('Starting timer...')
      timer.value = 5
      do {
        await new Promise(resolve => setTimeout(resolve, 100))
        timer.value--
      } while (timer.value)
      console.log('...and navigating')
    })
  }

  addRouteMiddleware((to) => {
    if (to.path === '/forbidden') {
      return false
    }
  })

  addRouteMiddleware((to) => {
    const { $config } = useNuxtApp()
    if ($config) {
      console.log('Accessed runtime config within middleware.')
    }

    if (to.path !== '/redirect') { return }

    console.log('Heading to', to.path, 'but I think we should go somewhere else...')
    return '/secret'
  })
})
torish14torish14

サーバールーター

この例は、server/api ディレクトリ内にサーバールートを作成する方法を示しています。

app.vue
<template>
  <NuxtExampleLayout example="server/routes">
    <p>Fetched from <pre>/api/mountain</pre>: {{ mountain }}</p>
  </NuxtExampleLayout>
</template>

<script setup>
const { data: mountain } = await useFetch('/api/mountain')
</script>
server/api/mountain.js
export default defineEventHandler(() => {
  return {
    title: 'Mount Everest',
    description: "Mount Everest is Earth's highest mountain above sea level, located in the Mahalangur Himal sub-range of the Himalayas. The China–Nepal border runs across its summit point",
    height: '8,848 m',
    countries: [
      'China',
      'Nepal'
    ],
    continent: 'Asia',
    image: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/600px-Everest_kalapatthar.jpg',
    dir: '/mountains',
    path: '/mountains/mount-everest',
    slug: 'mount-everest',
    updatedAt: '2020-12-11T15:40:35.000Z'
  }
})

設定の拡張

この例は、nuxt.config.ts の extends キーを使用して base/ ディレクトリを Nuxt アプリケーションのベースとして使用し、そのコンポーネント、コンポーザブル、または config を使用して、必要に応じてそれらをオーバーライド(再定義)する方法を示しています。

nuxt.config.ts
import { defineNuxtConfig } from 'nuxt'

export default defineNuxtConfig({
  extends: [
    './ui',
    './base'
  ],
  publicRuntimeConfig: {
    theme: {
      primaryColor: 'user_primary'
    }
  },
  modules: [
    '@nuxt/ui'
  ]
})
components/FancyButton.vue
<template>
  <BaseButton class="fancy-button">
    <slot />
  </BaseButton>
</template>

<style scoped>
button {
  appearance: none;
  background-color: #FAFBFC;
  border: 1px solid rgba(27, 31, 35, 0.15);
  border-radius: 6px;
  box-shadow: rgba(27, 31, 35, 0.04) 0 1px 0, rgba(255, 255, 255, 0.25) 0 1px 0 inset;
  color: #24292E;
}
</style>
pages/index.vue
<script setup>
const themeConfig = useRuntimeConfig().theme
const foo = useFoo()
const bar = getBar()
</script>

<template>
  <NuxtExampleLayout example="advanced/config-extends">
    theme runtimeConfig
    <pre>{{ JSON.stringify(themeConfig, null, 2) }}</pre>
    <BaseButton>Base Button</BaseButton>
    <FancyButton>Fancy Button</FancyButton>
    <UIButton>UI Button</UIButton>
    <br>
    {{ foo }} {{ bar }}
    <br>
    {{ $myPlugin() }}
  </NuxtExampleLayout>
</template>

<style scoped>
pre {
  text-align: left;
}
</style>
server/api/hello.ts
export default () => 'hello'
base/nuxt.config.ts
import { defineNuxtConfig } from 'nuxt'

export default defineNuxtConfig({
  autoImports: {
    dirs: ['utils']
  },
  publicRuntimeConfig: {
    theme: {
      primaryColor: 'base_primary',
      secondaryColor: 'base_secondary'
    }
  }
})
base/components/BaseButton.vue
<template>
  <button role="button">
    <slot />
  </button>
</template>
base/components/FancyButton.vue
<template>
  <BaseButton>
    <slot />
  </BaseButton>
</template>
base/composables/foo.ts
import { useState } from '#app'

export const useFoo = () => useState('foo', () => 'foo')
base/middleware/foo.ts
export default defineNuxtRouteMiddleware(() => {
  console.log('Hello from extended middleware !')
})
base/pages/foo.vue
<template>
  <div>
    Hello from extended page !
  </div>
</template>

<script setup>
definePageMeta({
  middleware: 'foo'
})
</script>
base/plugins/my-plugin.ts
export default defineNuxtPlugin((/* nuxtApp */) => {
  return {
    provide: {
      myPlugin: () => 'String generated from my auto-imported plugin!'
    }
  }
})
base/server/api/base.ts
export default () => 'base'
base/utils/bar.ts
export const getBar = () => 'bar'
ui/nuxt.config.ts
import { defineNuxtConfig } from 'nuxt'

export default defineNuxtConfig({
  components: [
    { path: './components', prefix: 'UI' }
  ]
})
ui/components/Button.vue
<script setup>
defineProps({
  color: {
    type: String,
    default: 'black'
  }
})
</script>

<template>
  <button class="ui-button" :style="{ color }">
    <slot />
  </button>
</template>

モジュール拡張 pages

この例では、モジュール内で extendPages を使用して新しいテストページを定義します。

nuxt.config.ts
import { defineNuxtConfig } from 'nuxt'

export default defineNuxtConfig({
  modules: [
    '~/modules/pages',
    '@nuxt/ui'
  ]
})
pages/index.vue
<template>
  <div>
    Go to <NuxtLink to="/test">
      Test Page
    </NuxtLink>
  </div>
</template>
layouts/default.vue
<template>
  <NuxtExampleLayout example="advanced/module-extend-pages">
    <slot />
  </NuxtExampleLayout>
</template>
modules/pages/index.ts
import { defineNuxtModule, extendPages } from '@nuxt/kit'
import { resolve } from 'pathe'

export default defineNuxtModule({
  setup () {
    extendPages((pages) => {
      // Add /test page
      pages.push({
        name: 'Test',
        path: '/test',
        file: resolve(__dirname, './pages/test.vue')
      })
    })
  }
})
modules/pages/pages/test.vue
<template>
  <div>
    <p>
      Go to <NuxtLink to="/">
        Homepage
      </NuxtLink>
    </p>
    <p>Test page added by module</p>
  </div>
</template>

テスト

app.vue
<script setup>
</script>

<template>
  <div class="hello">
    Hello Nuxt!
  </div>
</template>

<style scoped>
.hello {
  font-family: Arial, Helvetica, sans-serif;
  font-size: 3rem;
  padding: 10rem;
}
</style>
tests/basic-test.ts
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import { setup, $fetch, isDev } from '@nuxt/test-utils'

describe('example', async () => {
  await setup({
    rootDir: fileURLToPath(new URL('..', import.meta.url)),
    server: true
  })

  it('Renders Hello Nuxt', async () => {
    expect(await $fetch('/')).toMatch('Hello Nuxt!')
  })

  if (isDev()) {
    it('[dev] ensure vite client script is added', async () => {
      expect(await $fetch('/')).toMatch('/_nuxt/@vite/client"')
    })
  }
})

リアクティビティトランスフォーム

この例は、Nuxt3 でのリアクティビティトランスフォーム(コンパイラーマクロによってコードを変換する仕組み)のサポートを示しています。

nuxt.confit.ts
import { defineNuxtConfig } from 'nuxt'

export default defineNuxtConfig({
  modules: [
    '@nuxt/ui'
  ],
  experimental: {
    reactivityTransform: true
  }
  // builder: 'webpack'
})
app.vue
<script setup lang="ts">
let count = $ref(0)

function inc () {
  count++
}
function dec () {
  count--
}
</script>

<template>
  <NuxtExampleLayout example="experimental/reactivity-transform">
    <div>
      <Label :count="count" />
      <div class="flex gap-1 justify-center">
        <NButton @click="inc()">
          Inc
        </NButton>
        <NButton @click="dec()">
          Dec
        </NButton>
      </div>
    </div>
  </NuxtExampleLayout>
</template>
components/label.vue
<script setup lang="ts">
const { count } = defineProps<{
  count: number,
}>()
const doubled = $computed(() => count * 2)
</script>

<template>
  <div class="pb2">
    Count <b>{{ count }}</b><br>
    Doubled <b>{{ doubled }}</b>
  </div>
</template>

Wasm

この例は、Nuxt3 での WebAssembly のサーバー側サポートを示しています。

nuxt.config.ts
import { defineNuxtConfig } from 'nuxt'

export default defineNuxtConfig({
  nitro: {
    experiments: {
      wasm: true
    }
  },
  modules: [
    '@nuxt/ui'
  ]
})
app.vue
<script setup>
const a = ref(100)
const b = ref(250)

const { data } = await useAsyncData('sum',
  () => $fetch('/api/sum', { params: { a: a.value, b: b.value } })
)
</script>

<template>
  <NuxtExampleLayout example="experimental/wasm">
    <p>
      <code>a = 100</code>
    </p>
    <p>
      <code>b = 250</code>
    </p>
    <p>
      Computation performed server-side with WASM :
      <br>
      <code>{{ a }} + {{ b }} = {{ data.sum }}</code>
    </p>
  </NuxtExampleLayout>
</template>
sever/api/sum.ts
import { useQuery, lazyHandle } from 'h3'

export default lazyHandle(async () => {
  const { exports: { sum } } = await loadWasmInstance(
    // @ts-ignore
    () => import('~/server/wasm/sum.wasm')
  )

  return (req) => {
    const { a = 0, b = 0 } = useQuery(req)
    return { sum: sum(a, b) }
  }
})

async function loadWasmInstance (importFn, imports = {}) {
  const init = await importFn().then(m => m.default || m)
  const { instance } = await init(imports)
  return instance
}
server/wasm/sum.wasm
asm`sum
	  jname
server/wasm/sum.was
;; https://developer.mozilla.org/en-US/docs/WebAssembly/Understanding_the_text_format
;; https://webassembly.github.io/wabt/demo/wat2wasm/
(module
  (func (export "sum") (param i32 i32) (result i32)
    local.get 0
    local.get 1
    i32.add))

Vite-node

nuxt.config.ts
import { defineNuxtConfig } from 'nuxt'

export default defineNuxtConfig({
  modules: [
    '@nuxt/ui'
  ],
  experimental: {
    viteNode: true
  }
})
app.vue
<script setup lang="ts">
const count = ref(0)

function inc () {
  count.value++
}
function dec () {
  count.value--
}
</script>

<template>
  <NuxtExampleLayout example="experimental/vite-node">
    <div>
      {{ count }}
      <div class="flex gap-1 justify-center">
        <NButton @click="inc()">
          Inc
        </NButton>
        <NButton @click="dec()">
          Dec
        </NButton>
      </div>
    </div>
  </NuxtExampleLayout>
</template>

Locale

この例は、サーバー側とクライアント側の両方で、アプリケーションの Locale を処理するために構成可能な Locale を定義する方法を示しています。

app.vue
<script setup lang="ts">
// see ../compoables/locale.ts for the implementation
const locales = useLocales()
const locale = useLocale()
const date = useLocaleDate(new Date('2016-10-26') /* NUXT_BIRTHDAY */)
</script>

<template>
  <NuxtExampleLayout example="other/locale">
    <h1 class="text-xl opacity-50">
      Nuxt birthday
    </h1>
    <p class="text-4xl">
      {{ date }}
    </p>
    <div class="mt-4" />
    <label for="locale-chooser">Preview a different locale</label>
    <select id="locale-chooser" v-model="locale" class="m-auto w-50 border n-border-base rounded p-1">
      <option v-for="l of locales" :key="l" :value="l">
        {{ l }}
      </option>
    </select>
  </NuxtExampleLayout>
</template>
composables/locale.ts
import type { Ref } from 'vue'

export const useLocale = () => useState<string>('locale', () => useDefaultLocale().value)

export const useDefaultLocale = (fallback = 'en-US') => {
  const locale = ref(fallback)
  if (process.server) {
    // Learn more about the nuxtApp interface on https://v3.nuxtjs.org/docs/usage/nuxt-app#nuxtapp-interface-advanced
    const nuxtApp = useNuxtApp()
    const reqLocale = nuxtApp.ssrContext?.req.headers['accept-language']?.split(',')[0]
    if (reqLocale) {
      locale.value = reqLocale
    }
  } else if (process.client) {
    const navLang = navigator.language
    if (navLang) {
      locale.value = navLang
    }
  }
  return locale
}

export const useLocales = () => {
  const locale = useLocale()
  const locales = ref([
    'en-US',
    'en-GB',
    'ko-KR',
    'zh-CN',
    'ar-EG',
    'fa-IR',
    'ja-JP-u-ca-japanese'
  ])
  if (!locales.value.includes(locale.value)) {
    locales.value.unshift(locale.value)
  }
  return locales
}

// Using Intl.DateTimeFormat for language-sensitive date and time formatting
// Learn more: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat
export const useLocaleDate = (date: Ref<Date> | Date, locale = useLocale()) => {
  return computed(() => new Intl.DateTimeFormat(locale.value, { dateStyle: 'full' }).format(unref(date)))
}