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";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 with SvelteKit’serrorhelper:import { error } from "@sveltejs/kit";const article = await fetchArticle({ siteId, slug });if (!article) throw error(404, "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, or the siteId is wrong (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 a load
Section titled “Catching it in a load”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:
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"); }}<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.