Nuxt, or Vue with everything else included
A Vue project starts with a router, then server rendering arrives, then a data layer, then metadata handling and a sitemap. A month later half the work has gone into gluing together things other ecosystems ship in the box.
Nuxt closes that gap. It adds file based routing, server rendering in several modes, a backend layer, automatic imports, and a module system on top of Vue. It plays the role for Vue that Next.js plays for React, while going further towards full stack, since the server layer here is a separate, standalone engine.
Version status in mid 2026
Start with the thing that carries the most practical weight for this framework right now.
Nuxt 3 reached end of life on 31 July 2026. That is not a distant announcement but a fact from a few days ago, so a project sitting on version three no longer receives security fixes from the main repository. Commercial extended support from third parties exists, but that is a bridge rather than a destination.
Nuxt 4 is the current stable version and evolves incrementally. Release 4.5 closes a stage after which the team's attention turns to preparing version five and the tooling that eases the move.
Nuxt 5 remains in development with no firm release date, though the official roadmap estimates the fourth quarter of 2026. Version five is meant to bring version three of the Nitro server engine along with further breaking changes, split across two releases precisely so the ecosystem has time to adjust. The conclusion for anyone planning work is simple: today the migration target is version four, not waiting for five.
Migrating from three to four is gentle by design, since most changes could be enabled earlier through a compatibility flag. The largest visible difference is the directory layout, where application code moves into its own directory to separate it from server files and configuration.
Your first project
npx nuxi@latest init my-app
cd my-app
npm install
npm run devThe structure after installation is predictable, and that is one of this framework's stronger points. The pages directory maps to routes, the components directory is imported automatically, and the server directory holds API endpoints.
app/
pages/
index.vue
blog/[slug].vue
components/
ArticleCard.vue
layouts/
default.vue
server/
api/
articles.get.ts
nuxt.config.tsAutomatic imports cover components, composables, and helpers. A component from the components directory is used in a template with no import line, as are the framework's built in functions. That is convenient and confusing at first, since the origin of a given name is hard to guess. The remedy is jumping to definition from the editor, which works correctly thanks to types generated at startup.
Rendering modes
The most important architectural decision concerns where and when the HTML is produced, and Nuxt lets you settle it separately for each path.
Server side rendering generates the page on every request. Content is always current, at the cost of server work and a time to first byte tied to the data source.
Static generation builds pages once, at deploy time. The result is a file, so the response is instant and cheap, at the cost of freshness.
Client only rendering ships an empty shell and builds the view in the browser. Sensible for dashboards behind a login, pointless for content meant to be indexed.
Incremental regeneration blends the first two: the page is static but refreshes after a set interval.
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true },
'/blog/**': { isr: 3600 },
'/dashboard/**': { ssr: false },
'/api/**': { cors: true }
}
})That configuration is among the best designed things in this framework. Rather than picking a mode for the whole application, you describe behaviour per path in one place, and a marketing site, a blog, and a user dashboard can run in three different modes within a single deployment.
Note, though, that not every mode works the same everywhere. Incremental regeneration needs support on the hosting side, so before choosing a provider check that your mode is actually supported there rather than merely accepted in configuration.
Data fetching
This is where the framework departs most from plain Vue, and where misunderstandings arise most often.
<script setup lang="ts">
const { data: articles, status, error } = await useFetch('/api/articles', {
query: { limit: 10 }
})
</script>
<template>
<div v-if="status === 'pending'">Loading</div>
<ArticleCard v-for="a in articles" :key="a.id" :article="a" />
</template>The key property is that the request runs on the server during the first render, and the result reaches the browser alongside the HTML. The client does not repeat that request on startup, since the data already sits in the page payload.
Hence the most common mistake: using a plain network call in place of the framework's function. It works, and it runs twice, once on the server and once in the browser, doubling traffic to your API and causing content to flash.
The second trap is cache keys. Results are cached under a key derived from the address and parameters, so two different requests against the same address with different dynamic parameters need an explicit key, otherwise one overwrites the other.
The third is blocking rendering needlessly. Secondary data, a list of related articles for instance, need not hold up the first response. A lazy fetching option lets the page ship immediately with the rest pulled in on the client.
The server layer
The server directory turns a frontend project into a full stack application, and that is the difference from plain Vue with a router.
export default defineEventHandler(async (event) => {
const { limit } = getQuery(event)
const articles = await db.article.findMany({ take: Number(limit) || 10 })
return articles
})The file name determines the route and method, and the function body runs on the server, so API keys and the database connection never reach the browser. It pairs well with the data access layer covered in the piece on Prisma.
The server engine is independent of the framework and builds output matched to the target environment: a classic Node server, a serverless function, or an edge runtime. That means the hosting choice is largely reversible, since what changes is a build setting rather than application code.
The edge runtime carries limitations worth knowing before deployment, though. It lacks the full set of built in Node modules, so a library reaching for the file system or for native cryptography simply will not run. The same barrier applies to database drivers that open TCP sockets, which is why edge deployments reach for services exposed over plain HTTP, Upstash among them. The price is that every command carries HTTP overhead, so a loop of fifty operations costs noticeably more there than it would over TCP. Checking that early saves rewriting the data layer the day before launch.
Modules
The extension ecosystem here is stronger than in most frameworks of this class and often decides the choice.
The image module optimises graphics and generates size variants. The metadata and sitemap modules cover the search visibility layer. The state management module wires a store in with automatic imports. Modules also exist for Markdown based content, for multiple languages, and for the authentication layer.
export default defineNuxtConfig({
modules: [
'@nuxt/image',
'@pinia/nuxt',
'@nuxtjs/i18n',
'@nuxtjs/tailwindcss'
]
})A module's value lies in hooking not only into application runtime but into the build process as well. A module can add routes, extend configuration, and generate types, so the integration often runs deeper than installing an ordinary library.
The flip side is predictable: every module is a dependency that must keep up with framework versions. During migration between major releases it is usually modules rather than application code that decide when the move is possible. Before starting a migration, check compatibility across every module in use, since one unmaintained add on can block the whole thing.
It also pays to keep their number down to what is genuinely needed. A module added to save twenty lines of code stays in the project for years and shows up at every update.
Metadata and search visibility
Server rendering alone is not enough for a page to index well, and that misunderstanding comes up often.
Title and description are set per page rather than once for the whole application. The framework provides a composable you call inside the page component, so the description can depend on fetched data.
const { data: article } = await useFetch(`/api/articles/${slug}`)
useSeoMeta({
title: () => article.value?.title,
description: () => article.value?.summary,
ogImage: () => article.value?.cover
})Writing these as functions matters, since the values change along with the data. Passing plain values while the article is still loading writes empty tags that never update.
The canonical address matters just as much and often gets skipped. The same content reachable at several addresses, with a tracking parameter for instance, or with and without a trailing slash, splits signals between variants. Declaring the primary address explicitly settles that permanently.
The sitemap and the robots rules file come from modules and deserve enabling at project start rather than after launch. A sitemap generated automatically from routes stays current, while a hand maintained one stops being current within a fortnight.
Layers in larger projects
The layers mechanism serves organisations maintaining several applications around a shared core, and it is the least known part of the framework.
A layer is an ordinary project from which another project inherits configuration, components, composables, and even pages. A company with five sites can keep a shared theme, header, and authentication layer in one place, overriding only what differs in each application.
export default defineNuxtConfig({
extends: ['@company/base-layer']
})Overriding works by file name, so a component with the same name in the child project replaces the one from the layer. That is convenient and calls for care, since an accidental name collision produces a substitution nobody planned.
A layer is published as an ordinary package or pointed at by repository directly, which lets you start without building a release process. Across several applications, though, move to a versioned package quickly, since otherwise a change in the shared core reaches every project at once, with no way to roll it out singly and check the consequences.
Nuxt against the alternatives
| Option | Strength | Weakness | Pick it when |
|---|---|---|---|
| Nuxt | Complete out of the box, modules, server layer | Plenty of magic, many conventions | A Vue project needing search visibility |
| Next.js | Largest ecosystem, mature deployments | Tied to React | A team working in React |
| Vite plus Vue | Full control, no magic | You assemble everything | An app behind a login with no indexing need |
| Astro | Minimal JavaScript in the output | Weaker under heavy interactivity | Content led sites |
The decision reduces to two questions. First: does the team work in Vue or React, which settles the first two rows. Second: does the content need indexing, since an application entirely behind a login needs no server rendering and a simpler stack then wins.
Common mistakes
The first is staying on Nuxt 3 past end of life. Missing security fixes is not a convenience matter but a real risk, and migrating to version four is gentle by design.
The second is plain network calls instead of the framework's functions. The result is double fetching and content flashing on page startup.
The third is reaching for browser objects during rendering. Code runs on the server first, where those objects do not exist, so access to them belongs in a function running after the component mounts.
The fourth is keeping API keys in variables available client side. Configuration distinguishes private from public values, and a public value ends up in the bundle sent to the browser.
The fifth is one rendering mode for the whole application. A dashboard behind a login need not be indexed, and a home page need not be generated on every request.
The sixth is choosing hosting without checking support for your chosen mode. Incremental regeneration does not work everywhere, and discovering that at deploy time hurts.
FAQ
Is Nuxt 3 still supported?
No, support ended on 31 July 2026. Projects on that version no longer receive security fixes from the main repository. Commercial extended support is available, though the right direction is migrating to Nuxt 4.
Is it worth waiting for Nuxt 5?
No. The roadmap estimates version five for the fourth quarter of 2026 without naming a date, while version four is stable and actively developed. Migrating from three to four is gentle, so waiting only means longer without security fixes.
How does Nuxt differ from Next.js?
In the view layer and the philosophy. Next.js sits on React, Nuxt on Vue. Nuxt gives more in the box, including a standalone server engine and an extensive module system, at the cost of more conventions to learn.
Do I need a server to deploy?
Not always. The application can be built as a set of static files and served from any file host. A server becomes necessary only once you use rendering on request, incremental regeneration, or API endpoints.
Does Nuxt suit large applications?
Yes, provided the team accepts its conventions. Automatic imports and file based routing shorten code while demanding discipline in directory organisation. On larger projects the layers mechanism helps, letting you share configuration and components across applications.
Documentation sits on the project site, and development plans are described in the official roadmap.