useFetch on the Page, $fetch in Handlers
The Data Fetching Story
Nuxt gives you three pillars for fetching data — useFetch for pages, useAsyncData for custom logic, and $fetch for one-off calls inside handlers and actions.
What you'll learn
- Recognize the three primitives Nuxt ships for data fetching
- Pick the right one based on whether you are in a page or a handler
- Know they share the same request cache and serialization pipeline
Most apps need to load data — from your own API, a third-party service, or a database. Nuxt unifies this story with three primitives that work the same way on the server during SSR and on the client after hydration.
The Three Primitives
useFetch(url)— the default page-level hook. WrapsuseAsyncDataplus$fetch.useAsyncData(key, handler)— for custom logic, multiple fetches, transforms.$fetch(url)— the universal HTTP client. Use it inside actions, handlers, callbacks.
<script setup lang="ts">
// Page-level — runs on server during SSR, hydrates on client.
const { data: posts } = await useFetch('/api/posts')
// Inside a click handler — server already rendered, just call $fetch.
async function createPost() {
await $fetch('/api/posts', { method: 'POST', body: { title: 'Hi' } })
}
</script> Server-First, No Double Fetch
useFetch and useAsyncData run on the server during SSR, serialize the result into the page
payload, and the client hydrates from that payload — it does not re-fetch. This is the magic that
makes Nuxt apps feel fast and SEO-friendly out of the box.
$fetch Is ofetch
$fetch is a thin wrapper over the ofetch library — it parses JSON, throws on non-2xx, and works
in both Node and the browser. Reach for it inside:
- Pinia store actions
- Button click handlers
- Server route handlers (
server/api/*.ts) - Anywhere outside of
setup()top-level