【Nuxt 3】基本機能をなんとなく理解する

![download.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/e08982d2-30ad-8b70-d1ba-43d752452798.gif)


## Nuxtとは

![logo-green-black.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/9fb61a14-b8d7-5689-f1ad-917ae79f4480.png)



Nuxtとは、一言で言うと「**Vueを便利にしたヤツ**」です。

コンポーネント内のルールは基本的にVue.jsと同様です。

なので、Nuxtの学習をする際は、「プレーンなVue.jsと比較して何が違うのか」という点を理解することが重要です。


## 新規アプリケーションの作成

以下コマンドを実行することで、Nuxt3の新規アプリケーションが作成されます。
```
npx nuxi@latest init アプリケーション名
```
作成されてディレクトリに移動し、「npm run dev」コマンドを実行することで、開発サーバーが起動します。
```
cd アプリケーション名
npm run dev -- -o
```
開発サーバーが起動されていると、以下のようなNuxt3のウェルカムページが表示されます。Nuxtアプリケーションの作成ができました👏

![Screenshot 2024-07-17 at 23.50.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/630bfd2f-742c-cc10-16e4-2fb5a188c553.png)


## ディレクトリ構成
```
./
├── .git
├── .gitignore
├── .nuxt
├── README.md
├── app.vue
├── node_modules
├── nuxt.config.ts
├── package-lock.json
├── package.json
├── public
├── server
└── tsconfig.json
```
新規でアプリケーションを作成すると、👆のようなファイル・フォルダが作成されます。その中でも重要なものについて以下で紹介します。

### app.vue

Vueで言うところの`App.vue`で、Nuxtアプリケーションのルートコンポーネントです。

このファイルは、アプリケーション全体のレイアウトを定義するために使用されます。すべてのページやコンポーネントはこのファイル内でレンダリングされます

### nuxt.config.ts

Nuxtアプリケーションの設定ファイルで、アプリケーションの全体的な設定、プラグインの追加、モジュールの設定、環境変数の定義などを行うことができます。

このファイルを使用して、Nuxtアプリケーションの挙動をカスタマイズします。

## ルーティング

Vue.jsを使用したアプリケーション開発を行う際には、別途VueRouterをインストールすることで、ルーティングを実装しますが、Nuxtでは追加でライブラリのインストールをせずにルーティングを実装することができます。

Nuxtでは、「**ファイルシステムルーティング**」と呼ばれるシステムにより、ディレクトリ構造を元に自動的にルーティングを設定することができます。この機能を使用すると、手動でルートを設定する必要がなくなり、プロジェクトの構造がより直感的になります

### 基本


ファイルシステムルーティングを使用するには、`app.vue`ファイルに`<NuxtePage />`を追加してください。
`<NuxtePage />`とはVueRouterで言うところの`<router-view />`で、この`<NuxtePage />`の部分に、アクセスされたURLに該当するコンポーネントが表示されます。
```vue:app.vue
<template>
  <div>
    <NuxtPage />
  </div>
</template>
```

次に、プロジェクトルートに`pages`ディレクトリを作成してください。

ファイルシステムルーティングは、この`pages`ディレクトリ配下で定義されたコンポーネントを元にルーティングを行います。

例えば、`pages`ディレクトリ配下に`about.vue`を追加します。

```vue:pages/about.vue
<template>
  <div>
    <h1>About page</h1>
  </div>
</template>
```
ブラウザで「/about」にアクセスすると、上記で定義したコンポーネント`pages/about.vue`が表示されます。

![Screenshot 2024-07-18 at 18.16.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/c26d6680-052f-88bc-751b-8f29b292ac9c.png)

このように、アクセスさえたURLに対し、`pages/`配下から該当するコンポーネントを表示します。

「/」のようにルートにアクセスがあった場合は`pages/index.vue`が表示されます。

「/about」の場合でも、上記で紹介した`pages/about.vue`でもいいですが、`pages/about/index.vue`でも定義することが可能です。ですが、`pages/about.vue`と`pages/about/index.vue`両方が定義されている場合は、`pages/about.vue`が優先的に表示されます。


### ダイナミックルーティング

「/users/1」のように、パスパラメーターを定義したい場合には、`/pages/users/[id].vue`のようにコンポーネントを定義します。

```vue:/pages/users/[id].vue
<template>
  <div>
    <h1>User: {{ $route.params.id }}</h1>
  </div>
</template>
```

![Screenshot 2024-07-18 at 18.24.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/511e9aa6-11d0-b8dd-e608-2d5178683c72.png)

`<template>`内からパスパラメータにアクセスする際には、`$route.params`からアクセスすることができます。

`<script>`内で、パスパラメーターにアクセスする際には、`useRoute`メソッドを使用してルートの状態を取得し、`params`から該当するパラメータにアクセスします。
```vue
<script lang="ts" setup>
const route = useRoute();
console.log(route.params.id); // 1
</script>
```

パラメータ名は自由に指定することができます。例えば`/pages/users/[user_id].vue`のように指定した場合は、`$route.params.user_id`のようにアクセスする必要があります。

また、パラメータはディレクトリ名で表現することも可能です。「/users/1/likes」のようなパスに対するコンポーネントを作成する際には、`/pages/users/[id]/likes.vue`のように定義することができます。

| アクセスされたURL | 表示されるコンポーネント |
|:-----------|:------------|
| /       | pages/index.vue        |
| /about     | pages/about.vue または pages/about/index.vue      |
| /users       | pages/users.vue または pages/users/index.vue        |
| /users/1         | pages/users/[id].vue または pages/users/[id]/index.vue          |
| /users/1/likes       | pages/users/[id]/likes.vue または pages/users/[id]/likes/index.vue       |

また、`pages`ディレクトリから該当するページが見つからない場合は、以下のようにNuxtで用意された404ページが表示されます。

![Screenshot 2024-07-19 at 6.14.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/373c5782-584a-8006-862d-969b267bcb5f.png)


### 画面遷移

`template`内では、`NuxtLink`を使用して画面遷移リンクを作成することができます。propsの`to`には、遷移先のURLを指定します。
```vue
<NuxtLink to="/about">About</NuxtLink>
<NuxtLink to="/">Home</NuxtLink>
```
`script`内で画面遷移を実装する場合は、`navigateTo`メソッドを使用します。

```typescript
navigateTo("/about");
```

## エラーページ

Nuxtでは、コンポーネントのレンダリング時に何かしらのエラーが発生した場合や、存在しないURLに対してユーザーがアクセスを行った場合に表示するカスタムエラーページを作成することができます。

アプリケーションルートに`error.vue`という名前でコンポーネントを作成します。propsとしてエラーオブジェクトを受け取り、エラーの内容を画面に表示するようなものです。

```vue:error.vue
<template>
  <div>
    <h1>{{ error.statusCode }}</h1>
    <NuxtLink to="/">Go back home</NuxtLink>
  </div>
</template>

<script setup lang="ts">
import type { NuxtError } from "#app";

const props = defineProps({
  error: Object as () => NuxtError,
});
</script>
```

試しに、存在しないURLに対してアクセスを行ったり、コンポーネントのレンダリング時に何かエラーが発生すると、以下のように`error.vue`の内容が表示されます。

![Screenshot 2024-07-19 at 8.41.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/697538f6-1dfc-ea97-d3d5-6207c81138bc.png)

## middleware

Nuxtでは、特定のルートへアクセスする際に処理を挟み込むことができる機能を提供しています。

ユーザーの認証、リダイレクト、ログインチェックなど、ルートに関連するロジックを実行するために使用されることが多いです。

ミドルウェアは`middleware`ディレクトリで定義しましょう。`defineNuxtRouteMiddleware`を使用することでミドルウェアを定義することができます。`navigateTo`で、遷移先を指定できて、`abortNavigation`を使用することで遷移を中断できます。

```typescript:middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
  const user = getUser();

  if (!user.isLoggedIn) {
    return navigateTo('/login'); // ログイン画面に遷移
  }

  if (!user.isValid) {
    return abortNavigation(); // 遷移を中断
  }
});
```

ミドルウェアの適用は、各ページコンポーネントで行います。`definePageMeta`の`middleware`オプションを使用します。適用するミドルウェアは配列形式で渡します。その為、1つのページに対して複数のミドルウェアを適用することも可能です。

```vue
<template>
  <div>
    <h1>About page</h1>
  </div>
</template>

<script lang="ts" setup>
definePageMeta({
  middleware: ["auth"],
});
</script>
```

また、ミドルウェアを定義したファイル名に`.global`を付与することで、全てのページに対して適用されるミドルウェアを設定することもできます。

例えば今回作成した`middleware/auth.ts`を、`middleware/auth.global.ts`にすることで、全てのコンポーネントに対してミドルウェアが適用されます。

## layouts

Nuxtでは、Layoutという仕組みを提供しています。

レイアウトでは、アプリケーション全体また特定の画面グループで共通のテンプレートを定義するための仕組みです。
これにより、各ページで共通する部分（ヘッダー、フッター、ナビゲーションバーなど）を一箇所で管理できます。

アプリケーションルートに、`layouts`という名前でディレクトリを作成してください。ルールとして、レイアウト定義ファイルは`layouts`ディレクトリの配下に置く必要があります。

`layouts`ディレクトリ配下に`default.vue`という名前で以下のようなコンポーネントを作成してください。

`<slot />`の位置に、画面毎のコンポーネントが表示されます。
```layouts/default.vue
<template>
  <div>
    <nav class="navigation_bar">
      My awesome app
    </nav>
    <slot />
  </div>
</template>

<script setup lang="ts">
</script>

<style scoped>
.navigation_bar {
  width: 100%;
  height: 50px;
  background-color: rgb(246, 150, 24);
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 20px;
  font-weight: bold;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
</style>
```

次に、`app.vue`の記述を以下のように変更してください。`NuxtLayout`を追加することでLayoutsシステムを使用することができます。
```app.vue
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>
```

これでレイアウトの適用が完了しました。ブラウザで色々な画面に遷移しても、常に`layouts/default.vue`で定義した内容が表示されるようになります。

![Screenshot 2024-07-19 at 6.58.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/7bb4a611-884b-ecd7-4ce5-a382389345b9.png)

![Screenshot 2024-07-19 at 6.58.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/bba72caf-083c-1eb2-8b00-4af6a636e287.png)

また、`definePageMeta`というメソッドの`layout`オプションを各コンポーネントで指定することで、デフォルトのレイアウトを適用したくない。または他のレイアウトを使用することもできます。

`layout`で`false`を指定することで、レイアウトの適用を無効化することができます。
```vue
<template>
  <div>
    <h1>About page</h1>
  </div>
</template>

<script lang="ts" setup>
definePageMeta({
  layout: false,
});
</script>
```

![Screenshot 2024-07-19 at 7.03.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/0b5cdbc7-5f32-5777-36a6-9d7813a56138.png)

`layout`で、名前を指定することで、`layouts`配下の同名のレイアウトが適用されます。
```vue:layouts/custom.vue
<template>
  <div>
    <nav class="custom_navigation_bar">
      costom
    </nav>
    <slot />
  </div>
</template>

<script setup lang="ts">
</script>

<style scoped>
.custom_navigation_bar {
  width: 100%;
  height: 50px;
  background-color: rgb(24, 183, 246);
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 20px;
  font-weight: bold;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
</style>
```
```vue
<template>
  <div>
    <h1>About page</h1>
  </div>
</template>

<script lang="ts" setup>
definePageMeta({
  layout: "custom",
});
</script>
```

![Screenshot 2024-07-19 at 7.06.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/4c7913a3-3e4d-1e85-f0bf-e3695139486f.png)


## Auto Imports

Nuxt3では「Auto Imports」という機能があり、特定のディレクトリ配下のコンポーネントはimport文を記述せずにインポートすることができます。

```vue
<template>
  <div>
    <Card />
  </div>
</template>

<script lang="ts" setup>
</script>
```

デフォルトでAuto Importsが有効化されているのは、`components/`、`composables/`、`utils/` です。

これらは`nuxt.config.ts`にて、無効化・カスタマイズが可能です。

```typescript:nuxt.config.ts
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  compatibilityDate: '2024-04-03',
  devtools: { enabled: true },
  imports: {
    autoImport: false // 無効化
  }
})
```
```typescript:nuxt.config.ts
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  compatibilityDate: '2024-04-03',
  devtools: { enabled: true },
  imports: {
    dirs: ["some_dir/**"], // some_dir配下をAuto importの対象に追加
  }
})
```


## server

Nuxt3では、`server/`ディレクトリを利用して、サーバーサイドで実行される処理を定義することができます。
これを利用して、APIエンドポイントを実装することができます。

早速APIを作成してみましょう。以下のように`server/api/hello.ts`を定義してください。サーバー側の処理を定義する際には、`defineEventHandler`というメソッドを使用します。
```ts:server/api/hello.ts
export default defineEventHandler((event) => {
  return {
    hello: "world",
  };
});
```
定義したAPIを、クライアントサイドから実行してみましょう。

Nuxtでは、データフェッチを行う際には`useFetch`というメソッドを使用します。今回は`server/api/hello.ts`でAPIを作成したので、エンドポイントは`/api/hello`となります。
```vue
<template>
  <div>
    <h1>About page</h1>
    <pre>{{ data }}</pre>
  </div>
</template>

<script lang="ts" setup>
const { data } = await useFetch("/api/hello");
</script>
```
以下のように、正常にデータフェッチができていることが確認できます。

![Screenshot 2024-07-19 at 9.01.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/75148d5e-4abf-26f2-aaff-334473a4dd23.png)


また、HTTPメソッド毎に処理を分けたい場合には、ファイル名を変えてあげるだけで可能です。`.get`、`.post`、`.put`、`.delete`を用いて定義したAPI Routeが対応するHTTPメソッドを指定することができます。

例えば以下のように、`/api/hello`というエンドポイントでGET、POSTを作成してみます。
```typescript:server/api/hello.get.ts
export default defineEventHandler((event) => {
  return {
    message: "HTTP method is GET!",
  };
});
```

```typescript:server/api/hello.post.ts
export default defineEventHandler((event) => {
  return {
    message: "HTTP method is POST!",
  };
});
```

GETでリクエストを送った際には、`server/api/hello.get.ts`で定義したサーバーアクションが実行されます。
```vue
<template>
  <div>
    <h1>About page</h1>
    <pre>{{ data }}</pre>
  </div>
</template>

<script lang="ts" setup>
const { data } = await useFetch("/api/hello");
</script>
```

![Screenshot 2024-07-19 at 9.15.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/742d71d0-ecf6-17b2-6496-9b4c48b8425e.png)


POSTでリクエストを送った際には、`server/api/hello.post.ts`で定義したサーバーアクションが実行されます。
```vue
<template>
  <div>
    <h1>About page</h1>
    <pre>{{ data }}</pre>
  </div>
</template>

<script lang="ts" setup>
const { data } = await useFetch("/api/hello", {
  method: "post",
});
</script>
```

![Screenshot 2024-07-19 at 9.17.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/eb1e65c9-a606-bbf7-38ad-d07b7774ec03.png)


上記では単純に文字列を返却するAPI Routeを作成しましたが、Prisma等を使用してデータベースアクセスを行右ことも可能なので、本格的なAPIを実装することも可能です。

## レンダリング手法の選択

Nuxtでは、ページのレンダリング方法を選択することができます。こちらは少し長くなるので、また別の記事でご紹介しようと思います。