Nuxt 3 を今すぐオススメしたい 15 のポイント
Nuxt.js バージョン3のPublic Betaが公開されて約2ヶ月が経ちました。
私自身この2ヶ月、プライベートでNuxt 3を触っているのですが、かなり気に入っています。
とにかく開発体験が向上していて、一言で表すと「開発していて楽しい」フレームワークです。あまりにも気持ちよく開発できるので、Nuxt 3が公開されてから明らかに睡眠時間が減っています。
ただ、実際に何が変わったのか、どんなところが良いのか、よくわからずにとりあえず様子見している方もいると思います。
Vue 3の目玉機能であるComposition APIはVue 2/Nuxt 2でもプラグインで既に導入できますし、TypeScriptサポートもオプションで導入されていましたから、あえてNuxt 3に乗り換える理由がない、と感じているかもしれません。
それではあまりにも勿体ないので、Nuxt 3で2ヶ月ほど開発してみて、個人的に良いと思った点を「セットアップ時」「TypeScript」「ライブラリ」という観点でまとめてみました。
あくまで「良いと思った点」なので、網羅的な解説ではなく、また具体的なコード例などは省略したり他の方の書かれた記事へのリンクに代えさせて頂いております。
この記事を読んで、Nuxt 3使ってみたい!とか、業務で早くアップデートしたい!という気持ちになる方が一人でもいたら嬉しいです。
ちなみに私自身は主にVueユーザーで、Next.jsも個人で少し使っていますが業務で使った経験はないので、Next.jsに関する言及では誤解も含まれているかもしれませんが、ご了承ください。
セットアップ編
アプリケーション開発に必要な機能が最初から揃っている
まず、Nuxt.jsの大きな特徴でありアドバンテージとして、「必要なツール全部入り」という便利さがありましたが、これはNuxt 3でもしっかり受け継がれています。
Nuxt 3では、JSX/TSXやSCSS対応、APIサーバー作成、ルーティングのVue Router、メタタグやタイトルを定義するVue Metaなどの機能が最初から含まれており、必要なものが全て揃っています。
この1年、Vue 3を先行体験するためにViteやVue CLIを使ってみたり、Next.jsで一通り開発してみて個人的に感じたのは、「Nuxtって便利だったんだな」ということ。
例えばページルーティングのVoieなど、Viteにも代替プラグインは存在するのですが、それを探してきて設定方法を覚えて……というのが地味に大変だし、そしてそれはアプリケーションの本質に繋がる作業ではありません。省略できるのであればそれに越したことがないと思いました。
一方で、Nuxt 2も当初はゼロコンフィグで開発を始められるフレームワークでしたが、近年ではTypeScriptやSCSSなどの実質的な必須機能を追加する必要があったため、初期設定が簡単とは言いづらくなっていたのも事実。
その点、Nuxt 3では、APIサーバー作成機能やTypeScript・JSX/TSXなどが新たに内包され、しかも最初からセットアップされているので、本当に追加設定一切なしでフロントエンド開発をスタートできます。
create-nuxt-app の質問攻めがなくなった
Nuxt 2では npx create-nuxt-app ***
でプロジェクトを作成する際、パッケージマネージャー、UIフレームワーク、lintツールなど、たくさんの質問に答える必要がありました。これは確かに便利ではあったのですが、だいたい毎回選ぶ内容は一緒なので時間がかかるし、後から変更したい時にどこを弄れば良いのかがブラックボックス化しやすいという問題がありました。
Nuxt 3ではこの問題もなくなり、npx nuxi init ***
をすると、それ以上の入力不要でプロジェクトが作成されます。
TypeScriptはデフォルトでオンになっているし、lintツールや各種モジュールも必要があれば後から追加すれば良いだけなので、ここの設定がシンプルになったのは地味に嬉しい変化だと思います。
開発時の起動&ホットリロードがとにかく速い
Nuxt 3は開発時のビルドツールにViteが採用されており、Viteの最大のウリである、サーバー起動とHMRの高速化をNuxtでも受けられるようになっています。
Nuxt 2はかなり起動が遅く、設定によっては起動まで1分くらいかかることもあってストレスが溜まりがちでしたが、Nuxt 3はnpm run dev
から5秒程度で起動し、開発後のホットリロードも高速です。これだけでNuxt 3に乗り換える価値があると思います。
ちなみに、Nuxt 2の既存プロジェクトにViteを導入できる nuxt-vite というライブラリもありましたが、現在は Nuxt Bridge に吸収されているので、興味のある方は Nuxt Bridgeでバージョン3移行の準備を進めるのも良さそうです。
サーバーエンジン Nitro で API Routes, ISR, サーバーレスビルドに対応
Nuxt 3ではNitroと呼ばれる新しいサーバーサイドエンジンが導入されました。
パフォーマンスも向上していますし、単純に使い勝手としてもこれまで middleware
serverMiddleware
に分かれていたものが整理され、直感的に使いやすくなっています。
私自身もまだ全ての機能は試せていないので、詳細なところは書けないのですが、API Routesをサポートしていたり、デプロイ先に依存せずにISR的な機能を利用できたり、かなり進化しています。
特に後述するAPI Routesの自動型検出機能は、これだけでNuxt 3を使う価値があると思えるほどに革命的な開発体験です。
Composition APIのあらゆる関数が自動インポートされる
Nuxt 3で開発をスタートして、Nuxt 2やVue 3との違いでまず驚いたのは、Composition APIの全ての機能が、明示的にインポートしなくても使えることです。
後述のscript setup記法を除いて全てのコンポーネントに書くことになる defineComponent
、Nuxtの独自拡張である useAsyncData
や useFetch
、Nuxtに組み込まれている useRouter
や useMeta
まで、全てインポートなしで使えます。もちろんTypeScriptの型サポートも行われます。
さらに、関数だけでなく <NuxtLink>
や <Head></Head>
のような便利コンポーネントもグローバルに使えます。こちらも型チェックがサポートされていて、例えば nuxt-link
にtoプロパティを設定していないとエラーが出ます。
依存関係が暗黙的になってしまうのは好き嫌い分かれるところかもしれませんが、ほぼ全てのコンポーネントで使うことになる ref
や computed
といった基本的な関数を毎回書かなくて済むのはかなり楽です。
一部の設定が Next.js に準拠する形で変更されている
Nuxt 3で導入された変更点・新機能の中には、Next.jsに合わせたと思われるものがいくつかあります。
- 画像などの静的ファイルを置くディレクトリが
static
からpublic
になった - メタタグの設定方法として、
<Html><Head><Body>
などの大文字タグを使ってテンプレートに記述できるようになった - pagesでの動的ルーティングの指定が
_id.vue
から[id].vue
になった - サーバーサイドのAPIが
server
ディレクトリに置けるようになった
など。
ここはあくまで記述方法の違いなので、どちらが良いというものではないですが、NextとNuxtを両方使っているユーザーに優しく、取っつきやすくなりました。
TypeScript編
TypeScriptファーストなフレームワークに
Nuxt 3では、現代のフロントエンド開発の当たり前となっているTypeScriptが最初からフルサポートされるようになりました。
TS対応はNuxt 2系でもバージョンを重ねるごとに改善されてきてはいましたが、あくまで後付けの選択肢に過ぎず、事前の設定に知識が必要だったり、ドキュメントが不足していたりでちょっと苦しいところがありました。
その点、Nuxt 3は最初からTypeScriptを前提に作られているので、特に意識しなくても自然に導入・設定されています。
わかりやすいところで言えばデフォルトの設定ファイル nuxt.config もセットアップ時点で最初からTSファイルだし、公式ドキュメントのサンプルコードもTypeScriptで書かれています。むしろTypeScriptを外す方が大変なのではと感じるほどです。
API Routes 機能で実装した関数の返り値に自動で型が付く
Nitroの項目でも触れましたが、Nuxt 3ではAPI Routesを利用して作ったAPIをコンポーネントなどで呼び出す際に、パスを引数に入れるだけで自動的に型検知されます。これはちょっと驚くほどに便利です。
詳しい使い方については以下の記事で既に他の方が詳細にまとめてくださっているので、そちらをご覧ください。
付け加えると、setup内で呼び出せる useFetch
や useAsyncData
だけでなく、$fetch
という汎用的なAPIも提供されており、この自動型推論は $fetch
でも機能するので、axiosのような感覚で使うこともできます。
Next.js で同様にAPIの型推論を実装した Next.js の API Routes から SWR の型推論を導く
という記事が投稿されていましたが、Nuxt 3なら最初から対応済みです。
正直、API Routesでこの型推論ができるのであれば、Apollo Clientのようなデータクライアント、またはprovide/injectのようなパターンをあえて導入しなくても、APIサーバー + Composition APIという基本機能だけで十分に開発していけるのではと感じています。このあたりはもう少し実際に使ってみたいです。
VSCode拡張Volarで、Vue Templateの型検知が完全・高速にできる
VSCodeのVue向け拡張機能の定番といえばこれまではVeturでしたが、現在はVolarになっています。
Veturは型検知が完璧に動作しなかったり、異常に型推論が遅くなったりすることがあったのですが、Volarはかなり高速になっています。また、Veturでは実験的機能だったTemplateタグの型チェックなども完全にサポートされました。
Vue TemplateはTypeScriptのチェックが効かないというのも、もはや昔の話です。
ちなみにVolarはVue 2で使うこともでき、セットアップ方法はマーケットプレイスのページで説明されています。
Composition APIの標準採用
Vueにおける新しい記法として提案され、プラグインでVue 2にも先行提供されていたComposition API。Nuxt 3にも当然組み込まれ、今後はむしろこちらが主流になっていくと思われます。
従来の記法であるOptions APIと比較した最大の強みはTypeScriptとの親和性。これまでのVueは「頑張れば型を付けることもできる」程度の部分的なサポートで、thisに型が増えていくだけでしたが、
Composition APIではそもそもthisを使わなくなり、refやcomputedが全て関数化されたことで、何も意識せずに型安全な開発を行うことができます。
Vue 2時代のOptions APIに慣れている方からすると、Composition APIは見た目があまりにも違うので取っつきにくく感じるかもしれません(私も以前はそうでした)が、おそらく使ってみると結構同じです。computedはcomputedだし、watchはwatchだし、mountedはonMountedだし、少なくともReact Hooksを習得するよりは遥かに簡単です。
<script setup>
記法
圧倒的に簡潔に書ける Vue 3.2で新たに導入されたscript setupは、Composition APIをさらに推し進めたような記法で、Composition APIの良さを保ったままもっとシンプルに書けるようになりました。
詳しい解説は、上の方の記事が素晴らしいのでそちらも見て頂けたらと思うのですが、
単に記述量が減るだけでなく、ランタイムパフォーマンスが向上する、defineProps
/defineEmits
(後述)が使えるなど、基本的にはメリットしかない記法です。
唯一の弱点として、(自分の試してみた限りでは)script setup と JSX/TSXを組み合わせることはできないようです。とはいえ、script setupを使えばTSXと同じかそれ以上の簡潔さで書けるので、Vue 3環境であれば無理にTSXを使わなくても良いかなと個人的には思い始めています。
defineProps でやっとpropsが普通に書けるようになった
前述のscript setupでのみ使える defineProps
で、ようやくpropsの型定義が一般的なTypeScriptと同じ形式で書けるようになりました。
正直Vue 2と比較してこれが一番嬉しいポイントです。
例えば、Vue 2 で Propsを宣言しようとしたらこんな感じでした。
<script lang="ts">
import { PropType } from "@vue/composition-api";
import Author from "../types/author";
export default defineComponent({
props: {
id: {
type: Number,
required: true,
},
title: {
type: String,
required: true,
},
date: {
type: Object as () => string | null,
required: false,
default: () => null
},
excerpt: {
type: String,
required: true,
},
author: {
type: Object as PropType<Author>,
required: true,
},
slug: {
type: String,
required: true,
},
},
setup(props) {
/** propsを使った処理 **/
},
});
</script>
つらみ。
一応この書き方をすれば、親コンポーネントのtemplateでもVolarまたはVeturを通して型チェックはしてくれるので、使い物にならないわけではないのですが、
なぜかtypeは大文字だし、optional指定も直感的でないし、プリミティブでない型にはPropTypeが必要だし、いろいろと辛みがありました。propsが1~2個なら別に良いのですが、4~5個のpropsをこの記法で書くのはしんどい。
このprops宣言だけはOptions APIの負の遺産を継承してしまっていた感があり、Composition APIでもTSXでもこのPropType問題を回避することはできませんでした。
これを defineProps
で書き直すとこうなります。
<script lang="ts" setup>
import Author from "../types/author";
type Props = {
id: number;
title: string;
date?: string;
excerpt: string;
author: Author;
slug: string;
};
const { id, title, date, excerpt, author, slug } = defineProps<Props>();
/** propsを使った処理 **/
</script>
もはや説明不要ですね。最高です。
しかも、defineComponentではsetupの引数で setup({ author }) {}
のように書くとリアクティブでなくなってしまうので、常に props.title
のように書いて参照する必要がありましたが、defineProps では分割代入も対応しています。
Vue 2 の Composition APIでこれまで開発してきた人なら、propsを分割代入できないことに地味にストレスを感じていたと思いますが、これも改善されました。
Vue 3ではもはや従来の defineComponentを使う理由も、Options APIを使う理由もほとんどなくなったと言って良いでしょう。
ちなみに emitも同様に defineEmits
で定義できるようになっています。
ライブラリ編
JSX/TSXにデフォルトで対応
Vue + JSXの組み合わせについては、Vue 2では一応できないこともない、Composition APIならそれなりに使えるが不満もある……くらいの位置づけでした。
詳しくは会社のテックブログで少し前に書きましたが、Composition APIとTSXの組み合わせはそれなりに良い感じだったものの、事前に設定ファイルを作る必要があったり、HTMLがany型になってしまったりといった問題もありました。
Vue 3 (Vite) でも、any型問題こそ解消されていたものの、導入するための設定手順はあんまり楽になっていなかったのですが、Nuxt 3ではついに 完全に追加設定不要でJSXコンポーネントを使えるようになりました!
厳密には tsconfig.json
の compilerOptions
に "jsx": "preserve"
を追加する必要はあるのですが、それだけで使えます。 d.ts
ファイルを自作しなければならなかったNuxt 2や、@vitejs/plugin-vue-jsx を別で追加しなければならなかったViteと比べると雲泥の差。
Nuxt 3でようやく、Vue × JSX/TSXがVue Templateと並ぶ主要な選択肢になったと言えます。もちろんJSなので型検知もしっかり効きます。
SCSS、SASS、CSS Modules がすぐに使える
styleタグのsass対応についても、Config Fileに設定を記載する必要などもなく、<style lang="scss">
のような書き方をするだけで使えます。正確にはsassパッケージをインストールする必要はありますが、その旨も画面に表示されるので迷いません。
しかもVue 3では、CSS Modules や JS変数のCSSでの読み込みもネイティブサポートしており、使い方も上のページを見ればわかる通り、とても簡単です。CSSの書き方で迷わないのはVueの明確に優れている点の1つでしたが、この2つが正式にサポートされたことでさらに盤石なものになりました。
(ちなみにJS変数は CSS Variables を利用して実装されているのでIEでは動きませんが、Nuxt 3が正式リリースされる頃にはIEはサポート終了になっているはずなので、忘れましょう)
Vuex が必須ではなくなり、useStateとpiniaが登場
Nuxt 2では主要機能の1つとして組み込まれていたVuex。
数年前までは大規模アプリであればVuexを使うのが当たり前だったので重宝されていましたが、徐々にグローバルな状態管理ストアにアプリケーションが依存するリスク、TypeScriptや単体テストとの相性の悪さなどが問題視されるようになりました。
Composition APIのprovide/injectで型安全なストアパターンの実装も比較的簡単にできる今となっては、NuxtがVuexを強制的にバンドルしていることは、むしろビルドサイズの肥大化というデメリットになってしまいます。
また、大規模なチーム開発の場合、初期の実装者がVuexを使わない設計にしていても、追加設定なしで部分的に使えてしまうという問題もあります。
Nuxt 3ではVuexがバンドルされていないので、このような心配はなくなり、必要なツールだけを選べるようになりました。
そして、そんなVuexの(おそらく)代替として提供された機能が useState
。型としては ref
に近い定義で、SSR含めてコンポーネントを跨いでデータを共有できます。
Reactで例えると、シンプルな状態管理ライブラリJotaiに近いのではないでしょうか。
React HooksのuseStateと違うものなので、このネーミングはちょっと避けてほしかったな……とは正直思いましたが、ともかく機能としては使いやすいです。基本的にはデータストアはこれとコンポーネント管理で十分なケースが多いと思います。
もう少しリッチなストアが必要な場合、Vue 3でVuexに代わって推奨ライブラリとなった Pinia という選択肢もあります。
State、Getters、Actionsと、Vuexと同じような構造で直感的に使えながら、完全にタイプセーフで、しかも既にNuxt 3 対応モジュールが提供されています。(Nuxt 2でもVue/Viteでも使えます)
ただ、型安全とはいえVuex同様にthisを使う仕組みになっている点は、好き嫌いが分かれるかもしれません。
Tailwindのモジュールが既にある
これは時限的な話というかTipsに近いのですが、nuxt-windicssはNuxt 3に既に対応しています。なので、Tailwind CSSについては設定ファイルをそのまま持ってくるだけで移行できます。
対応モジュールはまだまだ少ないですが、主要なモジュールの多くでVersion 3 のサポートのissueが既に立っています。これが充実してくるとNuxt 3での開発がさらに便利になりますし、Nuxt 2からの移行のハードルも下がってくるので、今後に期待が膨らみます。
まとめ
他にも composables、plugins、Nuxt/Bridgeなどいろいろ触れるべき箇所はあるのですが、自分がちゃんと語れる範囲に絞ってみました。
全ての機能をより深く知りたい方は、Nuxt 3の公式ドキュメントや、他の方の記事にわかりやすくまとまっていますので、そちらもご覧ください。
Nuxt 3は、とにかくバージョン2系の課題とされてきた、TypeScript対応とパフォーマンスにかなり力を入れてきているなと感じました。API Routesやcomposablesフォルダの自動型検出などは、ちょっとやりすぎではと思うほどに強力で、
「Nuxt 3はこのくらいTSサポートしてくれたら十分かな」という事前の期待を何倍も上回ってきたので、ホスピタリティの高さに感動しています。
ここ数年のVue/Nuxtに対するネガティブイメージの多くはTypeScriptとの相性の悪さに原因があったと思っていますが、Composition API、Vue 3、Nuxt 3が登場した今となってはもはや使う側の気持ちの問題でしかないと思います。
Nuxt 2が抱えていた問題、React/Next.jsに遅れを取っていた様々な部分が確実に改善され、その上でNuxt.jsの大きな強みである、開発の際に必要となる様々な面倒さをフレームワーク側で解決している部分はしっかり引き継がれており、純粋にコーディングとプロダクトの実装に集中できる素晴らしいフレームワークです。
2022年は個人開発でも業務でも積極的に導入していきたいです。
Discussion
とっても素敵な記事でした!
最近Vue3を触っているのですが、script setup記法やComposition関数がとてもしっくりきておりコンフィグが少ないところも含め開発が楽しいです!
Nuxt3についてあまり追えていませんでしたので、概観を把握できとても参考になりました。
一点、Piniaの使い方について
実は、Piniaは
ref()
をそのままdefineStore
で定義しexportすることができます。「You can even use a function (similar to a component setup()) to define a Store for more advanced use cases:」の項参照
computed()
などもそのままexportできます。ご紹介くださったJotaiやRecoilと似た書き味でとても気に入っているので、こいずみさんももしよろしければ試してみてください!