I’ve been using Nuxt since the 2.x days. Back then it was the default choice for SSR in the Vue world, and by far the most established option. It worked, but it had its rough edges. Build times were painful, the module system was fragile, and if you wanted to do anything slightly outside the happy path, you were reading source code on GitHub at 11pm.
Nuxt 3 is a different framework. Not in the “we renamed some things and rewrote the docs” sense. Genuinely different under the hood. Nitro, the new server engine, changes the deployment story completely. The composition API is a first-class citizen instead of an afterthought. Auto-imports actually work without fighting the tooling. TypeScript support went from “it sort of works if you squint” to properly integrated.
I’ve shipped a few projects on Nuxt 3 now — a marketing site, a SaaS platform, a couple of smaller tools — and I’ve formed some opinions. Some enthusiastic, some less so.
What Nuxt 3 Gets Right
The Composition API feels native
In Nuxt 2, using the Composition API always felt like you were bolting something on. You’d install @nuxtjs/composition-api, and it kind of worked, but the DX was never smooth. useContext() was finicky. useFetch and useAsync behaved differently depending on the rendering mode. It was workable, but you could tell the framework wasn't designed for it.
In Nuxt 3, the Composition API is the foundation. useFetch, useAsyncData, useState, useHead — they all feel like they belong there. Data fetching with useFetch in particular is one of those things where, once you've used it, going back to managing loading states and error handling manually feels unnecessarily painful. (Worth noting: it's an SSR-friendly wrapper around useAsyncData and $fetch — if you use raw $fetch in components without wrapping it, you'll get duplicate server/client fetching. But the abstraction itself is solid.)
The composable pattern also makes code reuse cleaner. I’ve got a project where shared logic lives in composables/ and gets auto-imported across pages and components. No explicit imports, no registration. It just works. That sounds like it shouldn't be a big deal, but it removes a surprising amount of friction from day-to-day development.
Nitro changes the deploy story
This is the big one for me. Nuxt 2 was essentially “Node.js SSR server or static export, pick one.” If you wanted to deploy to anything that wasn’t a traditional server, you were fighting the framework.
Nitro makes Nuxt 3 far more deployment-flexible than Nuxt 2. You can target Node, serverless, edge, or static hosting from the same Nuxt architecture, which is a massive shift from the old deployment story. The server routes you write in server/api/ work across targets, though runtime differences still exist at the edges.
I’ve got one project running on Cloudflare Pages with edge-side rendering and another on a plain Netlify deploy. Same framework, same patterns, wildly different infrastructure. Nitro abstracts the runtime away, and for the most part, it actually delivers on that promise.
The server/ directory is also a nice touch. Having API routes co-located with your frontend code, using the same TypeScript setup, sharing types between client and server — it's a small thing that makes full-stack development feel more coherent than having a separate Express or Fastify backend.
Auto-imports are actually good now
I was skeptical about auto-imports. I’ve been burned by magic in frameworks before — things that work until they don’t, and then you have no idea why.
But Nuxt 3’s auto-import system is well-implemented. Components from components/, composables from composables/, utilities from utils/ — they're all available without explicit imports. The TypeScript integration means you still get full type inference, autocomplete, and jump-to-definition in your editor.
The key difference from previous magic-import systems is that it’s backed by proper type generation. There’s a .nuxt/ directory with generated types. Your IDE knows what's available. It's not guessing.
The module ecosystem is maturing
Nuxt’s module ecosystem is getting much stronger — from image and font tooling to SEO-focused modules and utilities. Adding something like SEO management or image optimization used to require a lot of manual configuration. Now it’s usually a module install and a few lines in nuxt.config.ts.
The module system itself is cleaner too. Writing custom modules is more straightforward than in Nuxt 2, and the hooks system gives you real extension points rather than the “override this internal thing and hope it doesn’t break” approach.
Where It Still Trips You Up
Hydration mismatches are still painful
This is my biggest complaint. If you ship SSR with Nuxt 3, you’ll likely run into hydration mismatches at some point. They’re not bugs in Nuxt specifically — they’re a fundamental challenge of SSR — but they are harder to debug than they should be.
The typical scenario: something renders differently on the server than on the client. A date formatted differently because of timezone. A random ID generated twice. A conditional that depends on window. Vue's hydration warning in the console tells you that there's a mismatch, but not always where or why in a way that's easy to trace.
I’ve lost hours to hydration issues. The fix is usually straightforward once you find it — wrap it in <ClientOnly>, use onMounted, or make the logic isomorphic. But the debugging experience could be better.
The “up but broken” deploy problem
This is something that caught me off guard and that I think a lot of Nuxt developers don’t think about enough.
Nuxt 3 produces hashed filenames for your JavaScript and CSS bundles. Every deploy generates new hashes. The HTML document references those specific files. This is standard for any modern frontend framework — it’s great for cache busting.
The problem is what happens at the CDN level. If your CDN is still serving the old HTML at some edge locations after a deploy, that HTML references bundle hashes that no longer exist. The page loads. The browser requests _nuxt/entry.a4f2c.js. That file is gone. The app doesn't boot.
The user sees a blank page. Your uptime monitor sees 200 OK. And if you're running Nuxt behind Cloudflare or a similar CDN, there's another fun one: if the CDN can't find your JS file, it might return its own HTML error page — with Content-Type: text/html. The browser silently blocks it because it expected application/javascript. Your app is dead, but every health check says it's fine.
This was a blind spot for me until I added checks that validate whether the HTML references actually resolve to the right assets with the right MIME types. I’ve been using Sitewatch for this — but whatever approach you use, if you’re shipping Nuxt to CDN-backed platforms, verifying asset integrity after deploys is worth the effort.
The docs are better but still have gaps
The Nuxt 3 docs are dramatically better than they used to be. The migration guide is solid. The core concepts are well-explained.
But once you get past the basics, you start finding gaps. Specific configuration options that aren’t documented. Edge cases with route middleware that you have to figure out from the source or community discussions. The nitro configuration section is particularly sparse for how powerful it is.
This isn’t a dealbreaker — the framework is open source and the community is active. But once you get into advanced Nitro and middleware cases, you still end up in source code, GitHub issues, and Discord faster than I’d like.
Ecosystem size still matters
Vue’s ecosystem is smaller than React’s. That’s not news. But it matters in practical terms when you’re building something and you need a specific library — a complex table component, an animation library, a specific integration — and the Vue 3 version either doesn’t exist or is maintained by a single person.
This has gotten better over the past couple of years. But it’s still a factor, especially for larger projects where you need production-grade libraries for things like data grids, rich text editors, or complex form handling.
My Honest Take
Nuxt 3 is the best it’s ever been. The developer experience is genuinely excellent for most use cases. If you’re building a marketing site, a SaaS frontend, a content-heavy site, or a full-stack app with Vue — Nuxt 3 is the right choice. I wouldn’t go back to Nuxt 2, and I wouldn’t reach for a manual Vue + Vite setup for anything that needs SSR or hybrid rendering.
But it’s not magic. You still need to understand SSR to debug hydration issues. You still need to think about CDN caching and deploy artifacts. You still need to verify that your production site actually works after a deploy — because the framework can’t tell you that.
The deploy and monitoring gap is something I think the Vue/Nuxt community talks about less than it should. We spend a lot of time on DX, build tooling, and component architecture. We don’t spend enough time on “what happens after npm run build finishes." That's where the silent failures live.
Thanks for reading and I hope you found this useful. If so, please help support me by hitting that clap button.
If you’d like to catch up with me sometime, follow me on Twitter | LinkedIn or simply visit my website.
P.S.: First, you should get my posts in your inbox. Do that here!
Secondly, consider supporting me and thousands of other writers by signing up for a membership on Medium. It only costs $5 per month, it supports us, writers, greatly, and you have the chance to make money with your writing as well. By signing up with this link, you’ll support me directly with a portion of your fee, it won’t cost you more. If you do so, thank you a million times.
Comments
Loading comments…