Data fetching
fetchArticles and fetchArticle are the keyless feed fetchers, re-exported from the shared @ghostwritr/feed core. They read your published articles straight from the static feed at feeds.ghostwritr.io/{siteId}/. They are server-oriented — call them where server code runs, then dehydrate the result to the client.
import { fetchArticles, fetchArticle } from "@ghostwritr/vue";
// every published article, newest firstconst articles = await fetchArticles({ siteId });
// one by slug — or null when it isn't in the feedconst article = await fetchArticle({ siteId, slug });In Nuxt with useAsyncData
Section titled “In Nuxt with useAsyncData”The common case. useAsyncData runs the fetcher on the server during SSR/SSG and ships the result to the client as dehydrated state, so the feed read stays server-side. Read siteId from runtimeConfig.public:
<script setup lang="ts">import { fetchArticles } from "@ghostwritr/vue";
const config = useRuntimeConfig();const { data: articles } = await useAsyncData("gw-articles", () => fetchArticles({ siteId: config.public.ghostwritrSiteId }),);</script>For a single article, key the call by slug and turn a null result into a real 404 with createError:
<script setup lang="ts">import { fetchArticle } from "@ghostwritr/vue";
const route = useRoute();const config = useRuntimeConfig();const { data: article } = await useAsyncData(`gw-${route.params.slug}`, () => fetchArticle({ siteId: config.public.ghostwritrSiteId, slug: String(route.params.slug), }),);
if (!article.value) { throw createError({ statusCode: 404, statusMessage: "Not found" });}</script>In a Nuxt server route
Section titled “In a Nuxt server route”When you want the feed read to never touch the client bundle — or to add your own caching/headers — call the fetcher inside a server/ route handler and read siteId from the environment:
import { fetchArticles } from "@ghostwritr/vue";
export default defineEventHandler(() => fetchArticles({ siteId: useRuntimeConfig().public.ghostwritrSiteId }),);import { fetchArticle } from "@ghostwritr/vue";
export default defineEventHandler(async (event) => { const slug = getRouterParam(event, "slug")!; const article = await fetchArticle({ siteId: useRuntimeConfig().public.ghostwritrSiteId, slug, }); if (!article) throw createError({ statusCode: 404, statusMessage: "Not found" }); return article;});At build time
Section titled “At build time”For pure SSG, the fetchers are just async functions — call them anywhere your build runs (a Nuxt route rules prerender, a nitro prerender hook, or your own generate script):
import { fetchArticles } from "@ghostwritr/vue";
const articles = await fetchArticles({ siteId: process.env.GHOSTWRITR_SITE_ID! });const routes = articles.map((a) => `/blog/${a.slug}`);Plain Vue (Vite SSR)
Section titled “Plain Vue (Vite SSR)”Outside Nuxt there’s no useAsyncData — await the fetcher in your server entry (or data loader) and pass siteId from import.meta.env:
import { fetchArticle } from "@ghostwritr/vue";
const article = await fetchArticle({ siteId: import.meta.env.VITE_GHOSTWRITR_SITE_ID, slug,});if (!article) { // render your 404}Where siteId comes from
Section titled “Where siteId comes from”| Context | Source |
|---|---|
| Nuxt page / server route | useRuntimeConfig().public.ghostwritrSiteId |
| Build script (Nuxt or plain) | process.env.GHOSTWRITR_SITE_ID |
| Plain Vite | import.meta.env.VITE_GHOSTWRITR_SITE_ID |
The siteId is a read capability, not a secret — it’s safe in public runtimeConfig. The fetchers still run server-side.
null vs. throw
Section titled “null vs. throw”fetchArticle returns null for an unknown slug; that’s a routing miss, so convert it to a 404 with createError. Everything else — a missing feed (unseeded siteId), a network failure, a bad response — throws a typed GhostwritrError with a stable code (NOT_FOUND fails closed rather than returning an empty list). See Error handling and Error handling concepts.
Related
Section titled “Related”- Keyless reads — why your
siteIdis the only key. - The content feed — what’s behind
feeds.ghostwritr.io/{siteId}/.