フロントエンド開発にNuxt３を使おう

# はじめに
Nuxt3はVue.jsベースのフレームワークで、より効率的にフロントエンド開発を行うことができます。
本フレームワークを使用する様々なメリットについて、またフロントサイドで私が良いなと感じたポイントを紹介します。
サンプルコードを交えて解説しますので、少しでもNux3を使ってみたいと思っていただければ幸いです。
(Nuxt3に関連するサービスなど、いくつか説明を省略しています。興味のある方は調べてみてください。)


# こんな人に読んでほしい
- Vue.jsの経験がある
- Nuxt.jsを使ってみたい
- もっと効率的にフロントエンド開発を行いたい
- フレームワークを使ってみたい


# 目次
- Nuxt3とは
- 環境構築
- ディレクトリ構造について
- 主要な機能について
- 機能紹介
- まとめ


# Nuxt3とは
[Nuxt3](https://nuxt.com/docs/getting-started/introduction)はNitroを搭載したVue.js のフルスタックフレームワークです。
Vueアプリケーション開発を効率化するための多くの機能を提供します。


## Nuxt2との違いについて
2022年11月16日正式リリースされたフレームワークで、Vue2からVue3ベースへアップデートされています。
以前はComposition APIを使用するため別途プラグインが必要でしたが(Nuxt2の説明は省きます)、デフォルトで使用可能になりました。
ビルドツールのViteをサポートしており、Nitroエンジンの搭載でパフォーマンスが向上しました。
さらにNuxt2ではサポートされていなかったTypeScriptをサポートしています。


# 環境構築
まずはサンプルコードを動かすための環境構築をしましょう。
コマンド１つで非常に簡単に構築可能です。
また、Node.js `v18.0.0`以上が推奨されているため、必要であればバージョンアップしてください。
（お使いの環境により方法が異なるため、Nodeのバージョンアップ方法は省きます）

インストールしたいディレクトリに移動し、以下コマンドを実行しましょう。
好きなパッケージマネージャーを選択するよう求められるので選択し、Enterで進んでください。
私は`yarn`を選択しました。

```
npx nuxi@latest init {project-name}
```

それでは必要な依存関係をインストールします。

```
cd {project-name}
```

```
yarn install
```

次に以下コマンドでアプリを起動してみましょう！

```
yarn dev
```

ブラウザのURLバーにURLを貼り付けてこのような画面が起動すれば成功です。

```
localhost:3000
```

![default.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952770/a7560dac-55af-2a6a-fb2d-814f5419c7d2.png)

インストール直後のディレクトリ構造はこのようになっています。
ブラウザ上に表示される内容に関わるファイルは`app.vue`ファイルのみだということがわかります。
(エディターはVSCodeで表示しています。)


# ディレクトリ構造について
Nuxt３は事前に決められている名前のディレクトリを追加することでアプリケーションを構築していきます。
それぞれの名前がどのような役割を持っているかを以下にまとめました。
後ほどそれぞれサンプルコードを交えて解説していきます。

| ディレクトリ名 | 役割 |
| ---- | ---- |
| pages | 各画面を配置 |
| layouts | 共通部品を配置 |
| components | コンポーネントを配置 |
| composables | composableな関数(reactiveな変数とロジックをコンポーネントから切り離して再利用できるようにした関数)を含むファイルを配置 |
| assets | cssファイルや画像などを配置 |
| public | 画像ファイルのような静的ファイルを配置(faviconとか)<br>assetsとの違いとして、バンドルによりビルド後に名称を変更したくないファイル(S3にアップするものなど)を配置 |
| plugins | アプケーション初期化時に実行される |
| middleware | Route Middlewareを利用することでページ間の移動、サイトへのアクセスをトリガーとしてページが表示される前に事前に設定していた処理を行う |
| server | API Routeを作成 |


# 主要な機能について
Nuxt3の強みのうち、いくつかフロントサイドの機能を紹介します。

- 自動ルーティング
  - 明示的にルーティングを書かなくてもやってくれる(VueRouterを使ってマッピングしている)
  - ネストされたページのルーティングも可能
- 動的ルーティング
  - URLの一部を動的に変更することで、一つのファイルで複数のルーティングを可能にする
  - [id].vueを作成することで取得可能
- 自動インポート
  - import文を書かなくてもコンポーネントを読み込んでくれる
- 共通レイアウト設定
  - デフォルトと名前付きの２パターン実装可能


# 機能紹介
上述した各ディレクトリの使い方を、サンプルコードを交えて紹介していきます。

- pagesディレクトリ
- layoutsディレクトリ
- componentsディレクトリ
- composablesディレクトリ
- assetsディレクトリ
- publicディレクトリ
- pluginsディレクトリ
- middlewareディレクトリ
- serverディレクトリ

## pagesディレクトリ
Nuxt3を使用しない場合、VueRouterを使用して明示的にルーティングをする方法が一般的かと思いますが、pagesディレクトリに配置した各画面ファイルは`自動ルーティング`されます。

以下コマンドで`pages/about.vue`を作成しましょう。

```
mkdir pages
```

```
code pages/about.vue
```

そして、以下内容を貼り付けてください。

```pages/about.vue
<template>
  <h1>About Page</h1>
</template>
```

URLを以下に変更してください。

```
localhost:3000/about
```

pagesディレクトリにファイルを作成するだけでルーティングしてくれるのでとても便利ですね。

![about.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952770/78092379-5d65-c5c4-6f8d-e4827358281e.png)

では、ディレクトリがネストされている場合はどうでしょうか。
`pages/user/index.vue`ファイルを作成しましよう。

```
mkdir pages/user
```

```
code pages/user/index.vue
```

同様に以下内容を貼り付けてください。

```pages/user/index.vue
<template>
  <h1>User Page</h1>
</template>
```

URLをネストされたディレクトリ名にしたい場合は、`index.vue`というファイル名で作成しましょう。
するとディレクトリ名でルーティングされます。

```
localhost:3000/user
```

![user.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952770/51aa3b59-d5bc-1b11-3cbd-2083ae987592.png)

続けて、動的ルーティングしてみましょう。
userのid毎にURLを変えたい場合などに有効です。
ファイル名が少し特殊で`[id].vue`のようにします。
コマンドベースで作成されない場合は手動作成してください。

```pages/user/[id].vue
<script setup>
  const router = useRoute();
  console.log('user id:', router.params.id);
</script>

<template>
  <h1>User ID Page</h1>
  <p>ページID: {{ $route.params.id }}</p>
</template>
```

```
localhost:3000/user/1
```

URLの末尾に付与した番号(ID)を変更してみましょう。
どの番号を付与しても画面が表示されます。これが動的ルーティングです。

また`<script>`タグに以下実装をすることでパラメータとしてIDを取得することができます。

```vue
const router = useRoute();
router.params.id
```

![user-id.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952770/062a5525-facb-8586-9396-122fb2f38976.png)


## layoutsディレクトリ
layoutsディレクトリは共通部品を配置します。
今回はヘッダータイトルを配置します。
layoutsディレクトリを作成し`default.vue`を作成しましょう。

```
mkdir layouts
```

```
code layouts/default.vue
```

```layouts/default.vue
<template>
  <div style="border-bottom: 3px solid;">
    <h1 style="background-color: #B0F8FF">共通ヘッダー</h1>
    <slot />
  </div>
</template>
```

`<slot />`タグをつけることを忘れないでください。
ここに他コンポーネントが展開されます。

また`app.vue`ファイルを以下に書き換えてください。
デフォルトのWelcomeページから独自で作成したページを表示するよう変更します。
`<NuxtLayout>`に共通部品が展開され、また`<NuxtPage />`タグに`pages`ディレクトリ以下のファイルが展開されます。

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

`localhost:3000/about`、`localhost:3000/user`にアクセスすると、すべてのページでヘッダータイトルが表示されることがわかります。

## componentsディレクトリ
componentsディレクトリにボタンコンポーネントを配置します。
`components`ディレクトリを作成し、その下に`button.vue`を作成しましょう。

```
mkdir components
```

```
code components/button.vue
```

```components/button.vue
<template>
  <button type="button" class="btn">ボタン</button>
</template>

<style>
.btn {
  width: 100px;
  height: 30px;
  background-color: blue;
  color: white;
}
</style>
```

それでは`about.vue`でコンポーネントを読み込んでみましょう。
Nuxt3を使わない場合はインポート文を記述する必要がありますが、自動ルーティング機能により記述は不要です。明示的に記述する方法もあるので好きな方で実装してください。
`about.vue`を以下のように書き換えます。

```pages/about.vue
<template>
  <h1>About Page</h1>
  <Button />
</template>
```

これでボタンコンポーネントが表示可能になります。

![components.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952770/6c86c4ea-bf0c-9b90-ca22-623d50317185.png)


## composablesディレクトリ
composablesディレクトリの中にcomposable関数を含むファイルを配置します。
今回は、ボタンクリックをトリガーとして数値をインクリメント、デクリメントする関数を作成してみます。
composablesディレクトリを作成し`useCounter.ts`を作成しましょう。

```
mkdir composables
```

```
code composables/useCounter.ts
```

```composables/useCounter.ts
export function useCounter(initialValue: number) {
  const count = ref(initialValue);
  const inc = () => (count.value = count.value + 1);
  const dec = () => (count.value = count.value - 1);
  return {
    count,
    inc,
    dec,
  };
}
```

そして`about.vue`でこの関数を読み出すため以下のように変更してください。

```pages/about.vue
<script setup>
const { count, inc, dec } = useCounter(1);
</script>

<template>
  <h1>About Page</h1>
  <div>Count:{{ count }}</div>
  <div>
    <Button @click="() => inc()">increase</button>
    <Button @click="() => dec()">decrease</button>
  </div>
</template>
```

左のボタンをくりっくするとインクリメント、右のボタンをクリックするとデクリメントします。
useCounterに初期値として`1`を渡し、それぞれのボタンのクリックをトリガーとして`inc`,`dec`が呼び出されます。

![counter.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952770/d8fa399b-f16f-a0a8-73ec-9df9fdaced0e.png)


## assetsディレクトリ
assetsディレクトリは静的ファイルを配置します。
`assets`ディレクトリを作成し、全体へ適用したいCSSファイルを作成しましょう。

```
mkdir -p assets/css
```

```
code assets/css/style.css
```

style.cssの中身として以下を貼り付けてください。

```assets/css/style.css
body {
  background-color: #FFFFBB;
}
```

またCSSファイルを読み込ませるため、`nuxt.config.ts`を以下へ書き換えてください。

```nuxt.config.ts
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  devtools: { enabled: true },
  css: [
    "@/assets/css/style.css"
  ],
})
```

今回は背景へ薄い黄色を設定しました。
どのページへ移動しても背景色が設定されていることが確認できます。

![assets.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952770/b38c03dd-bd21-a8e2-11b7-6dd763401882.png)


## publicディレクトリ
publicディレクトリはassetsディレクトリと同様静的ファイルを配置しますが、バンドルビルド後にファイル名を変更したくないファイルなどを配置します。
試しにビルドした結果を見てみましょう。

環境構築時にすでに`public/favicon.ico`が作成されています。
それではビルドしてみましょう。

```
yarn build
```

成功すると`.nuxt`, `.output`ディレクトリが作成されます。
静的ファイルは`.output/public`に集約されます。
`.output/public/favicon.ico`が作成されていることを確認してみましょう。
今回は省きますが、assets, publicそれぞれへ配置した静的ファイルへのアクセス方法も異なるので、気になる方は調べてみてください。


## pluginsディレクトリ
pluginsディレクトリはアプリケーション初期化時に実行してくれるもので、アプリケーション全体で利用するものを定義します。
今回は引数で受け取った文字列をHello XXと表示するプラグインを定義してみます。

まずはディレクトリを作成し、ファイルを作成します。

```
mkdir plugins
```

```
code plugins/hello.ts
```

```plugins/hello.ts
export default defineNuxtPlugin(() => {
  return {
    provide: {
      hello(msg: string) {
        return `Hello ${msg}!`;
      },
    },
  };
});
```

`World`文字列を引数に与えて呼び出してみます。
`about.vue`を以下に書き換えましょう。useNuxtApp関数を実行してプラグイン$helloを取り出しています。

```pages/about.vue
<script setup>
const { count, inc, dec } = useCounter(1);
const { $hello } = useNuxtApp();
</script>

<template>
  <h1>About Page</h1>
  <div>Count:{{ count }}</div>
  <div>
    <Button @click="() => inc()">increase</button>
    <Button @click="() => dec()">decrease</button>
  </div>
  <h2>{{ $hello('World') }}</h2>
</template>
```

`localhost:3000/about`アクセス時に`Hello World!`の文字列が表示されます。

![plugins.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952770/bdd644bc-1320-143e-a63e-528f092d848b.png)


## middlewareディレクトリ
ミドルウェアはページルーティングが発生する際に実行されます。
今回は`/admin`ページへアクセスするための認証ミドルウェアを作成し、クエリパラメータに`test=foo`が含まれるかどうかを判定していきます。
まずは`middleware`ディレクトリの下に`auth.ts`ファイルを作成します。

```
mkdir middleware
```

```
code middleware/auth.ts
```

以下を貼り付けてください。
URLが`/admin`でない場合は403エラーを発生させ、クエリパラメータに`test=foo`が含まれていない場合は`/about`にリダイレクトしています。

```middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
  if (to.path !== '/admin') {
    return abortNavigation(
      createError({ statusCode: 403, message: '認証されていません' })
    );
  }
  if (to.query.test !== 'foo') {
    return navigateTo("/about");
  }
});
```

またadminページも作成します。

```
code pages/admin.vue
```

`middleware: 'auth'`でauthミドルウェアを呼び出しています。

```pages/admin.vue
<script>
definePageMeta({
  middleware: 'auth',
});
</script>

<template>
  <div>
    <h1>Admin Page</h1>
  </div>
</template>
```

それでは以下URLでアクセスしてみましょう。
adminページが表示されれば成功です。また、クエリパラメータの値を変更してアクセスするとaboutページにリダイレクトされることも確認できます。

```
localhost:3000/admin?test=foo
```


## serverディレクトリ
`server`ディレクトリへはAPI Routeを定義します。
今回は`/api/hello`にアクセスすると”Hello World”を戻すシンプルなAPI Routeを追加します。

`server`ディレクトリへ、`api/hello.ts`を作成しましょう。

```
mkdir -p server/api
```

```
code server/api/hello.ts
```

```server/api/hello.ts
export default defineEventHandler(() => 'Hello World');
```

それでは以下URLでAPIを呼び出してみましょう。
`Hello World`が表示されるはずです。このように簡単にAPI Routeを定義することができます。

```
localhost:3000/api/hello
```

![api.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952770/7231c6d9-35bf-d978-6af4-c7da880fe87e.png)


# まとめ
Nuxt3ついて今回はフロントサイドを解説しました。
サーバーサイドにも便利な機能が多数あるのでぜひ調べてみてください。
機能が豊富でまた簡単に実装可能であるため、少しでも使ってみたいと思っていただければ幸いです。

ここまで読んでくださりありがとうございました！