N
Nuxtβ€’4mo ago
Quentin

Custom useFetch and error handling

Hi guys, I'm using Nuxt 3.16.1 in SSR. I'm tryin to create a custom useFetch to factorize my API calls, but what I want is : when there's an error during the server rendering, before hydration, throw the error returned by the API using my error.vue file. Here are my custom useFetch files :
// composables/useApi.js
export function useApi() {
const nuxtApp = useNuxtApp();

const setOptions = (options) => {
options ||= {};

options.onResponse = ({ response, request }) => {
if (response.status >= 400) {
console.log(response.status, request);

throw createError({
statusCode: response.status,
statusMessage: request,
});
}
};

return options;
};

const fetch = (url, options) => {
return useFetch(url, {
...setOptions(options),
$fetch: nuxtApp.$api,
});
};

const get = (url, options) => {
return nuxtApp.$api(url, {
...setOptions(options),
});
};

const post = (url, options) => {
return nuxtApp.$api(url, {
...setOptions(options),
method: 'POST',
});
};

const put = (url, options) => {
return nuxtApp.$api(url, {
...setOptions(options),
method: 'PUT',
});
};

return {
fetch,
get,
post,
put,
};
}
// composables/useApi.js
export function useApi() {
const nuxtApp = useNuxtApp();

const setOptions = (options) => {
options ||= {};

options.onResponse = ({ response, request }) => {
if (response.status >= 400) {
console.log(response.status, request);

throw createError({
statusCode: response.status,
statusMessage: request,
});
}
};

return options;
};

const fetch = (url, options) => {
return useFetch(url, {
...setOptions(options),
$fetch: nuxtApp.$api,
});
};

const get = (url, options) => {
return nuxtApp.$api(url, {
...setOptions(options),
});
};

const post = (url, options) => {
return nuxtApp.$api(url, {
...setOptions(options),
method: 'POST',
});
};

const put = (url, options) => {
return nuxtApp.$api(url, {
...setOptions(options),
method: 'PUT',
});
};

return {
fetch,
get,
post,
put,
};
}
// plugins/api.js
export default defineNuxtPlugin(() => {
const api = $fetch.create({
baseURL: 'http://myapi.com',
credentials: 'include',
headers: { accept: 'application/json' },
});

return {
provide: {
api,
},
};
});
// plugins/api.js
export default defineNuxtPlugin(() => {
const api = $fetch.create({
baseURL: 'http://myapi.com',
credentials: 'include',
headers: { accept: 'application/json' },
});

return {
provide: {
api,
},
};
});
7 Replies
kapa.ai
kapa.aiβ€’4mo ago
Hi @Quentin. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:
Quentin
QuentinOPβ€’4mo ago
In the first example, I see the status code in my SSR console, but the error is not thrown.
<script setup>
const { data } = await useApi().fetch('/v1/unauthorized-route');
console.log(data.value);
</script>
<script setup>
const { data } = await useApi().fetch('/v1/unauthorized-route');
console.log(data.value);
</script>
In the second example, the error is thrown and I see the error page. But in this case, I'll have to check the error.value each time i'm using useApi().fetch(). And i've near a hundred of those so it's not ideal.
<script setup>
const { data, error } = await useApi().fetch('/v1/unauthorized-route');

if (error.value) {
throw createError(error.value);
}

console.log(data.value);
</script>
<script setup>
const { data, error } = await useApi().fetch('/v1/unauthorized-route');

if (error.value) {
throw createError(error.value);
}

console.log(data.value);
</script>
kapa.ai
kapa.aiβ€’4mo ago
---------------------- Your approach to creating a custom useFetch composable and handling errors in Nuxt 3 is on the right track, but there are a few important details and best practices to consider for SSR error handling and integration with Nuxt's error system. Key Points from Nuxt Docs 1. Throwing Errors During SSR
To trigger Nuxt's error page (error.vue) during SSR, you should throw an error using createError inside your composable or page. This will cause Nuxt to render the error page before hydration, as you want. The recommended way is to use createError with appropriate statusCode and statusMessage. This is shown in the official docs:
const { data } = await useFetch(`/api/movies/${route.params.slug}`)
if (!data.value) {
throw createError({ statusCode: 404, statusMessage: 'Page Not Found' })
}

const { data } = await useFetch(`/api/movies/${route.params.slug}`)
if (!data.value) {
throw createError({ statusCode: 404, statusMessage: 'Page Not Found' })
}

Error Handling: createError 2. Custom useFetch Composable
When creating a custom useFetch (like your useApi), you should pass your custom $fetch instance via the $fetch option. This is the correct pattern and is documented in the Nuxt recipes:
export function useAPI<T>(url, options) {
return useFetch(url, {
...options,
$fetch: useNuxtApp().$api
})
}

export function useAPI<T>(url, options) {
return useFetch(url, {
...options,
$fetch: useNuxtApp().$api
})
}

Custom useFetch Recipe 3. Error Handling in onResponse
Throwing createError inside onResponse is valid, but you should ensure that the error is thrown in a way that Nuxt can catch it during SSR. Your current implementation is close, but you might want to set a more descriptive statusMessage and ensure you are not leaking sensitive data.
options.onResponse = ({ response }) => {
if (response.status >= 400) {
throw createError({
statusCode: response.status,
statusMessage: response.statusText || 'API Error',
})
}
}

options.onResponse = ({ response }) => {
if (response.status >= 400) {
throw createError({
statusCode: response.status,
statusMessage: response.statusText || 'API Error',
})
}
}

4. TypeScript and Error Types
If you want to type your errors, you can use the FetchError type from ofetch, as shown in the docs:
import type { FetchError } from 'ofetch'
import type { UseFetchOptions } from 'nuxt/app'

export function useAPI<T>(
url: string | (() => string),
options?: UseFetchOptions<T>,
) {
return useFetch<T, FetchError<CustomError>>(url, {
...options,
$fetch: useNuxtApp().$api
})
}

import type { FetchError } from 'ofetch'
import type { UseFetchOptions } from 'nuxt/app'

export function useAPI<T>(
url: string | (() => string),
options?: UseFetchOptions<T>,
) {
return useFetch<T, FetchError<CustomError>>(url, {
...options,
$fetch: useNuxtApp().$api
})
}

Custom useFetch Error Typing Recommendations - Throw createError in onResponse for SSR:
Your pattern is correct. Throwing createError in onResponse will trigger Nuxt's error page during SSR. - Set statusMessage appropriately:
Use response.statusText or a custom message, not the request object. - Use $fetch in useFetch:
Pass your custom $fetch instance via the $fetch option, as you are doing. - Avoid leaking sensitive data:
Do not include the full request object in the error message. Example Adjustment
// composables/useApi.js
export function useApi() {
const nuxtApp = useNuxtApp();

const setOptions = (options) => {
options ||= {};

options.onResponse = ({ response }) => {
if (response.status >= 400) {
throw createError({
statusCode: response.status,
statusMessage: response.statusText || 'API Error',
});
}
};

return options;
};
const fetch = (url, options) => {
return useFetch(url, {
...setOptions(options),
$fetch: nuxtApp.$api,
});
};

// ...get, post, put as you have them
return { fetch, get, post, put };
}
// composables/useApi.js
export function useApi() {
const nuxtApp = useNuxtApp();

const setOptions = (options) => {
options ||= {};

options.onResponse = ({ response }) => {
if (response.status >= 400) {
throw createError({
statusCode: response.status,
statusMessage: response.statusText || 'API Error',
});
}
};

return options;
};
const fetch = (url, options) => {
return useFetch(url, {
...setOptions(options),
$fetch: nuxtApp.$api,
});
};

// ...get, post, put as you have them
return { fetch, get, post, put };
}
This will ensure that during SSR, if your API returns an error, Nuxt will render error.vue as expected. For more details and examples, see the official Custom useFetch Recipe and Error Handling documentation. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.aiβ€’4mo ago
<@179707857120395271> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Quentin
QuentinOPβ€’4mo ago
The AI didn't help me at all so i'll be glad if someone have an idea to help me πŸ˜…
johntanzer
johntanzerβ€’4mo ago
Sorry. Don’t get exactly what you looking for? A full page error?
Quentin
QuentinOPβ€’4mo ago
What I want is if my API throws me a 401 error, i want to show the full page error showing 401 Unauthorized yes I solved my problem by making my fetch function async with await. Don't know if i'm doing the right way, but i didn't manage to do better so ... πŸ˜… I'm closing this issue

Did you find this page helpful?