In this blog post, you can explore the latest updates to Nitro, a powerful tool for building platform-agnostic server-side and backend applications. Nitro powers Nuxt and is accessible to everyone!

🌊 Native Response Streaming

Nitro 2.6 includes h3 1.8 for native Web Streams and Response support. It also includes runtime and type-safe request validation utils, event handlers with an object format, and more!

Read more:

Web Streams are now supported for Node.js, Bun, Deno, Cloudflare Workers, and Vercel. For incompatible platforms, Nitro automatically reads the full stream (#1624) and converts it to a compatible format. We are closely collaborating with various teams to expand streaming support across all layers.

📦 Runtime-Key Compatible Export Conditions

Nitro 2.6 now automatically adds export conditions based on the Runtime Keys Proposal (#1401).

Using platform export conditions, Nitro can smartly use the appropriate build of your project libraries for each deployment target. Runtime-key compatible conditions have been introduced to the unjs packages, such as unjs/node-fetch-native and unjs/ofetch, with the intention of standardizing this approach across more packages.

For advanced use cases, you can override export conditions using the exportConditions flag.

🧩 Async Context and Composition API

Nitro 2.6 introduces a new experimental useEvent() API, enabling request events to be available in all async contexts without the need for explicitly passing them around to all functions as arguments (#1546).

This unblocks a new powerful design pattern, making server events accessible in any async context using useEvent(). This feature was inspired by the Vue Composition API and is powered by unjs/unctx.

You can enable this feature by setting experimental.asyncContext: true.

Note: Currently, this feature is supported for Bun and Node.js environments supporting AsyncLocalStorage. We intend to increase platform coverage following AsyncContext proposal.

🚀 Bundled Runtime Dependencies

When building applications for production, Nitro uses a tool called vercel/nft to trace the files used from the node_modules folder and copies them into the .output/server/node_modules folder. This method has advantages like working well with the native modules that cannot be bundled with rollup. However, there are also some disadvantages with this method:

  • Additional data such as package.json files is copied to the output directory.
  • Increased startup overhead due to node_modules resolution.
  • Tracing allows us to optimize tree-shaking output up to the file level, as opposed to per-export tree-shaking.
  • Potential issues with wrong export condition resolutions and hoisting when multiple versions are present.

Nitro 2.6 now automatically bundles its own dependencies, such as h3, defu, hookable, ofetch, etc. While many of these are small and compact, bundling them results in substantial performance improvements!

Measurement / ModeExternal Deps (2.5)Bundled Deps (2.6)
Number of Files609
Disk Size508K292K
Disk Size (gzip)100Kb72Kb
Start34.7ms18ms
Fetch17.2 ms14.6ms
Start to Fetch53.2ms34.7ms

(Note: Benchmarks conducted on MBA M2 with Node.js v18.16.1. Refer to #1554 for benchmark script details.)

If you require the previous behavior for any reason, you can set the experimental.bundleRuntimeDependencies: false configuration.

🪝 Error Capturing and Lifecycle Hooks

Nitro 2.6 introduces support for two new APIs: useNitroApp().captureError() and event.captureError() (#1463), and it automatically captures lifecycle errors!

You can use a Nitro plugin to hook into both auto-captured and manually captured errors. This enables easy integration with your custom logging infrastructure.

export default defineNitroPlugin((nitro) => {
  nitro.hooks.hook('error', async (error, { event }) => {
    console.error(`${event.path} Application error:`, error)
  })
})

Furthermore, three global hooks have been introduced: request, beforeResponse, and afterResponse (#1545), allowing for global interception of request lifecycles via Nitro plugins.

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('request', (event) => {
    console.log('on request', event.path)
  })
  nitroApp.hooks.hook('beforeResponse', (event, { body }) => {
    console.log('on response', event.path, { body })
  })
  nitroApp.hooks.hook('afterResponse', (event, { body }) => {
    console.log('on after response', event.path, { body })
  })
})

💾 Default Persistent Data Storage

Nitro provides a versatile Key-Value storage layer powered by unjs/unstorage. The default storage backend is in-memory, and you can configure and combine multiple storage backends for use in both development and production environments.

Nitro includes a standard cache: storage, which is internally used for the cache layer and cached route rules. The data will be stored in .nitro/cache (or .nuxt/cache) during development. In Nitro 2.6, a new standard persistent storage, data: (#1352), is introduced. This is available for both development and production modes with Node.js-compatible runtime presets.

To minimize bundle size, we have created a new compact and production-ready fs-lite driver in unstorage. Data will be stored in the new .data/kv directory within your project and persist across builds. It's ready to use out of the box with useStorage('data') and you can always configure the data: mount point with your desired backend and use it at runtime!

🗺️ Lighter Source Maps

In Nitro 2.6, the legacy source-map-support polyfill has been removed, and an optimization has been introduced to automatically tree-shake sourcemap entries within the .output/server directory by removing content maps of libraries from node_modules. These changes significantly contribute to reducing the production bundle size.

To leverage native sourcemap support for Node.js, you can set the NODE_OPTIONS=--enable-source-maps environment variable. This option will be automatically enabled for the Nuxt and Nitro CLI in upcoming releases.

If you need to disable new optimizations, you can use the experimental.sourcemapMinify: false flag.

💻 Better CLI Experience

The Nitro CLI now supports the --preset argument and --minify/--no-minify arguments for nitro build (#1621).

Latest nitro CLI ships with the latest unjs/listhen, featuring Cloudflare Quick Tunnels (--tunnel) powered by unjs/untun and QR Support (--host --qr) powered by unjs/uqr.

The development server has been migrated to utilize unjs/httpxy, a TypeScript fork of http-proxy. This move aims to enhance performance and stability for the future.

🔥 Firebase 2nd Gen Preset

Thanks to extensive community efforts, Nitro now supports Firebase 2nd gen functions (#1500).

While the default target remains v1, we highly recommend migrating by setting the firebase.gen: 2 config (Learn More).

🔓 Upgrade to 2.6

When upgrading, make sure to renew your project's package manager lockfile and node_modules dir to avoid hosting issues.

✨ Other Enhancements

  • The formatting of console output for the failing prerendering routes has been improved. (#1471).
  • event.waitUntil API with Cloudflare integration (#1421).
  • Support for ignore to exclude scanned files (#1430).
  • Ability to ignore public assets with ignore options (#945).
  • New iis server preset (#1436).
  • New prerender:config, prerender:init, and prerender:done hooks (#1519).
  • Support for cached event handlers with variable host and headers (#1184).
  • Support for prerender query link exploration (#1474).
  • Support for listening to UNIX sockets using NITRO_UNIX_SOCKET (#1201).
  • Cookie headers are auto-split (#1452).

There are many other improvements and bug fixes. You can find a detailed list of changes in the changelog.