Svelte — overview
@ghostwritr/svelte pulls your published articles into Svelte 5 and SvelteKit — keyless feed fetchers, an <ArticleHead> component (plus a pure articleHead helper) for SEO + JSON-LD, and an optional <ArticleContent> Markdown renderer. It works in SvelteKit (server load, SSR, SSG/prerender) and in plain Svelte. Peer dependency: svelte >=5 — it uses Svelte 5 runes.
It is keyless: your siteId is the read capability, there is no API key and no authed fallback. Reads come straight from the static feed at feeds.ghostwritr.io/{siteId}/. A site with no published feed fails closed with a NOT_FOUND GhostwritrError rather than returning an empty list — so you can tell “nothing published yet” apart from a real failure. See Error handling.
npm install @ghostwritr/svelte pnpm add @ghostwritr/svelte yarn add @ghostwritr/svelte bun add @ghostwritr/svelte The three pieces
Section titled “The three pieces”- Keyless fetchers —
fetchArticles/fetchArticle, re-exported from the shared@ghostwritr/feedcore, so the contract is identical across the Next / Astro / React Router / Vue / Svelte SDKs.fetchArticlesreturns every published Article newest-first;fetchArticlereturns one by slug, ornull. Call them inside a SvelteKit serverloadso the site id stays off the client. <ArticleHead>— a Svelte component that renders<title>(=seoTitle), description, Open Graph + Twitter cards,<link rel="canonical">, and schema.orgArticleJSON-LD into<svelte:head>. Drop it into a page:<ArticleHead {article} siteName="Acme" />. Need a custom<svelte:head>? Import the purearticleHead(article, opts?)function instead and render the{ title, meta, link, script }pieces yourself. See SEO & JSON-LD.<ArticleContent>— at the@ghostwritr/svelte/article-contentsubpath, a component that rendersarticle.markdownto sanitized HTML via{@html}(GFM on, HTML sanitized — the same contract as@ghostwritr/react/@ghostwritr/vue). It lives at a subpath so data-only consumers don’t pull in the Markdown deps. SSR-safe (synchronous).
The 30-second example
Section titled “The 30-second example”A blog index and an article page with SEO meta — both fetched in a server load:
import { fetchArticles } from "@ghostwritr/svelte";import { env } from "$env/dynamic/private";
export async function load() { return { articles: await fetchArticles({ siteId: env.GHOSTWRITR_SITE_ID }) };}<script lang="ts"> let { data } = $props();</script>
<ul> {#each data.articles as a} <li><a href={`/blog/${a.slug}`}>{a.title}</a></li> {/each}</ul>import { error } from "@sveltejs/kit";import { fetchArticle } from "@ghostwritr/svelte";import { env } from "$env/dynamic/private";
export async function load({ params }) { const article = await fetchArticle({ siteId: env.GHOSTWRITR_SITE_ID, slug: params.slug, }); if (!article) throw error(404, "Not found"); return { article };}<script lang="ts"> import { ArticleHead } from "@ghostwritr/svelte"; import { ArticleContent } from "@ghostwritr/svelte/article-content"; let { data } = $props();</script>
<!-- <title>, OG/Twitter, canonical, and schema.org Article JSON-LD into <svelte:head> --><ArticleHead article={data.article} siteName="Acme" />
<article> <h1>{data.article.title}</h1> <ArticleContent markdown={data.article.markdown} class="prose" /></article>That is a complete, server-rendered blog reading from the keyless feed.