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/svelte (it originates in the shared @ghostwritr/feed core), and instanceof works across transpile targets:

import { GhostwritrError } from "@ghostwritr/svelte";
  • 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 with SvelteKit’s error helper:

    import { error } from "@sveltejs/kit";
    const article = await fetchArticle({ siteId, slug });
    if (!article) throw error(404, "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, or the siteId is wrong (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 in a server load so you can branch on the code. Show an empty state for the expected NOT_FOUND case, and rethrow everything else as a 500 so SvelteKit renders your error page:

src/routes/blog/+page.server.ts
import { error } from "@sveltejs/kit";
import { fetchArticles, GhostwritrError } from "@ghostwritr/svelte";
import { env } from "$env/dynamic/private";
export async function load() {
try {
const articles = await fetchArticles({ siteId: env.GHOSTWRITR_SITE_ID });
return { articles };
} catch (err) {
if (err instanceof GhostwritrError && err.code === "NOT_FOUND") {
// No feed yet — render an empty blog rather than a 500.
return { articles: [] };
}
// INVALID_RESPONSE / NETWORK_ERROR / etc. → the error page.
throw error(500, "Couldn't load the blog");
}
}
src/routes/blog/+page.svelte
<script lang="ts">
let { data } = $props();
</script>
{#if data.articles.length}
<ul>
{#each data.articles as a}
<li><a href={`/blog/${a.slug}`}>{a.title}</a></li>
{/each}
</ul>
{:else}
<p>No articles published yet.</p>
{/if}

For a single article, the same pattern applies — but null is the empty case there, not NOT_FOUND: throw error(404) when fetchArticle resolves to null, and keep the try/catch for the thrown codes. The GhostwritrErrorCode type is re-exported from @ghostwritr/svelte if you want to narrow on it.