In Nuxt 4's `useFetch`, what do `lazy: true`, `server: false`, `immediate: false` and `enabled: false` each change, and what does `status` show?
answer
- four switches, four different moments
- navigation versus where it runs
- idle until someone calls execute
- a barrier that blocks refresh too
basics
~20 slazy: true stops client navigation waiting on the data; server: false moves the fetch to the browser after hydration; immediate: false waits for execute(); enabled: false (Nuxt 4.5+) blocks every run. status shows idle, pending, success or error.
solid answer
~40 sAll four are options of `useFetch` and `useAsyncData`, with defaults `lazy: false`, `server: true`, `immediate: true` and `enabled: true`. `lazy: true` changes only client-side navigation: the route renders at once and you handle `status === 'pending'`, while the server render still awaits the data. `server: false` skips the server fetch, so the HTML ships the default value with `status` `idle`, and the request starts in the browser as the component hydrates. `immediate: false` skips the automatic first run on both sides; nothing happens until `execute()` or `refresh()`. `enabled: false`, added in Nuxt 4.5 and accepting a ref or getter, is a gate: it blocks the first run, `refresh()` and watcher triggers, and flipping it to `false` cancels an in-flight request without clearing `data`. Since Nuxt 4, `pending` is simply `status === 'pending'`.
code
vue · 18 lines<script setup lang="ts">
const airport = ref<string>()
// The page itself: in the HTML, navigation waits for it
const { data: board } = await useFetch('/api/flights/board')
// Secondary widget: browser only, never blocks navigation
const { data: weather, status: weatherStatus } = useFetch('/api/weather', {
server: false,
lazy: true,
})
// Gated: no request until the user picks an airport
const { data: delays, status: delayStatus } = useFetch('/api/flights/delays', {
query: { airport },
enabled: () => !!airport.value,
})
</script>go deeper
Recall the four defaults and the four status values, and that lazy is about navigation rather than where the request runs.
Explain what each switch changes on the server render, during hydration and on client navigation, and how status and pending read in each case.
Choose switches per data block: search-critical data server-side and blocking, secondary widgets client-only and lazy, and enabled for requests whose inputs may be missing.
Weigh blocked navigation against loading states and layout shift across the app, and set team defaults with createUseFetch rather than scattering per-call flags.
## The defaults and the four switches `useFetch` and `useAsyncData` share these options. Each default matches what most pages want: fetch on the server, make client navigation wait for the data, start immediately, and always be allowed to run. | Option | Default | What setting it changes | `status` in the server-rendered HTML | |---|---|---|---| | `lazy: true` | `false` | client navigation no longer waits; the server render still does | `success` or `error` | | `server: false` | `true` | no server fetch; the request starts in the browser on hydration | `idle` | | `immediate: false` | `true` | no automatic first run anywhere; call `execute()` | `idle` | | `enabled: false` | `true` (Nuxt 4.5+) | blocks every run, including `refresh()` and watchers | `idle` | `status` is one of `'idle'`, `'pending'`, `'success'` or `'error'`. In Nuxt 4, `pending` is derived from it: `true` only while `status` is `'pending'`. ## `lazy`: about navigation, not about the server `lazy: true`, or the shorthands `useLazyFetch` and `useLazyAsyncData`, changes only **client-side navigation**. The new route renders at once, the request starts as the component mounts, and your template shows the loading state. During server rendering the request is still awaited, so the HTML contains the data. A common misunderstanding follows from this. Awaiting a lazy call does not make client navigation wait: the docs state that the `await` resolves immediately there and `data` is still its default. If the route should wait, drop `lazy` rather than leaning on `await`. ## `server: false`: client-only data With `server: false`, the server never runs the handler and serialises the entry with `status: 'idle'` and `data` at its default. In the browser the request starts as the component hydrates, so even an awaited call leaves `data` empty inside `<script setup>` on the first load. On later client-side navigations the call behaves like any other and, unless it is also `lazy`, makes the navigation wait. Use it for data that is personal, expensive, or irrelevant to search engines, such as a live weather panel beside a flight board. ## `immediate: false` versus `enabled: false` They look alike and are not: 1. `immediate: false` skips only the **automatic first run**, on the server and in the browser. `execute()` and `refresh()` work at any time. In Nuxt 4, a key change on a non-immediate call fetches only if the entry has already fetched once. 2. `enabled: false` is a **barrier** on every execution: the first run, `execute`/`refresh`, and watcher triggers all do nothing while it is false. It accepts a ref or getter, so `enabled: () => !!airport.value` expresses *no request until the inputs exist*. 3. Flipping `enabled` from `true` to `false` **cancels an in-flight request**, sets `status` back to `idle` and keeps the last `data`. 4. Re-enabling does **not** refetch on its own; the next run comes from a key change, a watched source, or an explicit `refresh()`. ## Reading `status` and `pending` - `idle`: nothing has run yet, because of `immediate: false`, `server: false` on the server, or a closed `enabled` gate. - `pending`: a request is in flight. - `success`: the handler resolved and `data` holds its result. - `error`: the handler threw; `error` holds a `NuxtError` and `data` falls back to its default. Nuxt 3 kept `pending` true before a non-immediate call had ever run; Nuxt 4 does not, and `experimental.pendingWhenIdle` restores the old reading while you migrate. Prefer `status` in templates, because it separates *not started* from *loading*. ## Choosing for a flight-status page - The departures board is the page: keep the defaults, so it is in the HTML and navigation waits for it. - A weather or map widget: `server: false` with `lazy: true`, so it never delays the page. - A delays panel that needs an airport first: an `enabled` getter on the selection. - An export that runs on demand: `immediate: false` with `execute()` from a button, or plain `$fetch` if nothing renders from it. The interview-ready summary: `lazy` is *when navigation proceeds*, `server` is *where the first fetch runs*, `immediate` is *whether the first fetch starts by itself*, and `enabled` is *whether any fetch may run at all*.
- If you `await useFetch(url, { lazy: true })`, does client navigation wait for the data?No. On client-side navigation the `await` resolves immediately and `data` is still its default, so you must render from `status`. During server rendering the request is still awaited as usual. If the navigation should wait, drop `lazy` instead of relying on the `await`.
- Why does setting `enabled` back to `true` not show fresh data by itself?`enabled` only lifts the barrier; per the Nuxt docs, re-enabling does not refetch on its own. The next run comes from a key or watched option changing, a `refresh()` or `execute()` call, or `refreshNuxtData`. Turning it off mid-request cancels that request and resets `status` to `idle`, but keeps the last `data`.
saying these in an interview costs you the question
- lazy: true means the data is only ever fetched in the browser.
- immediate: false still fetches on the server and only delays the browser.
- enabled: false is just another spelling of immediate: false.
- In Nuxt 4, pending is true before an immediate: false call has ever run.
- Awaiting a lazy useFetch makes client navigation wait for the data.