もうv4になってんの!?
…Nuxtがv4になってたようです。
調べてみると2025年7月16日にリリースとのこと。
僕は2020年6月からNuxt v2を覚え始めて、2024年5月からv3の学習を始めました。 そこから数えると、たった1年半でのメジャーアップデートです。
このペースでアップデートされるときつい…。
v3のリリース自体は2022年11月16日だったようなので、3年弱での更新だったみたいです。
正直、また挙動テストしたりモジュールの確認したりするの、めんどくさいーというのが半分…いや、8割。 「v4を触れるようになっとかないと、モジュールの更新放置とか食らいそうだなー」というのが1割。 純粋な興味が1割。
調べてみると、v4はほぼv3のままで、一部に不可逆的な変更があるからメジャーアップデートにしただけみたい。 それなら軽く調べておくかーという感じで、小規模プロジェクトを移植しながら変更点を見ていくことにします。
このページの目次
Nuxt v4での変更点をざっくり
公式情報がちょっと分かりにくかったんですが、たぶんこのページかな?
ChatGPTくんに要約させると、
- プロジェクトのファイルをappディレクトリに隔離
- useAsyncDataなどの改良
- TypeScriptサポートの強化
- CLIと開発環境の高速化
「フレームワークが変わる」ほどの大きな変更じゃなくて、「構造整理と安定性の向上」にフォーカスしたアップデート、って感じみたいです。
実際、ほぼ変更なしで移植できました。
appディレクトリが必須になった
v2の頃はプロジェクト直下にcomponentsやpagesが並んでてゴチャゴチャしてたのが、v4では全部appフォルダ以下に移動しました。
v3の頃から同じ機能はあって、便利だなーと思う反面、ふとした瞬間に場所が分からなくなることがあって、結局v2のゴチャゴチャ方式のまま使っていました。
v4からはapp以下に置くのが必須になったようです。
具体的には、app以下に以下のフォルダを配置します(デフォルトじゃ作られませんでした)。
- assets
- components
- composables
- layouts
- middleware
- pages
- plugins
- utils
だいたいv3の時と同じです。
確かv3の頃はassetsが使えなかったので、今回のv4で復活したっぽい。 実際に、例にあった通りCSSをここに置いて、ちゃんと動いてます。
utilsは初めてみました。 公式によると、公式コンポーザブル(useStateとか)を使う自作コンポーザブルと、ただのユーティリティ関数を分けるために新設されたみたいです。
後は次のファイルもapp以下に配置。
- app.vue
- app.config.ts
- error.vue
ルートもappもだいぶすっきりしました。
nuxt.config.tsの変更点
「nuxt.config.js」が完全に廃止されて、「nuxt.config.ts」が必須になったようです。
それ以外は変更なしかも。
うちの環境では、次の項目がそのまま使えることを確認しました。
- app.baseURL
- plugins
- modules
- css
- vite.css.preprocessorOptions.scss.additionalData
- postcss
- experimental.defaults.nuxtLink.trailingSlash
モジュール設定として、「nuxt-gtag」の「gtag」や「@nuxtjs/sitemap」の「site」もそのままでした。
ただ、cssの指定がv3ではファイル名と形式をオブジェクトで指定していたんですが、
css: [
{ src: '~/css/style.scss', lang: 'scss' },
],
v4ではこれだと動作せず、ファイル名単体にする必要がありました。
css: [
'~/assets/style.scss',
],
その他の変更点は公式を確認してください。
sassモジュール
v4でもそのまま使えるっぽいです。 インストール方法もv3と同じです。
npm install -D sass
外部ファイルが従来のcssフォルダ以下ではなく、assetsフォルダで指定されてるので、そこが変更点かも。 cssフォルダで動くかどうかはまだ調べていません。
さっきも書いた通り、nuxt.config.tsの「css」では、オブジェクトではなくファイル名だけに変更します。
css: [
'~/assets/style.scss',
],
拡張子で判断しているのか、これでちゃんとSCSSとして読んでくれていました。
SCSS用の変数を定義したファイルは、vite以下のadditionalDataで指定するのはv3と同じ。
vite: {
css: {
preprocessorOptions: {
scss: {
additionalData: '@use "~/assets/_colors.scss" as *;',
},
},
},
},
あと、これはsassモジュール側の問題だと思いますが、「map-get」が非推奨になり、「map.get」を使うように変更しました。
これ以外の設定は公式を見てください。
nuxt-gtagモジュール
v4でもそのまま使えるっぽいです。 インストール方法もv3と同じです。
npx nuxt module add gtag
ただ、うちのパーミッションの問題なのか、モジュール側の問題なのか、nuxt.config.tsの自動更新がうまくいかなかったので、手動で追記する必要がありました。
追記内容もたぶん変更はないと思います。
modules: [
'nuxt-gtag',
],
gtag: {
id: 'G-XXXXXXXXX'
},
他のオプションは公式を見てください。
@nuxtjs/sitemapモジュール
v4でもそのまま使えるっぽいです。 インストール方法もv3と同じです。
npx nuxi@latest module add sitemap
gtagモジュールと違い、こちらはnuxt.config.tsにちゃんと追記されました。
設定内容もたぶん変更はありません。
modules: [
'@nuxtjs/sitemap',
],
site: {
url: 'XXXXXXXXX',
},
他のオプションは公式を見てください。
このモジュールは、Nuxt SEOというモジュールパックの一部みたいですが、v3の時に痛い目を見たのと、canonicalやOGP、構造化データは自前で用意してあるので、今回もたぶん使わないと思います。
@nuxtjs/i18nモジュール
v4でもそのまま使えるっぽいです。 インストール方法もv3と同じです。
npx nuxi@latest module add i18n
コイツはけっこうややこしいです。
基本的な設定や挙動はv3の時と同じで、
まずnuxt.config.tsに基本的な設定を追加。
modules: [
'@nuxtjs/i18n',
],
i18n: {
locales: [
{ code: 'ja', language: 'ja_JP', name: '日本語' },
{ code: 'en', language: 'en_US', name: 'English' },
],
defaultLocale: 'ja',
},
ここに言語別テキストも設定できるらしいんですが、v3の時にうまくいかなくて、どっちみち量が多いと隔離したくなるからうちは別ファイルでやってます。 ここで設定したい人は公式を見てください。
外部ファイルでテキストを設定する場合は、v3時代はプロジェクトルートの「i18n.config.ts」に定義できました。 v4ではここでは読んでくれず、ルートに「i18n」フォルダを作ってその中に「i18n.config.ts」を配置してください。 ここで基本的な設定もできるかもですが、試していません。 v3の時はできませんでした。
ここからがちょっとやっかいです。
僕は各ページで現在の言語コードを取得して処理の振り分けをよくやるんですが、各ページでcomputedを定義するのが面倒でグローバルで定義していました。 v3の頃はコンポーザブルでこんな風に定義して使い回していました。
export const lang = computed(() => {
const app = useNuxtApp()
if ( app.$i18n ) {
return app.$i18n.locale.value
}
})
今見るとずいぶんブサイクですが、このブサイクなやり方ができなくなってるっぽい。 ダメなのはこんな呼び出し方。
const app = useNuxtApp()
const locale = app.$i18n.locale
const locale = this.$i18n.locale
できなくはないんですが、呼び出しタイミングでは同期ずれが起きるのか、言語切り替えとかでハイドレーションみたいなエラーがめっちゃ出ました。
対策は簡単で、I18nのコンポーザブルを使うこと。
const i18n = useI18n()
const locale = i18n.locale
const { locale } = useI18n()
これでもまだ問題があって。
script setup(Composition API)の方を使ってると問題なさそうですが、僕はdefineNuxtComponent(Options API)を使ってて、こっちだとuseI18nを使えるタイミングが限られます。 computedやmethodsの中で使うと警告やエラーが出ます。 警告が出ないのはsetup()の中だけ。
export default defineNuxtComponent({
setup() {
const { locale } = useI18n()
return {
lang: locale,
}
},
})
setupって初めて知ったんですが、setup>head>data>mountedの順で実行されていて、最速だから同期ずれが起きない? 少なくともこれでエラーが出なくなりました。
ただ、これだと結局各ページで定義しないといけなくて、ChatGPTくんに相談したところ、まずコンポーザブルにこれを定義。
export const useLang = () => {
const { locale } = useI18n()
return computed(() => locale.value)
}
んで、コイツを各ページで呼び出し。
export default defineNuxtComponent({
setup() {
return {
lang: useLang(),
}
},
})
これだと少なくとも処理部分は1箇所だけで定義できます。
これで一応思った動作になったかな?
もうちょっと動かさないと分からないので、また何か気付いたら書き足しておきます。
trailingSlash関連
trailingSlashとは、URL末尾に「/」を付けるかどうかの設定です。
Nuxt v2時代は、nuxt.config.jsに1行設定を入れるだけで済んでいましたが、v3以降はルーターまわりの仕様が変わったため、少し手作業が必要になりました。 デフォではスラッシュなしで統一されていて、僕はスラッシュを付ける派なので、設定が必須です。
やり方はたぶんv3と同じで、設定が必要なのは以下の2箇所です。
- NuxtLinkのリンク先URL
- スラッシュなしURLへアクセスがあった際のリダイレクト
まず、NuxtLinkのリンク先はv3と同じくnuxt.config.tsで設定できます。
experimental: {
defaults: {
nuxtLink: {
trailingSlash: 'append',
}
}
},
v4でも、experimental設定は残ったままのようですが、まだ使えています。
リダイレクトはmiddlewareフォルダに以下の内容で「trailingSlash.global.ts」を作っておくと、自動インポートされてリダイレクトされます。
export default defineNuxtRouteMiddleware((to, from) => {
//末尾スラッシュなら除外
if ( to.path.endsWith('/') ) return
//画像などのファイル名なら除外
if ( to.path.match(/\.[a-zA-Z0-9]+$/) ) return
return navigateTo({
path: to.path + '/',
query: to.query,
hash: to.hash,
replace: true
})
})
「?id=1」のようなGETは「to.query」に、「#intro」のようなIDは「to.hash」に入っているので、「to.path」の評価では不要でした。
Nuxtが自動生成する「/_nuxt/」以下のファイルや、APIなんかはミドルウェアを通過しないので、除外処理を入れなくていいです。。
あとはsitemapモジュールが入っている場合は、nuxt.config.tsに以下の設定が必要です。
site: {
trailingSlash: true,
},
defineNuxtComponentはまだ使えた
ページのvueファイルのscript部分では、まずv2形式(Options API)があって、
<script>
export default {
head() {
},
data() {
},
computed() {
},
methods() {
},
}
</script>
v3からComposition APIというのになりました。
<script lang="ts">
</script>
Nuxt側はやたらとこっちを推してて、ChatGPTに聞くとTypeScriptの都合だとかなんとか。
scriptタグの中で宣言した変数がOptions APIで言うところの「data」、関数は「methods」になります。 「computed」も忘れたけど独特な書き方で対応できました。
僕としては絶望的に使いにくくて。
「computed」とか「methods」でグループ化されないから何がどこにあるのか分かりにくくて、処理も上から順だからすげー管理しにくく感じます。
これを解決する方法がNuxt v3の頃からあって、defineNuxtComponentというやつです。 これだとほぼv2と同じようにコードが書けます。
v4でもこのdefineNuxtComponentが使えたので、v2形式で続行可能です。
ただ、公式を見ても、もうasyncDataとheadについてしか書かれてなくて、Nuxt v5ではいよいよなくなるのかなーと。
その時が来たらComposition APIを勉強します。
自前のグローバル変数や関数
サイト名とか、いろんなページで使う関数や定数など、グローバルで自分が使う変数を1箇所で管理したい場合があると思います。 Nuxt v2の時代はプラグインでinjectという方法を使っていました。
injectはv3の頃から使えなくなって、代わりの方法がいくつか用意されています。
Nuxt側が使って欲しそうなのはapp.config.ts。 v4でファイルの場所がappフォルダ以下に固定されました。 これを使えばだいたい思った動作になりますが、nuxt.config.tsからはアクセスできないっぽい。
次にcomposablesという機能。 これもnuxt.config.tsからは呼び出せません。
useAppConfigやuseNuxtAppなしで直接呼び出せるグローバル変数や関数が定義できて、めちゃくちゃ重宝してました。 ただ、仕事として作るサイトだと雑な運用はダメだろうから、ちょっと慎重に扱わないといけなさそう。
僕は特に制限なくcomposablesを便利に使いまくってたんですが、Nuxt側からすると「useState」とか、Nuxt側のコンポーザブルを使う関数を想定してたみたいで、Nuxt v4からはコンポーザブルを使わないユーティリティ関数用にutilsというのが追加されてました。
ざっと見た感じ、書き方はcomposablesとほぼ同じかな? 僕はcomposables+utilsをcomposablesに定義してたので、せっかくなので分離しようかなと思います。
最後に、provideという方式。
export default defineNuxtPlugin(() => {
return {
provide: {
db: { primaryColor: '#ababab' }
}
}
})
こんな感じで、injectみたいな感じで使えて、単純にデータを定義するファイルと、それをdefineNuxtPluginするファイルに分けると、前者がnuxt.config.tsからも読めるので、結局この方式にもどりました。
今のところ、同じ考え方でv4でも使えてます。
僕は日曜プログラマなのでたぶんあまり良くない使い方をしてると思うんですが、詳しくはv3の時代にいろいろ調べたので良かったら読んでみてください。
感想とか
ほとんどv3のままでした。
Nuxt v4は、Windows 98 3rd Editionとして出ても良かったのに、無理やり新しいラベルを貼り付けられた、
Windows Meのような存在!
…それじゃイメージ悪いか。
まだまだテストは続けるので、何か新しい発見があれば追記しておきます。
活動支援として、Appleのギフトカード、作業中のおやつなどで応援お願いします。
🎁 Amazonから応援