v2.11.0
🌟 What is new?
🟢 Better Errors
Error handlers for both development and production environments have been rewritten (see #3002 for more details).
Powered by poppinss/youch (v4.x), you can now view beautiful error pages with stack traces in both the browser and CLI.
Nitro automatically applies source maps to error stack traces—no need to set NODE_OPTIONS="--enable-source-maps"
—allowing you to pinpoint the exact file and line where an issue occurs.
Previously, Nitro conditionally rendered JSON errors based on certain user-agent
headers or when requests were made to /api/*
.
For more consistent behavior, any request not made from a browser (i.e., without the Accept: text/html
header) will always receive a JSON response. Additionally, all production errors are now returned in JSON format.
You can still override the error handler for both development and production using the errorHandler
config. If you want to fall back to the default error handler conditionally, you can simply return
without handling the response (see #3085 for more details).
Additional strict security headers will be always added to error responses (#2907).
Example error in development:
![]() |
![]() |
Example error in production
{
"error": true,
"url": "http://localhost:3000/",
"statusCode": 500,
"statusMessage": "Server Error",
"message": "Server Error",
"data": {
"foo": "bar"
}
}
🟢 Better Dev Server
Every time you run the nitro dev
or nuxt dev
command, Nitro creates an isolated worker thread and proxies requests from the browser to the server inside the worker using a Unix socket or a named pipe on Windows.
To improve stability and provide a better experience, the internal mechanism for creating and reloading the server and handling errors has been rewritten (see #3135 for more details).
🟢 Database Improvements
Nitro's database support, powered by db0 0.3.x, now offers improved types, better-prepared statements, better API consistency, and native node:sqlite
support (#3127).
Thanks to native node:sqlite support in Node.js (>= 22.5.0) and Deno (>= 2.2), you can now use databases in both environments without external dependencies or extra configuration!
🟢 Improved baseURL
Handling
For deployments that need to be served under a base URL (e.g., /admin
), Nitro provides a baseURL
config. When enabled, all routes and static assets will be prefixed accordingly.
Depending on the deployment platform, the build output structure may need to be adjusted. The Netlify and Stormkit presets have been updated with fixes (#2966, #2971, 5a51bf06). Additionally, we have enabled baseURL
for nitro-deploys for end-to-end testing.
The new Nitro error handler also improves the user experience. If a request is made to the server without the required base URL, it will now automatically redirect (#3146).
🟢 Prerender Crawler
Nitro's prerender feature includes a crawler that scans each page to discover additional links. However, previous versions relied on regex matches, which occasionally led to false detections.
This release migrates to natemoo-re/ultrahtml for more precise link extraction (#3068).
🟢 Improved Module Resolution
Nitro has migrated to exsolve for internal module resolution. This change improves performance (up to 3x), and stability, and enforces stricter search paths (#3125).
🟢 Firebase App Hosting
Zero-config deployment support for Firebase App Hosting has been added (#2864, #2895, #2967).
🟢 Node.js Compatibility for edge and Deno v2
The Nitro server can be deployed to any runtime and platform. We built unenv and integrated it into Nitro so that if your code or any library you use requires Node.js-specific features such as Buffer, process.env, or setImmediate, they can continue working through automatically injected lightweight polyfills.
Thanks to efforts like WinterCG, the ecosystem has shifted towards using more web standards that work in any JavaScript runtime.
However, some Node.js features remain difficult to replace, such as node:async_hooks, node:crypto, node:net, and even process.env
, which plays a de facto standard role while standards for JavaScript servers continue to evolve.
Edge runtimes (Cloudflare Workers, Deno Deploy, Netlify Edge, and Vercel Edge) are built on top of the V8 engine and traditionally lacked support for Node.js APIs and globals. Thankfully, both Deno (v2) and Cloudflare (workerd) have made significant efforts to support Node.js APIs in their edge runtimes. You can check the status using the unofficial platform-node-compat tests.
This improvement makes Nitro's support for edge runtimes faster, smaller, and more capable. Features like AsyncLocalStorage, which cannot be simply polyfilled, benefit from these new APIs.
While edge platforms have (partially) implemented Node.js APIs, we still need to polyfill any unsupported features using unenv.
Unenv v2 is a major rework with all Node.js modules rewritten as ESM polyfill modules, ensuring full coverage of known exports from the latest Node.js LTS release. With Unenv v2, we can create hybrid/native Node.js polyfills that combine native runtime support with polyfills at the module export level (used for cloudflare presets).
Enabling Node.js compatibility:
- deno-deploy and netlify-edge: Automatically enabled after upgrade for all Node.js APIs (#3129).
- deno-server: Upgrade to Deno v2 and set
compatibilityDate
to2025-01-30
or later (#3050). - vercel-edge (deprecated): Automatically enabled after upgrade for a subset of Node.js APIs (#3136).
- cloudflare: Add
nodejs_compat
to your wrangler configuration. - nuxt-hub: Automatically enabled after upgrading dependencies.
For Cloudflare deployments, we have also added an experimental new method to enable Node.js compatibility, which will be enabled by default in future releases.
Please note that creating a wrangler.toml
file or using deployConfig
flag, disables Cloudflare dashboard features, and variables set from the dashboard will be lost.
Opt-in to cloudflare deploy config
// Nitro
export default defineNitroConfig({
compatibilityDate: "2025-03-01",
cloudflare: { nodeCompat: true, deployConfig: true }
});
// Nuxt
export default defineNuxtConfig({
compatibilityDate: "2025-03-01",
nitro: { cloudflare: { nodeCompat: true, deployConfig: true } }
});
📦 Dependency Upgrades
Several dependencies across unjs have been updated, focusing on performance improvements and an ESM-only dist to reduce install size. This has already reduced the package size by approximately ~28MB
, with further reductions planned as we phase out dual-format published packages.
- unenv upgraded to v2 (full rewrite).
- db0 upgraded to v0.3 (ESM-only, native
node:sql
, improvements). - ohash upgraded to v2 (#3114) (ESM-only, native
node:crypto
support, much faster) - unimport upgraded to v4 (improvements).
- pathe upgraded to v2 (improvements).
- untyped upgraded to v2 (ESM-only, less install size).
- c12 upgraded to v3 (ESM-only).
- cookie-es upgraded to v2 (ESM-only).
- esbuild upgraded to v0.25.
- chokidar upgraded to v4 (#3113).
📝 Other Changes
Note
164 commits, 168 files with 8,341 additions, and 5,476 deletions from 33 contributros since last 2.x release.
Changelog below is subset of changes (compare all changes)
🚀 Enhancements
- cloudflare-durable: Allow redirect
fetch
to durable object (#3048) - openapi: Allow
$global.components
indefineRouteMeta
(#2921) - Support
unenv: []
as array of presets in config (#3141) - Add
build:before
hook (#3142)
🩹 Fixes
- Allow patching
globalThis.fetch
(#3009) - Update
dynamic-require
plugin to match new webpack chunk format (#2947) - Unique imports for
server-handlers-meta
(#2945) - Auto import multiple levels of nested directories in
utils/
(#2953) - Scan route files with other characters in param name (#2872)
- Add NitroApp type export to main subpath for backward compatibility (#2875)
- Run nitro modules before
createUnimport
(#3016) - Respect configured
workspaceDir
(#3148) - config: Use
static
preset ifstatic: true
config is set (#2860) - config: Avoid serialization warning when primitives have prototype changed (#2902)
- config: Normalize route rules with leading slash (#2978)
- config: Clone preset object (#2983)
- config: Make sure serialized storage options are not modified (#2977)
- externals: Respect importer path for resolving (#3137)
- timing: Silent in test (f8cb7620)
- server-assets: Include files without extension by default (#2995)
- rollup: Remove
unenv/npm/consola
polyfill (57bd74bd) - rollup: Resolve unenv paths with expected version (#3073)
- cache: Polyfill
event.waitUntil
forcachedEventHandler
(#3083) - types: Deduplicate aliases (#3120)
- node-cluster: Create dedicated chunk for worker (#2894)
- azure-functions: Pass body to the context as-is (#2965)
- cloudflare: Add known
cloudflare:
externals (#2976) - netlify: Avoid second bundling (#3046)
- deno-deploy: Always externalize
node:
imports (a9fc2673)
💅 Refactors
- Use node-mock-http for local fetch (#3069)
- Split legacy error utils (#2982)
- Improve env expansion regex (#3037)
- Use
toExports
util fromunimport
(#3059) - Replace
@rollup/pluginutils
withunplugin-utils
(#3075) - Remove extra tag in logs (#3143)
- vercel, netlify: Hide ISR warning in tests (cc564996)
- cloudflare: Sync wrangler types with upstream (#3054)
- cloudflare: Split legacy presets (834dd483)
📖 Documentation
- config: Document
compatibilityDate
option (#2941) - config: Document
openAPI.production
option (#2940) - guide: Add Deno to installation methods (#3010)
- config: Update openAPI section (#2869)
- cache: Add return type constraint for cached functions (#3097)
- netlify: Add deprecated warning for
netlify-builder
preset (#2972) - cloudflare: Remove note about pages and
wrangler.toml
support (#3117) - vercel: Update docs (#3149)
- database: Update
postgresql
example (#3092) - Link route meta to openAPI (#3082)
- Minor tweaks (#2896, #2915, #2931, a0b6ad8b, #2963, #2916, #3039, #3038, #2987, #3124, #3116)
🌊 Types
- Fix type for
event.$fetch
(#2913)
❤️ Contributors
- @0xflotus
- @ZerxZ
- @beer (@iiio2)
- @productdevbook
- Aleksandr Kriuchkov (@artmizu)
- Alexander Lichter (@TheAlexLichter)
- Buer Yang (@noootwo)
- Buzut (@Buzut)
- Connor Pearson (@cjpearson)
- Dallas Johnson (@dallasjohnson)
- Daniel Roe (@danielroe)
- Giorgio Boa (@gioboa)
- GuySake (@UngererFabien)
- Huseeiin (@huseeiin)
- Julien Huang (@huang-julien)
- Kevin Deng (@sxzz)
- Kotkoroid (@kotkoroid)
- Leandro Soares (@SoaresMG)
- Lee Robinson (@leerob)
- Lehuy (@LehuyH)
- Lucas (@mirsella)
- Marvin Hagemeister (@marvinhagemeister)
- Muhammad Ubaid Raza (@mubaidr)
- Oskar Lebuda (@OskarLebuda)
- Pooya Parsa (@pi0)
- Restent Ou (@gxres042)
- Sebastian Krah (@Exotelis)
- Shichao Zhang (@AngryBerryMS)
- Siddharth Gelera (@barelyhuman)
- Yauheni Vasiukevich (@EvgenyWas)
- Yuangwang (@Yuangwang)