Struggling to use $fetch instead of usefetch in Nuxt :
Stefan Bogdanescu
Founder & Senior Architect · 2026-06-29
Struggling to use useFetch instead of $fetch in Nuxt: Understanding Data Fetching Paradigms
As senior developers working within the Nuxt ecosystem, we often encounter subtle but frustrating warnings regarding data fetching. One common point of confusion arises when deciding between the reactive state management provided by composables like useFetch and the simpler utility function $fetch. If you are building custom APIs or composables, understanding this distinction is key to writing clean, robust, and performant code.
This post dives into why you are seeing warnings when trying to mix these methods and how to architect your data fetching logic correctly in Nuxt.
The Nuxt Data Fetching Philosophy: Reactivity vs. Utility
The warning you encountered—[nuxt] [useFetch] Component is already mounted, please use $fetch instead—is not an error in the traditional sense; it’s a signal from the framework about the intended usage pattern. It highlights a difference in how Nuxt manages data state based on the context of component mounting.
useFetch: The Reactive Approach
useFetch is designed to integrate deeply with Vue's reactivity system within a component lifecycle. It automatically handles loading states, error states, and caching, making it ideal for data that directly dictates the rendering or interaction flow of a specific view. When you use useFetch, you are telling Nuxt: "This data is critical to this component's state."
$fetch: The Utility Approach
In contrast, $fetch is a lower-level utility function provided by Nuxt/Nitro that handles the raw HTTP request. It is focused purely on making an API call and returning the raw response. It bypasses some of the component lifecycle overhead associated with useFetch. This makes $fetch excellent for fetching data in server contexts, utility functions, or when you need to manage the state entirely outside of a specific Vue component's reactive scope.
Deconstructing Your Composable
Your goal was to create a reusable function (useApiFetch) that abstracts API calls. The conflict arises because you are trying to force useFetch (a component-aware hook) into a utility slot where $fetch (a raw request utility) might be more appropriate, especially if the caller doesn't need the full reactive lifecycle management of a component.
Here is how we can refactor your composable to align with Nuxt’s best practices:
import type { UseFetchOptions } from "nuxt/app";
import { useRequestHeaders } from "nuxt/app";
import useCookie from "#usecookie"; // Assuming you are using a cookie library
export function useApiFetch<T>(path: string, options: UseFetchOptions<T> = {}) {
let headers: any = {
accept: "application/json",
referer: "http://localhost:3000",
};
const token = useCookie("XSRF-TOKEN");
if (token.value) {
headers["X-XSRF-TOKEN"] = token.value as string;
}
if (process.server) {
headers = {
...headers,
...useRequestHeaders(["cookie"]),
};
}
// Decision Point: Use useFetch for reactive state or $fetch for simple utility
return useFetch("http://localhost:8000" + path, {
credentials: "include",
watch: false,
...options,
headers: {
...headers,
...options?.headers,
},
});
}
While the code above is technically sound for creating a data-fetching utility, if you are running into issues related to component mounting, it suggests that the context where useApiFetch is called expects a simpler payload. If your requirement is simply to make an API call and handle the response without complex reactive state management tied directly to the calling component's lifecycle, switching from returning a useFetch object to directly executing $fetch might resolve the warning:
// Alternative implementation using $fetch for non-reactive calls
export function fetchApiData(path: string, options: Record<string, any> = {}) {
const url = "http://localhost:8000" + path;
// Logic for headers and cookies remains the same... (omitted for brevity)
return $fetch(url, {
...options,
// Headers would be injected here based on your logic
});
}
Conclusion: Choosing the Right Tool
The core takeaway is that there is no single "right" way to fetch data; there is only the appropriate way. Use useFetch when you need reactive state management tied to a component's lifecycle (e.g., displaying loading spinners, handling immediate user input based on the result). Use $fetch when you are performing simple, utility-based HTTP requests where the resulting data isn't necessarily driving the primary reactivity of that specific view.
When designing robust backend interactions—whether it’s ensuring secure token handling or structuring your payload (similar to how well-structured APIs in systems like Laravel are designed)—clarity in frontend tooling is paramount. Always assess whether you need reactivity, or just a raw response, before selecting your data fetching tool.