Svelte quickstart
@ghostwritr/svelte is a keyless feed client for Svelte 5 and SvelteKit. You call its fetchers in a server load (+page.server.ts), so your site id never reaches the client; <ArticleHead> writes your SEO + JSON-LD into <svelte:head>, and <ArticleContent> renders the body. No API key — your siteId is the only key. This quickstart wires a SvelteKit /blog index and /blog/[slug] route end to end.
-
Install the SDK. The Markdown renderer ships in the same package, at the
/article-contentsubpath:npm install @ghostwritr/sveltepnpm add @ghostwritr/svelteyarn add @ghostwritr/sveltebun add @ghostwritr/svelte -
Add your site ID to
.env. SvelteKit reads it server-side via$env/dynamic/private— it’s the only key you need, see Your site ID..env GHOSTWRITR_SITE_ID=your-site-id -
Build the index
load. A serverloadruns the fetcher during SSR (and prerender), then ships the result to the page:src/routes/blog/+page.server.ts import { fetchArticles } from "@ghostwritr/svelte";import { env } from "$env/dynamic/private";export async function load() {return { articles: await fetchArticles({ siteId: env.GHOSTWRITR_SITE_ID }) };} -
Render the index, newest first.
datacomes from theloadabove, via the Svelte 5$props()rune:src/routes/blog/+page.svelte <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> -
Build the article
load.fetchArticlereturnsnullwhen the slug isn’t in the feed — turn that into a real 404 with SvelteKit’serror(404)helper so the route renders your error page:src/routes/blog/[slug]/+page.server.ts 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 };} -
Render the article page.
<ArticleHead>(from@ghostwritr/svelte) writes the SEO meta into<svelte:head>;<ArticleContent>(from the@ghostwritr/svelte/article-contentsubpath) renders the body to sanitized HTML:src/routes/blog/[slug]/+page.svelte <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’s a complete, server-rendered blog reading from the keyless feed. Run vite dev and open /blog.
What to reach for next
Section titled “What to reach for next”- Data fetching — calling the keyless fetchers in a
+page.server.tsload, readingsiteIdfrom$env, and why there’s no shipped load wrapper. See Data fetching. - SEO — everything
<ArticleHead>emits and how to override the canonical URL, plus thearticleHeadpure function for a custom<svelte:head>. See ArticleHead and SEO & JSON-LD. - Publish instantly — verify the signed
feed.updatedwebhook to revalidate the moment a new article lands. See Instant updates. - Error handling —
nullvs. throw, and the typedGhostwritrErrorcodes. See Error handling and Error handling concepts.