Skip to content

Vue — overview

@ghostwritr/vue pulls your published articles into Vue 3 and Nuxt 3 — keyless feed fetchers, a pure articleHead helper for SEO + JSON-LD, and an optional ArticleContent Markdown renderer. It works in Nuxt (server routes, useAsyncData, SSG) and in plain Vue/Vite SSR. Peer dependency: vue >=3.

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.

npm install @ghostwritr/vue
  • Keyless fetchersfetchArticles / fetchArticle, re-exported from the shared @ghostwritr/feed core, so the contract is identical across the Next / Astro / React Router / Vue SDKs. fetchArticles returns every published Article newest-first; fetchArticle returns one by slug, or null. Call them server-side — inside useAsyncData, a Nuxt server route, or at build time.
  • articleHead(article, opts?) — a pure function that returns a useHead() input: <title> (= seoTitle), description, Open Graph + Twitter cards, <link rel="canonical">, and schema.org Article JSON-LD. It returns plain head data; you decide when to call useHead. See SEO & JSON-LD.
  • ArticleContent — at the @ghostwritr/vue/article-content subpath, a render-function component that renders article.markdown to sanitized HTML (GFM on, HTML sanitized — the same contract as @ghostwritr/react). It lives at a subpath so data-only consumers don’t pull in the Markdown deps. SSR-safe (synchronous).

A blog index and an article page with SEO meta — both server-side in Nuxt:

pages/blog/index.vue
<script setup lang="ts">
import { fetchArticles } from "@ghostwritr/vue";
const { data: articles } = await useAsyncData("gw-articles", () =>
fetchArticles({ siteId: useRuntimeConfig().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>
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 { data: article } = await useAsyncData(`gw-${route.params.slug}`, () =>
fetchArticle({
siteId: useRuntimeConfig().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 is a complete, server-rendered blog reading from the keyless feed.