Nuxt 4 — The Fullstack Vue Framework for Production in 2026

Posted on: 4/26/2026 12:13:31 AM

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.

4.4Latest version (03/2026)
14,000xFaster module ID parsing
28xFaster route generation (dev)
~50msTTFB with ISR on CDN

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}` }
})

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

Step 1 — Upgrade dependencies
Run npx nuxt upgrade --dedupe to update Nuxt and deduplicate the lockfile. Check module compatibility.
Step 2 — Run automated codemod
Use npx codemod@latest nuxt/4/migration-recipe to automatically transform most breaking changes.
Step 3 — Move to app/ (optional)
The legacy structure still works, but gradually migrating to app/ leverages IDE performance gains and type separation.
Step 4 — Verify TypeScript
Run nuxi typecheck — the new type projects may surface previously hidden type errors.
Step 5 — Test and deploy
Run your test suite, verify the build process, deploy to staging before production.

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