---
title: "Nuxt 4.2: abort signals in useAsyncData and a new error overlay · vuejs.school"
description: "Nuxt 4.2 hands every useAsyncData handler an AbortSignal that cancels replaced requests for real, and shows a technical error overlay with your own error page."
canonical_url: "https://vuejs.school/community/blog/nuxt-4-2-abort-signals-and-the-dev-overlay"
meta:
  "og:description": "Nuxt 4.2 hands every useAsyncData handler an AbortSignal that cancels replaced requests for real, and shows a technical error overlay with your own error page."
  "og:image": "https://vuejs.school/_og/d/a_~TnV4dCA0LjI6IGFib3J0IHNpZ25hbHMgaW4gdXNlQXN5bmNEYXRhIGFuZCBhIG5ldyBlcnJvciBvdmVybGF5IMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~TnV4dCA0LjI6IGFib3J0IHNpZ25hbHMgaW4gdXNlQXN5bmNEYXRhIGFuZCBhIG5ldyBlcnJvciBvdmVybGF5%2Cdescription_~TnV4dCA0LjIgaGFuZHMgZXZlcnkgdXNlQXN5bmNEYXRhIGhhbmRsZXIgYW4gQWJvcnRTaWduYWwgdGhhdCBjYW5jZWxzIHJlcGxhY2VkIHJlcXVlc3RzIGZvciByZWFsLCBhbmQgc2hvd3MgYSB0ZWNobmljYWwgZXJyb3Igb3ZlcmxheSB3aXRoIHlvdXIgb3duIGVycm9yIHBhZ2Uu%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_sky%2Cp_Ii9jb21tdW5pdHkvYmxvZy9udXh0LTQtMi1hYm9ydC1zaWduYWxzLWFuZC10aGUtZGV2LW92ZXJsYXki%2Cs_3pOGuhNPPyzDQChW.png"
  "og:image:alt": "Nuxt 4.2: abort signals in useAsyncData and a new error overlay · vuejs.school"
  "og:image:height": "600"
  "og:image:type": image/png
  "og:image:width": "1200"
  "og:title": "Nuxt 4.2: abort signals in useAsyncData and a new error overlay · vuejs.school"
  "twitter:image": "https://vuejs.school/_og/d/a_~TnV4dCA0LjI6IGFib3J0IHNpZ25hbHMgaW4gdXNlQXN5bmNEYXRhIGFuZCBhIG5ldyBlcnJvciBvdmVybGF5IMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~TnV4dCA0LjI6IGFib3J0IHNpZ25hbHMgaW4gdXNlQXN5bmNEYXRhIGFuZCBhIG5ldyBlcnJvciBvdmVybGF5%2Cdescription_~TnV4dCA0LjIgaGFuZHMgZXZlcnkgdXNlQXN5bmNEYXRhIGhhbmRsZXIgYW4gQWJvcnRTaWduYWwgdGhhdCBjYW5jZWxzIHJlcGxhY2VkIHJlcXVlc3RzIGZvciByZWFsLCBhbmQgc2hvd3MgYSB0ZWNobmljYWwgZXJyb3Igb3ZlcmxheSB3aXRoIHlvdXIgb3duIGVycm9yIHBhZ2Uu%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_sky%2Cp_Ii9jb21tdW5pdHkvYmxvZy9udXh0LTQtMi1hYm9ydC1zaWduYWxzLWFuZC10aGUtZGV2LW92ZXJsYXki%2Cs_3pOGuhNPPyzDQChW.png"
  "twitter:image:alt": "Nuxt 4.2: abort signals in useAsyncData and a new error overlay · vuejs.school"
---

> Index of every page on this site: [llms.txt](https://vuejs.school/llms.txt)

## What shipped in Nuxt 4.2

The release post lists these items:

- `useAsyncData` handlers receive an `AbortSignal`, so `refresh()` and `clear()` can cancel the request in flight.
- In development, your custom error page and a technical error overlay with the stack trace are shown together.
- An opt-in, experimental Vite Environment API integration behind `experimental.viteEnvironmentApi`.
- The Nitro server integration moved into its own package, `@nuxt/nitro-server`, as an internal refactor.
- Renderer dependencies are computed at build time rather than on each cold start, and the kit and schema packages dropped dependencies they did not need.
- An experimental option, `experimental.extractAsyncDataHandlers`, that moves data handlers into separate chunks for prerendered sites.
- Experimental TypeScript plugin support behind `experimental.typescriptPlugin`.

Of the rest, the new package and the speed-ups need nothing from you, and the three experimental options stay off until you turn them on.

Smaller items: a `declarationPath` option for components, an `extensions` option for the kit's `resolveModule`, a `setGlobalHead` utility for module authors, and fixes for the route hash being dropped on `routeRules` redirects, object-form `href` in `<NuxtLink>`, auto-imported components passed to `h()`, and array handling in app config during hot reload.

## Pass the abort signal to `$fetch`

Before 4.2, `dedupe: 'cancel'`, the default for `useAsyncData`, meant "ignore the result of the previous request". When a refresh replaced a pending request, the HTTP request itself kept running to the end. Now the handler receives a signal as its second argument, and when you pass it on to `$fetch`, the replaced request is aborted for real:

*app/pages/users.vue*

```
<script setup lang="ts">
type User = { id: number; name: string }

const { data: users, status } = await useAsyncData('users', (_nuxtApp, { signal }) =>
  $fetch<User[]>('/api/users', { signal }),
)
</script>

<template>
  <p v-if="status === 'pending'">Loading…</p>
  <ul v-else>
    <li v-for="user in users" :key="user.id">{{ user.name }}</li>
  </ul>
</template>
```

The handler's first argument is still the Nuxt app instance, which most handlers ignore. The second is an object with `signal`. `$fetch` accepts a `signal` option like the native `fetch` does, so passing it through is the whole change. The handler type is now `(nuxtApp, { signal }) => Promise<T>`, but a handler that only declares `nuxtApp`, or nothing at all, still type-checks and still runs.

The handler's signal is aborted in four cases:

- A new request for the same key starts while one is pending (a `refresh()`, an `execute()` or a change to a watched value), under the default `dedupe: 'cancel'`.
- You call `clear()`.
- The request is still pending when the new `timeout` option, given in milliseconds, runs out.
- A signal of your own, passed to `refresh()`, is aborted (next section).

A cancelled request is not an error. When a newer request replaces it, the newer one sets `data` and `status`. When `clear()` or your own signal cancels it, Nuxt catches the `AbortError`, leaves `error` empty and sets `status` back to `'idle'`. A timeout is the exception: it ends with a `TimeoutError`, so `error` is set and `status` becomes `'error'`, like any failed request.

`useFetch` builds on `useAsyncData`. It already aborted a replaced request before 4.2, with an `AbortController` of its own, and it already had a `timeout` option. In 4.2 it passes Nuxt's signal to `$fetch` instead, so the four cases above apply to `useFetch` too, without any change to your code.

## Cancel a refresh with your own `AbortController`

`refresh()` and `execute()` accept a `signal` too, which lets a user, or a component's lifecycle, cancel a request. This component loads a report only when asked (`immediate: false`) and offers a Cancel button:

*app/components/ReportPanel.vue*

```
<script setup lang="ts">
const { data: report, status, refresh } = await useAsyncData(
  'report',
  (_nuxtApp, { signal }) => $fetch('/api/report', { signal }),
  { immediate: false },
)

let controller: AbortController | undefined

function run() {
  controller = new AbortController()
  return refresh({ signal: controller.signal })
}

function cancel() {
  controller?.abort()
}

onBeforeUnmount(cancel)
</script>

<template>
  <button type="button" :disabled="status === 'pending'" @click="run">Run report</button>
  <button type="button" :disabled="status !== 'pending'" @click="cancel">Cancel</button>
  <pre v-if="report">{{ report }}</pre>
</template>
```

Nuxt merges the controller's signal with its own internal one, so the handler still receives a single `signal`. When the user clicks Cancel, the request is aborted, Nuxt catches the `AbortError`, and `status` returns to `'idle'`, which re-enables the Run button.

Nuxt does not cancel a pending request when its component unmounts, so `onBeforeUnmount(cancel)` stops a slow report when the user leaves.

## Your error page and the stack trace in development

Before 4.2, a custom `error.vue` was also what you saw in development, so the stack trace you need to find the cause stayed hidden unless your page printed it. In 4.2, when a request fails on the server during development, Nuxt renders your error page and lays a technical overlay over it with the stack trace and debugging details.

A small preview in the bottom-right corner switches between the two views: click it to see the page your users would get, and click the corner again to bring the overlay back. Escape also hides the overlay. It is added only in development and never ships to production.

## Three experimental options to try in a branch

`experimental.typescriptPlugin: true` installs and configures the `@dxup/nuxt` module, which adds TypeScript language-service plugins for Nuxt: renaming an auto-imported component file updates its references, go to definition works on dynamic glob imports and on runtime config properties, and a `$fetch('/api/users')` call jumps to its handler in `server/api`. In VS Code the plugins need the workspace version of TypeScript: run "TypeScript: Select TypeScript Version" and choose "Use Workspace Version".

`experimental.extractAsyncDataHandlers: true` moves the functions you pass to `useAsyncData` and `useLazyAsyncData` into separate chunks that load on demand. On a prerendered site the handler code is only needed at build time, so the client bundle shrinks; the post reports 39% less JavaScript on an earlier version of nuxt.com and says results vary.

`experimental.viteEnvironmentApi: true` lets one Vite dev server serve the client and server builds as two environments instead of running two servers. The post marks it highly experimental and calls it the first breaking change toward the next major version of Nuxt; setting `future.compatibilityVersion: 5` opts into it and into future breaking changes, and the post says to do that for testing only.

## Upgrade, then add the signal to your handlers

One command refreshes the lockfile and the dependencies Nuxt relies on:

```
npx nuxt upgrade --dedupe
```

The upgrade alone changes nothing on the network for `useAsyncData`: a handler that ignores the signal still lets a replaced request run to the end. After upgrading, go through your handlers:

1. In each `useAsyncData` handler that calls `$fetch`, add `{ signal }` as the second parameter and pass it to the request.
2. Leave it out where the request changes something on the server, for example a `POST` that sends an email. With the signal, a refresh aborts that request mid-flight, and the server may see a closed connection. To keep the signal there, pass `dedupe: 'defer'`, so a refresh reuses the pending request instead of replacing it, or stop refreshing while it is pending.

Upgrade now. The signal makes `dedupe: 'cancel'` do what its name says, the overlay gives you the stack trace without hiding your own error page, and nothing else in the list requires a change to your code.

**Check it stuck**3 questions from the Data fetching quiz.

[**Practice now**](https://vuejs.school/learn/nuxt/data-fetching?tab=quiz)

**Was this helpful?**

## **Keep reading**

[Release notes · 7 min read**What's new in Nuxt 4.6: nuxt/server, typed $fetch, cookie sessions and Vapor**](https://vuejs.school/community/blog/whats-new-in-nuxt-4-6) [Tutorial · 6 min read**useFetch or $fetch? Picking the right one in Nuxt**](https://vuejs.school/community/blog/usefetch-or-fetch-picking-the-right-one-in-nuxt)

```json
{"@context":"https://schema.org","@graph":[{"@id":"https://vuejs.school/#website","@type":"WebSite","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school/","publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/nuxt-4-2-abort-signals-and-the-dev-overlay#webpage","@type":"WebPage","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2025-10-30","description":"Nuxt 4.2 hands every useAsyncData handler an AbortSignal that cancels replaced requests for real, and shows a technical error overlay with your own error page.","name":"Nuxt 4.2: abort signals in useAsyncData and a new error overlay","url":"https://vuejs.school/community/blog/nuxt-4-2-abort-signals-and-the-dev-overlay","breadcrumb":{"@id":"https://vuejs.school/community/blog/nuxt-4-2-abort-signals-and-the-dev-overlay#breadcrumb"},"isPartOf":{"@id":"https://vuejs.school/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://vuejs.school/community/blog/nuxt-4-2-abort-signals-and-the-dev-overlay"]}],"primaryImageOfPage":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/#identity","@type":"Organization","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school","logo":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/community/blog/nuxt-4-2-abort-signals-and-the-dev-overlay#article","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2025-10-30","description":"Nuxt 4.2 hands every useAsyncData handler an AbortSignal that cancels replaced requests for real, and shows a technical error overlay with your own error page.","headline":"Nuxt 4.2: abort signals in useAsyncData and a new error overlay","inLanguage":"en","isAccessibleForFree":true,"thumbnailUrl":"https://vuejs.school/_og/d/a_~TnV4dCA0LjI6IGFib3J0IHNpZ25hbHMgaW4gdXNlQXN5bmNEYXRhIGFuZCBhIG5ldyBlcnJvciBvdmVybGF5IMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~TnV4dCA0LjI6IGFib3J0IHNpZ25hbHMgaW4gdXNlQXN5bmNEYXRhIGFuZCBhIG5ldyBlcnJvciBvdmVybGF5%2Cdescription_~TnV4dCA0LjIgaGFuZHMgZXZlcnkgdXNlQXN5bmNEYXRhIGhhbmRsZXIgYW4gQWJvcnRTaWduYWwgdGhhdCBjYW5jZWxzIHJlcGxhY2VkIHJlcXVlc3RzIGZvciByZWFsLCBhbmQgc2hvd3MgYSB0ZWNobmljYWwgZXJyb3Igb3ZlcmxheSB3aXRoIHlvdXIgb3duIGVycm9yIHBhZ2Uu%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_sky%2Cp_Ii9jb21tdW5pdHkvYmxvZy9udXh0LTQtMi1hYm9ydC1zaWduYWxzLWFuZC10aGUtZGV2LW92ZXJsYXki%2Cs_3pOGuhNPPyzDQChW.png","@type":["Article","BlogPosting"],"articleSection":["Release notes"],"author":{"@id":"https://vuejs.school/#identity"},"image":{"@id":"https://vuejs.school/#/schema/image/1"},"isPartOf":{"@id":"https://vuejs.school/community/blog/nuxt-4-2-abort-signals-and-the-dev-overlay#webpage"},"mainEntityOfPage":{"@id":"https://vuejs.school/community/blog/nuxt-4-2-abort-signals-and-the-dev-overlay#webpage"},"publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/nuxt-4-2-abort-signals-and-the-dev-overlay#breadcrumb","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","name":"Blog","item":"https://vuejs.school/community/blog","position":1},{"@type":"ListItem","name":"Nuxt 4.2: abort signals in useAsyncData and a new error overlay","position":2}]},{"@id":"https://vuejs.school/#logo","@type":"ImageObject","caption":"vuejs.school","contentUrl":"https://vuejs.school/apple-touch-icon.png","inLanguage":"en","url":"https://vuejs.school/apple-touch-icon.png"},{"@id":"https://vuejs.school/#/schema/image/1","@type":"ImageObject","contentUrl":"https://vuejs.school/_og/d/a_~TnV4dCA0LjI6IGFib3J0IHNpZ25hbHMgaW4gdXNlQXN5bmNEYXRhIGFuZCBhIG5ldyBlcnJvciBvdmVybGF5IMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~TnV4dCA0LjI6IGFib3J0IHNpZ25hbHMgaW4gdXNlQXN5bmNEYXRhIGFuZCBhIG5ldyBlcnJvciBvdmVybGF5%2Cdescription_~TnV4dCA0LjIgaGFuZHMgZXZlcnkgdXNlQXN5bmNEYXRhIGhhbmRsZXIgYW4gQWJvcnRTaWduYWwgdGhhdCBjYW5jZWxzIHJlcGxhY2VkIHJlcXVlc3RzIGZvciByZWFsLCBhbmQgc2hvd3MgYSB0ZWNobmljYWwgZXJyb3Igb3ZlcmxheSB3aXRoIHlvdXIgb3duIGVycm9yIHBhZ2Uu%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_sky%2Cp_Ii9jb21tdW5pdHkvYmxvZy9udXh0LTQtMi1hYm9ydC1zaWduYWxzLWFuZC10aGUtZGV2LW92ZXJsYXki%2Cs_3pOGuhNPPyzDQChW.png","inLanguage":"en","url":"https://vuejs.school/_og/d/a_~TnV4dCA0LjI6IGFib3J0IHNpZ25hbHMgaW4gdXNlQXN5bmNEYXRhIGFuZCBhIG5ldyBlcnJvciBvdmVybGF5IMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~TnV4dCA0LjI6IGFib3J0IHNpZ25hbHMgaW4gdXNlQXN5bmNEYXRhIGFuZCBhIG5ldyBlcnJvciBvdmVybGF5%2Cdescription_~TnV4dCA0LjIgaGFuZHMgZXZlcnkgdXNlQXN5bmNEYXRhIGhhbmRsZXIgYW4gQWJvcnRTaWduYWwgdGhhdCBjYW5jZWxzIHJlcGxhY2VkIHJlcXVlc3RzIGZvciByZWFsLCBhbmQgc2hvd3MgYSB0ZWNobmljYWwgZXJyb3Igb3ZlcmxheSB3aXRoIHlvdXIgb3duIGVycm9yIHBhZ2Uu%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_sky%2Cp_Ii9jb21tdW5pdHkvYmxvZy9udXh0LTQtMi1hYm9ydC1zaWduYWxzLWFuZC10aGUtZGV2LW92ZXJsYXki%2Cs_3pOGuhNPPyzDQChW.png"}]}
```