Skip to content

Build1 publisher2 min readPublished

Nuxt 4.4 turns the hand-rolled useFetch wrapper into a compiler macro

Nuxt 4.4 adds createUseFetch and createUseAsyncData, factories that return the stock composable signature with shared defaults built in. Hand-written wrappers can go, provided each factory is exported from a directory Nuxt scans so the build can inject SSR deduplication keys.

The Engineer · Build desk

Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction

What happened

  • Pages calling a factory-built useAPI get the same data, status, error and refresh with the same typing, and options such as query, transform and pick still pass through.
  • A plain object passed to the factory sets defaults the caller can override, while a function receives the caller's options and returns a merge the factory controls.
  • Defaults that need useRuntimeConfig() or useNuxtApp() require the function form, because a plain object is evaluated once at module scope with no Nuxt instance.
  • An ofetch instance already configured in a plugin, with its own retry or logging, can be handed to the factory through the $fetch option.
  • createUseAsyncData applies the same pattern to loaders that are not HTTP calls, such as a GraphQL client, an SDK or a Supabase query.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • decision Each existing wrapper has to be classed as convenience or policy before it moves, because the two modes settle conflicts with the caller in opposite directions.
  • exposure A function-mode factory guarantees only the keys spread after the caller's options, so reviewers must read merge order to know what a client actually enforces.
  • constraint A migration leaves two client paths in the codebase, since form submits and button clicks stay on $fetch or $api to avoid confusing caching.

Code review has to check where the factory is declared. A dev.to walkthrough that adapts the recipe in the Nuxt docs describes createUseFetch as a compiler macro [13][3]. Nuxt finds the exported declaration at build time and injects deduplication keys, so SSR hydration reuses the server payload and skips a second fetch [3]. Move the same call into a component or a random utility file and those keys are gone [4]. The post does not say whether the build warns when that happens.

That build step settles the doubt hand-written wrappers left behind. According to the post, those wrappers repeated baseURL, the Authorization header and the 401 redirect at every call site, then lost some of useFetch's typing or left teams unsure whether SSR deduplication still worked [2]. With the factory, the generics come from Nuxt and the team no longer maintains its own [1].

Function mode enforces policy through ordinary object spread, in an order the author writes. The billing example spreads callerOptions first and then sets baseURL from runtime config. That URL always wins, and callers cannot point the client elsewhere [7]. The ofetch example puts $fetch first, set to the plugin's $api, and spreads callerOptions after it [9]. A later key overwrites an earlier one, so a caller passing its own $fetch to that client replaces the plugin instance [1]. The post chose function mode there to reach useNuxtApp() inside setup [8].

I think handing the author the merge is the right design. Nuxt does not have to guess which keys are policy, and the merge function states it in code [6].

For an app talking to an authenticated external backend, I would put one ofetch instance behind both the render path and the event path. The plugin in the post builds it with $fetch.create, retry: 2 and retryStatusCodes of 502, 503 and 504, then provides it as $api [9]. The factory and the direct $api calls then share one retry policy [9][12]. An app with one same-origin /api route and no headers should keep plain useFetch. The post says a factory there adds a file and a name for no gain [11].

What to watch

  • Whether Nuxt's build starts warning when createUseFetch is declared outside a scanned directory, which would move the placement rule from code review into the compiler.
  • Whether the Nuxt docs recipe adds a supported way to lock specific keys in function mode beyond the author's spread order.
  • Reports on SSR key behaviour for createUseAsyncData factories wrapping SDK or Supabase loaders in production apps.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories