In Nuxt 4, what does Nitro's `useStorage()` give a server route, and where does that data live in development versus a production deployment?
answer
- one key-value API, many drivers
- mount points by name
- dev writes to disk
- production defaults to memory
- one instance's memory is not shared
basics
~20 suseStorage() returns Nitro's key-value storage, with getItem, setItem, removeItem and getKeys over named mount points. In development the data and cache mounts are folders on disk; in production anything unconfigured is in memory, except the data mount on Node presets.
solid answer
~50 sNitro's `useStorage()`, auto-imported in Nuxt 4 server code, returns an unstorage instance: an async key-value API (`getItem`, `setItem`, `hasItem`, `removeItem`, `getKeys`) over **mount points**, each backed by a driver such as memory, filesystem or Redis. `useStorage('cache')` returns a view prefixed with that mount. You configure mounts in `nuxt.config.ts` under `nitro.storage` for production and `nitro.devStorage` for the dev server, or mount a driver at runtime from a Nitro plugin. The defaults differ by environment: in dev, `data` and `cache` are folders on disk; in a production build, anything unmounted, including the `cache` mount behind cached handlers, is in memory, and on Node-based presets the `data` mount becomes files under `./.data/kv`. Memory is per instance and lost on restart, so once a BFF runs on several instances or on serverless, mount shared storage for anything that must agree across requests.
code
ts · 12 lines// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
storage: {
cache: { driver: 'redis', url: 'redis://cache.internal:6379' },
holds: { driver: 'redis', url: 'redis://cache.internal:6379', base: 'holds' },
},
devStorage: {
holds: { driver: 'fs', base: './.data/holds' },
},
},
})go deeper
Recall that useStorage is Nitro's server-side key-value store with getItem and setItem, and that it lives on the server, not in the browser.
Explain mount points and drivers, the difference between nitro.storage and nitro.devStorage, and the default locations of data and cache in dev and production.
Show how instance count and preset change what storage means, when to put cache on shared storage, and why read-modify-write on a key-value store needs an atomic alternative.
Decide which state a Nuxt BFF may hold at all versus what belongs in the upstream services, and what shared infrastructure that commits the team to operating.
## What `useStorage` is Nitro ships a storage layer built on **unstorage**, a key-value library with interchangeable drivers. In a Nuxt 4 app, `useStorage` is auto-imported in `server/` code: - `useStorage()` returns the root storage. - `useStorage('holds')` returns a view prefixed with the `holds` mount, so `getItem('A1024')` reads `holds:A1024`. - The API is asynchronous: `getItem`, `setItem`, `hasItem`, `removeItem` and `getKeys`. - Keys use `:` as the namespace separator. A **mount point** attaches a driver to a prefix. The same handler code works whether `holds` is backed by memory, a folder or Redis; only configuration changes. ## Where the data lives by default | Mount | Dev server | Production build, Node preset | Production build, other presets | |---|---|---|---| | unmounted keys | memory | memory | memory | | `data` | filesystem, `.data/kv` | `fsLite` driver, `./.data/kv` | memory | | `cache` (cached handlers and functions) | filesystem in the build directory | memory | memory | | `root`, `src` | read-only filesystem views | not mounted | not mounted | These are defaults, applied only when you have not configured the mount yourself. Two readings of the table matter: 1. **Production memory is per instance.** Three Node processes behind a load balancer hold three separate `cache` mounts, and a `removeItem` on one does not reach the others. On serverless or edge presets an instance can disappear between requests, taking its memory with it. 2. **`.data/kv` is local disk.** It survives restarts on one machine, but a second machine has its own folder, and many hosts give containers an ephemeral filesystem. The deployment preset decides which column applies. You pick it with the `preset` option under `nitro` in `nuxt.config.ts` or the `NITRO_PRESET` environment variable at build time, and many hosts are detected automatically. ## Configuring mounts Static configuration goes in `nuxt.config.ts`: - `nitro.storage` defines mounts for production builds. - `nitro.devStorage` overrides mounts in the dev server, so you can keep Redis for production and a folder for local work. - Mounting `cache` itself moves every cached handler's entries, which is how several instances share one cache and one invalidation. When the connection details come from runtime config, mount the driver from a Nitro plugin instead: in `server/plugins/storage.ts`, create the driver with values from `useRuntimeConfig()` and call `useStorage().mount('redis', driver)`. ## Using it in a backend-for-frontend An inventory BFF might keep short-lived **reservation holds** in a `holds` mount and share the handler cache between instances: - `holds` on Redis in production and a local folder in development. - `cache` on Redis so cached stock and its eviction are shared. - Handlers that treat storage as a remote service, awaiting every call. The key-value API is not transactional. A read-modify-write such as "get the hold count, add one, set it" can lose updates when two requests interleave. Where counts must be exact, use the backing store's own atomic operations through its client, or keep the source of truth in the upstream inventory service. ## Storage versus the other places state could live | Place | Lifetime | Shared across instances | |---|---|---| | a module-level variable in `server/utils` | the process | no | | `useStorage()` with default mounts in production | the process (memory) | no | | `useStorage('data')` on a Node preset | the machine's disk | no | | a mount backed by Redis or another network store | as long as that store keeps it | yes | | the upstream service | its own | yes | The value of `useStorage` is that the code stays the same while the row you are on changes with configuration. Start with the default for local work, and decide the production row per mount before the second instance appears. ## Mistakes interviewers listen for - Expecting `useStorage()` data written in production to survive a restart without configuring a persistent driver. - Evicting a cache entry on one instance and assuming all instances see it. - Configuring only `devStorage` and shipping a production build that silently uses memory. - Storing class instances or functions: persistent drivers keep values in serialised form, so store plain data that survives a round trip. - Reading storage in a hot path without thinking about latency: once a mount is Redis, every `getItem` is a network round trip.
- Why can a cache eviction work in development but not after deploying to three instances?In the dev server there is one process with one `cache` mount on disk. A production build keeps the `cache` mount in memory by default, one copy per instance, so `useStorage('cache').removeItem(...)` clears only the instance that handled that request. Mount `cache` on shared storage under `nitro.storage` so every instance reads and evicts the same entries.
- When would you mount storage from a Nitro plugin instead of nuxt.config.ts?When the driver needs values that are only known at runtime, such as a Redis host from runtime config overridden by environment variables. A plugin in `server/plugins/` runs at instance start, reads `useRuntimeConfig()`, creates the driver and calls `useStorage().mount(...)`. Static `nitro.storage` values are fixed at build time.
saying these in an interview costs you the question
- Nitro's useStorage writes to the browser's localStorage, like VueUse's useStorage.
- Data written with useStorage in production persists across restarts by default.
- Evicting a key with useStorage clears it on every server instance automatically.
- nitro.devStorage settings also apply to production builds.
- useStorage operations are synchronous, so no await is needed.