Skip to content

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";
  • fetchArticle({ siteId, slug }) returns null when 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 returns null. A site with a published feed but zero pages returns []; a site with no feed yet throws NOT_FOUND (below).

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:

codeWhenTypical handling
NOT_FOUNDSite has no published feed yet (manifest 404).Render an “empty blog” state, or a 404.
INVALID_RESPONSEThe 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_ERRORThe fetch itself failed (DNS, TLS, offline). status is 0.Retry or render a transient-failure state.
CONFIGA missing siteId/slug or an invalid staticBaseUrl. status is 0.Fix the call — this is a wiring bug.
RATE_LIMITED / SERVER_ERRORThe feed origin returned 429 / 5xx.Retry with backoff; surface a transient state.

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:

pages/blog/index.vue
<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.