Nuxt 4 — The Fullstack Vue Framework for Production in 2026
Posted on: 4/26/2026 12:13:31 AM
Table of contents
- 1. New Directory Structure — Separating app/ and server/
- 2. TypeScript — Separate Type Projects
- 3. Data Fetching — useFetch Factories and Smart Caching
- 4. Hybrid Rendering — ISR, SWR, Edge and Route Rules
- 5. Nitro — Universal Server Engine
- 6. Nuxt 4.4 — Latest Feature Highlights
- 7. Performance Optimization — From Build to Runtime
- 8. Nuxt 4 vs Next.js 15 — Practical Comparison
- 9. Migrating from Nuxt 3 to Nuxt 4
- 10. Production Best Practices
- 11. Conclusion
If you're building web applications with Vue.js and need a truly fullstack framework for production — spanning SSR, ISR, edge rendering, and optimized TypeScript — then Nuxt 4 is the answer in 2026. Released stable in July 2025 and continuously improved through version 4.4 (March 2026), Nuxt 4 is not just a regular upgrade but a comprehensive restructuring of project architecture, data fetching, rendering strategies, and developer experience. This article dives deep into every critical aspect of Nuxt 4 — from the new directory structure, hybrid rendering, custom useFetch factories, to edge deployment strategies with Nitro.
1. New Directory Structure — Separating app/ and server/
The most significant and visible change in Nuxt 4 is the directory structure. All client application code moves into the app/ directory, completely separated from server/, shared/, and root configuration files.
my-nuxt-app/
├─ app/ # Application code (client + universal)
│ ├─ assets/
│ ├─ components/
│ ├─ composables/
│ ├─ layouts/
│ ├─ middleware/
│ ├─ pages/
│ ├─ plugins/
│ ├─ utils/
│ ├─ app.vue
│ └─ app.config.ts
├─ shared/ # Code shared between client & server
│ ├─ types/
│ └─ utils/
├─ server/ # API routes, server middleware
│ ├─ api/
│ ├─ middleware/
│ └─ utils/
├─ public/
├─ content/
├─ nuxt.config.ts
└─ tsconfig.json # Only 1 file needed
Why does this matter?
On Windows and Linux, file watchers perform significantly faster when they don't have to monitor node_modules/ and .git/ at the same level as source code. IDEs also gain clearer distinction between client and server code, resulting in more accurate autocompletion. Legacy project structures continue working without mandatory migration.
2. TypeScript — Separate Type Projects
Nuxt 4 creates separate TypeScript projects for each context: app/, server/, shared/, and builder code. This means when you write code in server/api/, TypeScript only suggests APIs available on the server (Node.js APIs, database clients...) without mixing in DOM APIs or Vue composables. Conversely, code in app/ cannot see server utilities unless they reside in shared/.
// shared/types/user.ts — available in both client and server
export interface User {
id: number
name: string
email: string
role: 'admin' | 'editor' | 'viewer'
}
// server/api/users/[id].get.ts — runs on server only
import type { User } from '~/shared/types/user'
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
// database query, server-only logic
return { user } satisfies { user: User }
})
// app/pages/users/[id].vue — runs on client (and SSR)
const { data: user } = await useFetch<User>(`/api/users/${route.params.id}`)
Only one tsconfig.json file is needed at the project root — Nuxt automatically generates the appropriate project references. Previously, many projects had to maintain 2-3 separate tsconfig files, which is no longer necessary.
3. Data Fetching — useFetch Factories and Smart Caching
Nuxt 4.4 introduces createUseFetch and createUseAsyncData — allowing you to create custom versions of useFetch with their own default options. This is a game-changer for large projects that need to call different APIs with separate base URLs, headers, and error handling.
// app/composables/useApiFetch.ts
export const useApiFetch = createUseFetch((currentOptions) => {
const config = useRuntimeConfig()
const { token } = useAuth()
return {
...currentOptions,
baseURL: currentOptions.baseURL ?? config.public.apiBase,
headers: {
...currentOptions.headers,
Authorization: `Bearer ${token.value}`,
},
onResponseError({ response }) {
if (response.status === 401) navigateTo('/login')
},
}
})
// app/composables/usePublicFetch.ts — no auth needed
export const usePublicFetch = createUseFetch({
server: false, // fetch on client only
baseURL: '/api/public',
})
Usage in components feels just as natural as the native useFetch:
<script setup lang="ts">
// Automatically includes auth headers, base URL, error handling
const { data: posts } = await useApiFetch<Post[]>('/posts')
// Public API, client-side only
const { data: stats } = await usePublicFetch<Stats>('/stats')
</script>
Automatic data sharing
When multiple components call useFetch or useAsyncData with the same key, Nuxt 4 automatically shares the data instead of making duplicate API calls. When a component unmounts, its data is automatically cleaned up — no manual management required.
4. Hybrid Rendering — ISR, SWR, Edge and Route Rules
One of Nuxt 4's greatest advantages over other frameworks is the ability to combine multiple rendering strategies within the same application through Route Rules. Each route can be individually configured with its own rendering approach.
graph TB
REQ["User Request"] --> RR{"Route Rules Engine"}
RR -->|"/blog/*"| ISR["ISR
Cache 1h, CDN edge"]
RR -->|"/dashboard/*"| SSR["SSR
Server render per request"]
RR -->|"/about"| SSG["SSG
Pre-rendered at build"]
RR -->|"/api/*"| SWR["SWR
Stale-While-Revalidate"]
ISR --> CDN["CDN Edge Cache"]
SSR --> SERVER["Nitro Server"]
SSG --> STATIC["Static Files"]
SWR --> CACHE["In-Memory LRU Cache"]
CDN --> USER["Response ~50ms TTFB"]
SERVER --> USER
STATIC --> USER
CACHE --> USER
style REQ fill:#e94560,stroke:#fff,color:#fff
style RR fill:#2c3e50,stroke:#fff,color:#fff
style ISR fill:#4CAF50,stroke:#fff,color:#fff
style SSR fill:#2196F3,stroke:#fff,color:#fff
style SSG fill:#ff9800,stroke:#fff,color:#fff
style SWR fill:#9C27B0,stroke:#fff,color:#fff
style USER fill:#e94560,stroke:#fff,color:#fff
Figure 1: Hybrid Rendering — each route rendered with its most suitable strategy
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
// Blog posts: ISR with 1-hour TTL
'/blog/**': { isr: 3600 },
// Homepage: SWR - serve stale, revalidate in background
'/': { swr: 600 },
// Dashboard: always SSR, needs real-time data
'/dashboard/**': { ssr: true },
// Landing pages: pre-render at build time
'/about': { prerender: true },
'/pricing': { prerender: true },
// API: CORS + cache
'/api/**': {
cors: true,
headers: { 'Cache-Control': 'max-age=60' },
},
},
})
| Strategy | TTFB | Best for | Trade-off |
|---|---|---|---|
| SSG (Static) | ~10ms | Landing pages, docs, about | Requires rebuild when content changes |
| ISR (Incremental) | ~50ms | Blog, catalog, marketing | Data may be stale within TTL |
| SWR (Stale-While-Revalidate) | ~50ms | API responses, feeds | First request may be slow |
| SSR (Server) | ~200ms | Dashboard, user-specific content | Every request triggers a fresh render |
| Edge SSR | ~30ms | Global content, needs freshness | Limited Node.js APIs |
5. Nitro — Universal Server Engine
Behind Nuxt 4 is Nitro — the server engine that enables deploying the same codebase anywhere: Node.js server, Cloudflare Workers, Vercel Edge Functions, AWS Lambda, Deno Deploy, or Bun. Nitro automatically optimizes build output for each target platform.
// server/api/products/[id].get.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
// Using Nitro built-in KV storage
const cached = await useStorage('cache').getItem(`product:${id}`)
if (cached) return cached
const product = await db.query('SELECT * FROM products WHERE id = ?', [id])
// Cache for 5 minutes
await useStorage('cache').setItem(`product:${id}`, product, { ttl: 300 })
return product
})
// server/api/upload.post.ts — File upload with multipart
export default defineEventHandler(async (event) => {
const files = await readMultipartFormData(event)
if (!files?.length) throw createError({ statusCode: 400, message: 'No file' })
const file = files[0]
// Process file: save to R2, S3, or local
await useStorage('assets').setItemRaw(`uploads/${file.filename}`, file.data)
return { url: `/uploads/${file.filename}` }
})
Popular deploy targets
Nitro supports 15+ deployment targets: Node.js (default), Cloudflare Workers/Pages, Vercel Edge/Serverless, Netlify Edge, AWS Lambda, Azure Functions, Deno Deploy, Bun, Docker. Just change nitro.preset in config — no application code changes needed.
6. Nuxt 4.4 — Latest Feature Highlights
6.1. Typed Layout Props
Pages can now pass props directly to layouts via definePageMeta — with full type safety:
<!-- app/layouts/panel.vue -->
<script setup lang="ts">
defineProps<{
sidebar?: boolean
title?: string
}>()
</script>
<template>
<div class="panel-layout">
<aside v-if="sidebar">...</aside>
<main>
<h1 v-if="title">{{ title }}</h1>
<slot />
</main>
</div>
</template>
<!-- app/pages/dashboard.vue -->
<script setup lang="ts">
definePageMeta({
layout: {
name: 'panel',
props: { sidebar: true, title: 'Dashboard' }, // ✅ autocomplete works
},
})
</script>
6.2. Payload Extraction Mode
For applications using ISR/SWR, Nuxt 4.4 provides payloadExtraction: 'client' mode — inlining the full payload into the initial HTML while generating _payload.json for client-side navigation. Combined with runtime LRU cache, serverless functions avoid re-rendering when serving payloads:
export default defineNuxtConfig({
experimental: {
payloadExtraction: 'client',
},
})
6.3. Accessibility Announcer
The new useAnnouncer composable notifies dynamic content to screen readers — essential for accessibility compliance:
const { polite, assertive } = useAnnouncer()
async function addToCart(item: Product) {
await $fetch('/api/cart', { method: 'POST', body: { itemId: item.id } })
polite(`Added ${item.name} to cart`) // Screen reader announces when idle
}
function showCriticalError(msg: string) {
assertive(msg) // Screen reader announces immediately
}
6.4. Build Profiling
The nuxt build --profile command outputs Chrome Trace, JSON report, and CPU profile — enabling detailed analysis of build time for each module, plugin, and transform:
nuxt build --profile
# Output:
# ✓ .nuxt/perf-trace.json → open in Chrome DevTools
# ✓ .nuxt/perf-report.json → long-term tracking
# ✓ nuxt-build.cpuprofile → flame graph
7. Performance Optimization — From Build to Runtime
7.1. Bundle Optimization
// nuxt.config.ts — lazy load heavy components
export default defineNuxtConfig({
components: [
{ path: '~/components', pathPrefix: false },
{ path: '~/components/heavy', prefix: 'Heavy', global: false },
],
})
<!-- Lazy load components only when needed -->
<template>
<LazyHeavyChart v-if="showChart" :data="chartData" />
<LazyHeavyEditor v-if="isEditing" :content="content" />
</template>
7.2. Image Optimization with Nuxt Image
<template>
<NuxtImg
src="/hero.jpg"
format="avif,webp"
width="1200"
height="600"
sizes="sm:100vw md:80vw lg:1200px"
loading="lazy"
placeholder
/>
</template>
7.3. View Transitions API
Nuxt 4.4 extends View Transitions support with type definitions — enabling different animations depending on navigation type:
export default defineNuxtConfig({
experimental: {
viewTransition: true,
},
})
<script setup lang="ts">
definePageMeta({
viewTransition: {
type: 'slide-left', // forward navigation
},
})
</script>
8. Nuxt 4 vs Next.js 15 — Practical Comparison
| Criteria | Nuxt 4 | Next.js 15 |
|---|---|---|
| Base framework | Vue 3.6 | React 19 |
| Server engine | Nitro (universal, 15+ targets) | Built-in (Vercel-optimized) |
| Rendering modes | SSR, SSG, ISR, SWR, Edge — per route | SSR, SSG, ISR — per route |
| Auto-imports | Composables, components, utils | None (manual imports) |
| File-based routing | Yes (Vue Router v5) | Yes (App Router) |
| State management | useState + Pinia (built-in) | React Context + Zustand/Jotai |
| Edge deployment | Cloudflare, Vercel, Netlify, Deno... | Vercel Edge (native), others via adapter |
| Learning curve | Low — Vue Options/Composition API | High — RSC, Server Actions, Suspense |
| Bundle size (starter) | ~45KB gzipped | ~85KB gzipped |
9. Migrating from Nuxt 3 to Nuxt 4
npx nuxt upgrade --dedupe to update Nuxt and deduplicate the lockfile. Check module compatibility.npx codemod@latest nuxt/4/migration-recipe to automatically transform most breaking changes.app/ leverages IDE performance gains and type separation.nuxi typecheck — the new type projects may surface previously hidden type errors.Note for module authors
Nuxt 2 compatibility has been completely removed from @nuxt/kit in Nuxt 4. If you maintain a Nuxt module, you need to update it to support Nuxt 3+/4+ only.
10. Production Best Practices
// nuxt.config.ts — production-ready configuration
export default defineNuxtConfig({
// Hybrid rendering
routeRules: {
'/': { swr: 600 },
'/blog/**': { isr: 3600 },
'/app/**': { ssr: true },
'/admin/**': { ssr: false }, // SPA mode for admin
},
// Performance
experimental: {
payloadExtraction: 'client',
viewTransition: true,
},
// Nitro — deploy target
nitro: {
preset: 'cloudflare-pages', // or 'node-server', 'vercel-edge'
compressPublicAssets: true,
minify: true,
},
// Vite optimizations
vite: {
build: {
cssCodeSplit: true,
rollupOptions: {
output: {
manualChunks: {
'vue-vendor': ['vue', 'vue-router', 'pinia'],
},
},
},
},
},
})
Pre-launch checklist
1. Configured routeRules appropriately for each route group.
2. Enabled payloadExtraction: 'client' if using ISR/SWR.
3. Ran nuxt build --profile to verify no module builds excessively slowly.
4. Tested on target deployment platform (Cloudflare, Vercel...) not just locally.
5. Enabled compression (gzip/brotli) at reverse proxy or CDN level.
6. Images use <NuxtImg> with avif/webp formats.
11. Conclusion
Nuxt 4 is not merely the next version of Nuxt 3 — it marks the maturity of the Vue.js ecosystem in directly competing with React/Next.js in the fullstack framework segment. With a cleaner project structure, optimized TypeScript, flexible hybrid rendering, custom useFetch factories, and Nitro supporting deployment to any platform, Nuxt 4 is production-ready for projects of all scales — from personal blogs to enterprise SaaS.
Version 4.4 continues raising the bar with typed layout props, an accessibility announcer, build profiling, and payload optimization — features demonstrating that the Nuxt team invests not only in feature parity but deeply in real-world developer experience and performance.
References:
Announcing Nuxt 4.0 — Nuxt Blog
Nuxt 4.4 Release Notes — Nuxt Blog
Rendering Modes — Nuxt 4 Docs
Performance Best Practices — Nuxt 4 Docs
Vue, Nuxt & Vite Status in 2026 — Five Jars
Rspack — The Rust-Powered Bundler That Replaces Webpack with 20x Faster Builds
Deno 2 — The Next-Gen JavaScript Runtime: Secure by Default, Native TypeScript & NPM Compatible
Disclaimer: The opinions expressed in this blog are solely my own and do not reflect the views or opinions of my employer or any affiliated organizations. The content provided is for informational and educational purposes only and should not be taken as professional advice. While I strive to provide accurate and up-to-date information, I make no warranties or guarantees about the completeness, reliability, or accuracy of the content. Readers are encouraged to verify the information and seek independent advice as needed. I disclaim any liability for decisions or actions taken based on the content of this blog.