Nuxt v3 to v4 Migration

Introduction
I've been using Nuxt.js v3 for my content sites for a while now, and with the release of Nuxt v4, I finally decided to take the plunge.
Related Blog Posts
Projects Updated
- Pennock Projects - My personal blog where I share articles, projects, and updates about my work and interests. I had to upgrade this site from Nuxt v2 to v4. Two full feature jumps.This site was built with Nuxt v2 and Nuxt Content v2, so it required a significant upgrade to Nuxt v4 and Nuxt Content v3.
- JAMStart - My starter template for new static content sites. This site was already running Nuxt v3 with
compatibilityVersion: 4, which made the upgrade to Nuxt v4 much smoother.
Upgrade vs Install
In review the Nuxt 4 Upgrade Guide The main steps involved in the Nuxt v4 upgrade were:
- Installing Nuxt v4
The first instruction was to install Nuxt v4 using npm. Like this:
npm install nuxt@^4.0.0
This lead to weird warnings about deprecated packages and an outdated version of Nuxt.
npm install nuxt@^4.0.0
npm warn deprecated node-domexception@1.0.0: Use your platforms native DOMException instead
npm warn deprecated unplugin-vue-router@0.14.0: Merged into vuejs/router. Migrate: https://router.vuejs.org/guide/migration/v4-to-v5.html
npm warn deprecated glob@10.5.0: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version.
added 255 packages, removed 38 packages, changed 90 packages, and audited 1451 packages in 50s
>npm outdated
Package Current Wanted Latest Location Depended by
nuxt 4.0.0 4.4.2 4.4.2 node_modules/nuxt JAMStart
So this was a bit of a mistake as the NPM command literally upgraded Nuxt to the Nuxt 4.0.0 release, which was a bit too old and had deprecation warnings. I then upgraded to the latest Nuxt v4 with:
npm upgrade nuxt
added 52 packages, removed 276 packages, changed 60 packages, and audited 1227 packages in 30s
which put latest Nuxt 4.4.2 release.
Avoiding this Mistake
Something I've learned is to use install instead of upgrade, so you can specify the exact version of Nuxt to install with:
npm install nuxt@4.4.2
Migration Doc
App Directory
I was prepared to move all my site into the new app directory, but I was pleasantly surprised that I did not have to do this. I decided with the other issues, I would postpone this for for my next git check in. I'll update this topic when I do that.
Single Data Fetching
In reading the migration guide, I made aware of the change to single data fetching with useAsyncData (and useFetch). Given I haven't implemented a central store layer, using something like pinia, which is overkill on a static site like mine. But I did uncover an issue with with custom controls and using unique names to all my useAsyncData.
For example, I have a custom controls, like ArticleList and PostList which fetches markdown content pages from different subdirectories within the content directory and may have limited depth and or sorting. These controls can exist on multiple pages, such as the blog list, top 3 new posts, or the cheat sheet list. To avoid hydration errors, I had to give each useAsyncData call a unique name based on the content being fetched. For example, in my PostList.vue component, I had to generate a unique query name based on the rootPath, sortBy, sortOrder, and limit props. This ensures that each instance of the PostList component fetches its own data without conflicts. For example, I might use the names:
blog-posts-3-newestcheat-posts
Configuration Problems
Most of migration issues were related to configuration problems. The Nuxt v4 migration guide has a section on configuration changes, but it was not comprehensive and did not cover all the issues I encountered. For some, I had to do a lot of research, replace a module, and trial and error to figure out how to update my configuration files to work with Nuxt v4.
devtools Warning
When I started up the development server with npm run dev I got the following warning:
Vite discovered new dependencies at runtime: 4:20:36 PM
./virtual:nuxt:C%3A%2Fdev%2Fpp%2FJAMStart%2F.nuxt%2Fruntime.vue-devtools-client.E24JpsC1QHyv30MzBlyQv2ApgjFxiuh969Gs938_Ip8.js
@vue/devtools-core
@vue/devtools-kit
Pre-bundle them in your nuxt.config.ts to avoid page reloads:
export default defineNuxtConfig({
vite: {
optimizeDeps: {
include: [
'@vue/devtools-core',
'@vue/devtools-kit',
]
}
}
})
Learn more: https://vite.dev/guide/dep-pre-bundling.html
To solve this I added the optimizeDeps configuration to my nuxt.config.ts file as shown in the warning message. This pre-bundles the dependencies and should prevent the warning from appearing again.
zod Warning
I also got a warning about Zod, a new schema module for Nuxt Content, about it not being optimized for production when I started the development server, so I added it to the optimizeDeps configuration as well like this:
export default defineNuxtConfig({
vite: {
optimizeDeps: {
include: [
'@vue/devtools-core',
'@vue/devtools-kit',
'zod'
]
}
}
})
Incompatible CloudFlare Module
The basic module for CloudFlare Analytics is not compatible with Nuxt v4.
WARN Module nuxt-cloudflare-analytics is disabled due to incompatibility issues: 4:19:21 PM
- [nuxt] Nuxt version ^3.0.0 is required but currently using 4.4.2
There was no update available for the nuxt-cloudflare-analytics module, and it seems to be abandoned. I looked for an alternative CloudFlare Analytics module, but there were no other Nuxt modules available for CloudFlare Analytics. I then looked for a more general solution that would allow me to use the CloudFlare Analytics script without relying on a specific module. This led me to the NuxtScripts module, which allows you to easily add third-party scripts to your Nuxt application.
I removed the deprecated modules like this:
- Uninstall the deprecated module: :
npm uninstall nuxt-cloudflare-analytics
- Remove the module
nuxt-cloudflare-analyticsfrom themodulesarray innuxt.config.ts.
export default defineNuxtConfig({
modules: [
// other modules...
'nuxt-cloudflare-analytics' // remove this element from the array
],
});
3. Commented out the configuration token like this:
```ts
export default defineNuxtConfig({
// cloudflareAnalytics: {
// token: 'XXXXXXXXXXXX'
// },
You'll need this token later.
New NuxtScripts CloudFlare
I found a new nuxt module called NuxtScripts that contains many third party scripts including CloudFlare Analytics. It recently left beta and is at 1.1.1 as of this writing. It also has a host of other scripts from GA, Ads, etc. but I'm only interested in CloudFlare Analytics as it is cookie-less and free. Here's what happened when I upgraded.
- Then I installed the
NuxtScriptsmodule.npx nuxi@latest module add scripts
- Then I edited the
nuxt.config.tsfile to move my CloudFlare token to a new key for theNuxtScriptsmodule. I added the following to thescripts.registrykey innuxt.config.ts:
export default defineNuxtConfig({
scripts: {
registry: {
cloudflareWebAnalytics: {
token: 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4', // replace with your actual token
trigger: 'onNuxtReady',
}
}
}
});
Initially this looked like it was working, but then when I inspected it on my dev site at AWS S3 I could see the cloudflare beacon the following network error:
"Request URL
http://dev.pennockprojects.com/_scripts/p/cloudflareinsights.com/cdn-cgi/rum
Request Method
POST
Status Code
405 Method Not Allowed
This was because the NuxtScripts CloudFlare module was using some sort of local proxy. Notice how it was trying to connect to itself dev.pennockprojects.com rather than to the actual CloudFlare CDN. I guess the NuxtScripts module uses some sort of local proxy for all its scripts, which on an S3 WebSite will fail with a 405 Method Not Allowed.
To resolve I had to add two additional settings, proxy: false and bundle: false, to prevent the NuxtScripts module from trying to proxy the CloudFlare script through the local server.
export default defineNuxtConfig({
scripts: {
registry: {
cloudflareWebAnalytics: {
token: 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4', // replace with your actual token
trigger: 'onNuxtReady',
proxy: false, // add this for S3 hosting
bundle: false // add this for S3 hosting
}
}
}
});
The final configuration tweak was that I only wanted it to beacon in production, so I forced it to use the beacon only in production (only when generated) by wrapping the scripts configuration to the $production key in nuxt.config.ts like this:
export default defineNuxtConfig({
$production: { // These options only in prod.
scripts: {
registry: {
cloudflareWebAnalytics: {
token: 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4',
trigger: 'onNuxtReady',
proxy: false,
bundle: false
}
}
}
}
});
Sitemap zeroRuntime
After upgrading to Nuxt v4, I noticed that the sitemap was not being generated correctly. I was using the @nuxtjs/sitemap module, which is compatible with Nuxt v4, but it was not generating any dynamic sources for the sitemap. This was likely due to a change in how Nuxt v4 handles dynamic imports and modules.
[@nuxtjs/sitemap 3:49:49 PM] ℹ No dynamic sources detected. Consider enabling zeroRuntime to reduce server bundle size. See https://nuxtseo.com/sitemap/guides/zero-runtime
The warning message suggested enabling zeroRuntime to reduce server bundle size, which I did by adding the following configuration to nuxt.config.ts:
export default defineNuxtConfig({
sitemap: {
zeroRuntime: true
}
});
Sourcemap Client Only
After upgrading to Nuxt v4, other warnings were about missing sourcemaps for sub dependencies, for example in module-preload-polyfill which is a dependency of NuxtScripts.
WARN [plugin nuxt:module-preload-polyfill] Sourcemap is likely to be incorrect: a plugin (nuxt:module-preload-polyfill) was
used to transform files, but didn't generate a sourcemap for the transformation. Consult the plugin documentation for help
I was able to resolve this by enabling sourcemaps in the client only. A static web site does not need server-side sourcemaps, so this reduces the bundle size, as well. You can choose client-side sourcemaps in nuxt.config.ts like this:
sourcemap: {
client: true,
},
Full Configuration Diff
Here is the diff of the nuxt.config.ts file showing all the configuration changes I made to upgrade to Nuxt v4:
export default defineNuxtConfig({
modules: [
- 'nuxt-cloudflare-analytics'
+ '@nuxt/scripts',
],
- cloudflareAnalytics: {
- token: 'XXXXXXXXXXXXXXX'
- },
+ $production: {
+ scripts: {
+ registry: {
+ cloudflareWebAnalytics: {
+ token: 'XXXXXXXXXXXXXXXX',
+ trigger: 'onNuxtReady',
+ proxy: false,
+ bundle: false
+ }
+ }
+ },
+ },
+
sitemap: {
- strictNuxtContentPaths: true
+ zeroRuntime: true
},
+ sourcemap: {
+ client: true,
+ },
+ vite: {
+ optimizeDeps: {
+ include: [
+ '@vue/devtools-core',
+ '@vue/devtools-kit',
+ 'zod',
+ ]
+ }
+ }
})