Nitro v3: What’s Coming with Nuxt 5

Heads-up! Nitro v3 is still under active development. The features and APIs discussed here can change before the final release. Nuxt 5 will ship with Nitro v3 once it's stable.

Michael Thiessen
Nuxt 3

The Mastering Nuxt FullStack Unleashed Course is here!

Get notified when we release new tutorials, lessons, and other expert Nuxt content.

Click here to view course

Nitro v3 represents the biggest update to Nuxt's server engine since its creation. But this isn't just a minor version bump with a few tweaks.

Nitro v3 is a complete overhaul with major new features:

  • Built-in task runners
  • Scheduled cron jobs
  • Cross-environment WebSocket support
  • H3 v2 with srvx

And some breaking changes:

  • Dropped Node 16 support
  • Removed app config functionality
  • Changed default caching behavior

So let's dive into the new features and breaking changes that will transform how you build full-stack applications in Nuxt.

Built-in Task Runner: Your New Best Friend

The biggest addition to Nitro v3 is the brand new Tasks API. You can now create custom server tasks that run on-demand or automatically.

Here's how you create a task:

// server/tasks/backup-database.js
export default defineTask({
  meta: {
    name: 'db:backup',
    description: 'Create a full database backup'
  },
  async run({ payload, context }) {
    const timestamp = new Date().toISOString()
    const backupName = `backup-${timestamp}.sql`

    // Your backup logic here
    await createDatabaseBackup(backupName)

    return {
      success: true,
      backupName,
      timestamp
    }
  }
})

The task system gives you a clean way to handle one-off operations. You can trigger data seeding, run cleanup scripts, or perform maintenance operations without cluttering your API routes.

But the real power comes from how you can execute these tasks. During development, Nitro exposes special endpoints for task management:

  • /_nitro/tasks lists all available tasks
  • /_nitro/tasks/db:backup executes your backup task directly

You can also run tasks programmatically from anywhere in your application:

// api/admin/backup.js
export default defineEventHandler(async (event) => {
  // Trigger the backup task
  const result = await runTask('db:backup', {
    payload: { urgent: true }
  })

  return {
    message: 'Backup completed',
    details: result
  }
})

This approach keeps your task logic separate from your API logic. Your endpoints stay focused on handling requests while tasks handle the heavy lifting.

You can also query task status and results through the CLI, making it easier to monitor background operations during development.

Scheduled Cron Tasks: Background Jobs Made Simple

Tasks become even more powerful when you schedule them. Nitro v3 includes built-in cron job support that works across different deployment platforms.

You configure scheduled tasks in your Nitro config:

// nitro.config.ts
export default defineNitroConfig({
  scheduledTasks: {
    // Run every 5 minutes
    '*/5 * * * *': ['cache:cleanup'],

    // Daily at midnight
    '0 0 * * *': ['reports:generate', 'logs:archive'],

    // Weekly on Sunday at 2 AM
    '0 2 * * 0': ['db:optimize']
  }
})

Then create the corresponding task files:

// server/tasks/cache-cleanup.js
export default defineTask({
  meta: {
    name: 'cache:cleanup',
    description: 'Remove expired cache entries'
  },
  async run() {
    const expired = await findExpiredCacheEntries()

    for (const entry of expired) {
      await deleteCacheEntry(entry.key)
    }

    return {
      cleaned: expired.length,
      timestamp: Date.now()
    }
  }
})

The scheduling mechanism adapts to your deployment platform. Node deployments use the Croner engine internally, while Cloudflare deployments integrate with Cloudflare Cron Triggers.

This means you get background job capabilities without setting up external services like Redis or additional cron servers. Your scheduled tasks run as part of your application.

You can even combine manual and scheduled execution. A task can be triggered both by a cron schedule and through API calls when needed.

Cross-Environment WebSocket Support

Real-time communication gets a major upgrade with integrated CrossWasm v1 for WebSocket support. Nitro v3 provides a unified WebSocket interface that works across different JavaScript runtimes.

Here's how you can set up WebSocket handling:

// server/api/websocket.js
export default defineWebSocketHandler({
  open(peer) {
    console.log('WebSocket connection opened:', peer.id)
    peer.send('Welcome to the server!')
  },

  message(peer, message) {
    console.log('Received message:', message)

    // Broadcast to all connected clients
    peer.send('Hello from the server!')
  },

  close(peer) {
    console.log('WebSocket connection closed:', peer.id)
  }
})

The WebSocket support abstracts platform differences. Whether you're running on Node.js, Cloudflare Workers, or other edge environments, the same API works consistently.

This unified approach eliminates the need to write platform-specific WebSocket code. You can develop locally with Node.js and deploy to edge environments without changing your WebSocket implementation.

H3 v2 with srvx

Nitro v3 now uses H3 v2 as the default HTTP server library. H3 v2 is a major upgrade to the HTTP server library that Nitro uses. It's a complete rewrite that brings a lot of new features and improvements.

One of the most exciting new features is srvx, which is a new way to write HTTP servers. It's a more modern and flexible way to write HTTP servers.

We'll give you a full guide to srvx in a future article.

Node 18 and Higher Only

Nitro v3 requires Node.js 18 or higher. Support for Node 14 and 16 has been completely dropped.

This change eliminates the need for custom polyfills. Node 18 includes native APIs like global fetch, so Nitro removed its internal fetch polyfill entirely.

But this means you need to audit your deployment environment before upgrading. If you're still running Node 16 in production, you'll need to update your runtime first.

The upside is better performance and access to modern JavaScript features. Nitro can now rely on native APIs instead of maintaining compatibility shims.

Package Renamed to nitro

The NPM package name changed from nitropack to nitro. The legacy nitropack package still exists as a mirror for compatibility, but the primary package is now simply nitro.

I've often been confused by this, since the name is totally different from the package name!

App Config Support Completely Removed

Here's a breaking change that might catch you off guard. Nitro v3 removes support for app.config.ts and the useAppConfig() function entirely.

In Nitro v2, you could create an app config file:

// app.config.ts (NO LONGER WORKS)
export default defineAppConfig({
  apiUrl: 'https://api.example.com',
  features: {
    analytics: true,
    payments: false
  }
})

And access it at runtime:

// This function no longer exists
const config = useAppConfig()

Now, in Nitro v3, you need to migrate to useRuntimeConfig() instead:

// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    public: {
      apiUrl: 'https://api.example.com',
      features: {
        analytics: true,
        payments: false
      }
    }
  }
})

Then access it using the runtime config:

const config = useRuntimeConfig()
const apiUrl = config.public.apiUrl

SWR Disabled by Default (A Caching Behavior Change)

Stale-While-Revalidate (SWR) is now disabled by default for cached functions.

In Nitro v2, cachedFunction and defineCachedEventHandler (which uses cachedFunction under the hood) automatically used SWR:

// Nitro v2 behavior
const getCachedData = cachedFunction(async () => {
  return await fetchExpensiveData()
})

// Would serve stale data while revalidating in background
const data = await getCachedData()

In Nitro v3, you need to explicitly opt into SWR behavior:

// Nitro v3 - explicit SWR configuration
const getCachedData = cachedFunction(async () => {
  return await fetchExpensiveData()
}, {
  swr: true,
  maxAge: 300 // 5 minutes
})

Or configure it through route rules:

// nitro.config.ts
export default defineNitroConfig({
  routeRules: {
    '/api/slow-endpoint': {
      headers: { 'cache-control': 's-maxage=300' },
      swr: 600 // 10 minutes SWR
    }
  }
})

This change prevents the unintuitive caching behaviour that Nitro v2 had, which I've been tripped up by before! So I'm glad to see this change.

Previously, cached responses could live longer than expected due to implicit SWR behavior. Now you have explicit control over when and how SWR caching occurs. Your cached functions will behave more predictably by default.

Async Context Enabled by Default

AsyncLocalStorage context is now enabled by default, preserving request-specific state across asynchronous operations.

This change improves reliability for features like per-request logging and request-scoped data.

The async context makes sure that logging, tracing, and request-scoped data remain accessible throughout the entire request lifecycle. You don't need to manually propagate context through function param.

Faster Build Performance with JITI v2

Build startup performance gets a significant boost through JITI v2 (Just-In-Time Import) integration.

JITI v2 enables native ESM imports where possible, leading to faster configuration loading and module initialization. The result is shorter startup times and faster Hot Module Replacement (HMR) during development.

You'll notice these performance improvements most during initial project startup, configuration changes that trigger rebuilds, and development server restarts.

Looking Forward

Nitro v3 sets the foundation for the next generation of full-stack JavaScript applications. The built-in task runner eliminates the need for external job queues in many scenarios.

The improved WebSocket support opens up possibilities for real-time features without platform-specific code. And the performance optimizations make edge deployment more attractive for applications of all sizes.

But perhaps most importantly, the breaking changes clean up technical debt and establish patterns that will remain stable going forward. Nitro v3 is designed to be the foundation for future development rather than just another incremental update.

The coordination with Nuxt 5's release makes sure that the entire ecosystem moves forward together.

Michael Thiessen
Michael is a passionate full time Vue.js and Nuxt.js educator. His weekly newsletter is sent to over 11,000 Vue developers, and he has written over a hundred articles for his blog and VueSchool.

Follow MasteringNuxt on