Vue quickstart
@ghostwritr/vue is a keyless feed client for Vue 3 and Nuxt 3. You call its fetchers server-side — inside useAsyncData, a Nuxt server route, or at build time — and articleHead builds your useHead() input for SEO + JSON-LD. No API key — your siteId is the only key. This quickstart wires a Nuxt /blog index and /blog/[slug] page end to end.
-
Install the SDK. The Markdown renderer ships in the same package, at the
/article-contentsubpath:npm install @ghostwritr/vuepnpm add @ghostwritr/vueyarn add @ghostwritr/vuebun add @ghostwritr/vue -
Add your site ID to Nuxt’s
runtimeConfig.publicso it’s readable in pages viauseRuntimeConfig(). It’s the only key you need — see Your site ID.nuxt.config.ts export default defineNuxtConfig({runtimeConfig: {public: {// NUXT_PUBLIC_GHOSTWRITR_SITE_ID in the environment overrides this.ghostwritrSiteId: "",},},});.env NUXT_PUBLIC_GHOSTWRITR_SITE_ID=your-site-id -
Build the index page.
useAsyncDataruns the fetcher on the server (and dehydrates the result), so the feed read happens where it should:pages/blog/index.vue <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><template><ul><li v-for="a in articles" :key="a.id"><NuxtLink :to="`/blog/${a.slug}`">{{ a.title }}</NuxtLink></li></ul></template> -
Build the article page.
fetchArticlereturnsnullwhen the slug isn’t in the feed — turn that into a real 404 withcreateErrorso Nuxt renders your error page:pages/blog/[slug].vue <script setup lang="ts">import { fetchArticle, articleHead } from "@ghostwritr/vue";import { ArticleContent } from "@ghostwritr/vue/article-content";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" });}// <title>, description, Open Graph + Twitter, canonical, and schema.org Article JSON-LD.useHead(articleHead(article.value, { siteName: "Acme" }));</script><template><article v-if="article"><h1>{{ article.title }}</h1><ArticleContent :markdown="article.markdown" class="prose" /></article></template>
That’s a complete, server-rendered blog reading from the keyless feed. Run nuxt dev and open /blog.
What to reach for next
Section titled “What to reach for next”- Data fetching —
useAsyncData, server routes, build time, readingsiteIdfromruntimeConfig, and why there’s no shipped composable. See Data fetching. - SEO — everything
articleHeademits and how to override the canonical URL. See articleHead and SEO & JSON-LD. - Publish instantly — verify the signed
feed.updatedwebhook to purge a cache the moment a new article lands. See Instant updates. - Error handling —
nullvs. throw, and the typedGhostwritrErrorcodes. See Error handling and Error handling concepts.