Error handling
The fetchers draw a clean line: a missing single article is an expected null; everything else — an unseeded site, a malformed feed, a network failure — is a thrown, typed GhostwritrError. The contract fails closed: there is no API key and no authed fallback, so a bad read surfaces as an error rather than silently returning empty. See Error handling for the cross-SDK model.
GhostwritrError is re-exported from @ghostwritr/vue (it originates in the shared @ghostwritr/feed core), and instanceof works across transpile targets:
import { GhostwritrError } from "@ghostwritr/vue";null vs. throw
Section titled “null vs. throw”-
fetchArticle({ siteId, slug })returnsnullwhen the site’s feed exists but has no article at that slug (a 404 on the by-slug document). This is the normal “no such post” case — turn it into a real 404:const article = await fetchArticle({ siteId, slug });if (!article) throw createError({ statusCode: 404, statusMessage: "Not found" }); -
fetchArticles({ siteId })never returnsnull. A site with a published feed but zero pages returns[]; a site with no feed yet throwsNOT_FOUND(below).
The error codes
Section titled “The error codes”GhostwritrError carries a status (the HTTP status, or 0 for client-side errors), a code from the GhostwritrErrorCode union, and details. The codes you’ll handle:
code | When | Typical handling |
|---|---|---|
NOT_FOUND | Site has no published feed yet (manifest 404). | Render an “empty blog” state, or a 404. |
INVALID_RESPONSE | The feed manifest, a page, or an article was non-JSON or violated the article contract. | Log + alert — this is a feed-side contract violation, not a user error. |
NETWORK_ERROR | The fetch itself failed (DNS, TLS, offline). status is 0. | Retry or render a transient-failure state. |
CONFIG | A missing siteId/slug or an invalid staticBaseUrl. status is 0. | Fix the call — this is a wiring bug. |
RATE_LIMITED / SERVER_ERROR | The feed origin returned 429 / 5xx. | Retry with backoff; surface a transient state. |
Catching it in useAsyncData
Section titled “Catching it in useAsyncData”Wrap the fetch so you can branch on the code. Show an empty state for the expected NOT_FOUND case, and rethrow everything else so Nuxt renders your error page:
<script setup lang="ts">import { fetchArticles, GhostwritrError } from "@ghostwritr/vue";
const { data: articles } = await useAsyncData("gw-articles", async () => { try { return await fetchArticles({ siteId: useRuntimeConfig().public.ghostwritrSiteId }); } catch (err) { if (err instanceof GhostwritrError && err.code === "NOT_FOUND") { // No feed yet — render an empty blog rather than a 500. return []; } throw err; // INVALID_RESPONSE / NETWORK_ERROR / etc. → error page }});</script>
<template> <ul v-if="articles?.length"> <li v-for="a in articles" :key="a.id"> <NuxtLink :to="`/blog/${a.slug}`">{{ a.title }}</NuxtLink> </li> </ul> <p v-else>No articles published yet.</p></template>In plain Vue (no Nuxt) the same try/catch applies — catch inside your own loader and decide between an empty-state ref and rethrowing to your error boundary. The GhostwritrErrorCode type is re-exported from @ghostwritr/vue if you want to narrow on it.