Vue
Read Graft content from Vue code, and render MDX with Vue components.
@usegraft/sdk-vue gives you Vue composables that read content over HTTP, and a
component that renders MDX bodies with your own Vue components. Use it when:
- your Vue app runs in the browser, such as a single-page app built with Vite,
- you load content after the page is on screen, such as a live search box, or
- you render MDX bodies with Vue components like a
<Callout>.
The composables call Graft's Content API, so something has
to serve it: graft serve, or the Content API handler mounted in your own
backend. The browser never receives a database connection or the index file.
Before you start, you need a graft.config.ts and a running Content API.
By the end of this page you have a typed client, a component that shows one
document, a search box, and an MDX body rendered with Vue components.
Install
npm i @usegraft/sdk-vuepnpm add @usegraft/sdk-vuebun add @usegraft/sdk-vueIt needs Vue 3.5 or newer.
Create the client and composables
Create the client once, pointed at your Content API, and build the composables from it:
import { createGraft, createGraftComposables } from "@usegraft/sdk-vue";
import { collections } from "../graft.config";
export const graft = createGraft({
endpoint: "https://cms.example.com/api/content/v1",
collections,
});
export const { useContent, useContentList, useContentSearch } = createGraftComposables(graft);The composables are typed from your own graft.config.ts, so
post.data.title is checked at compile time. There is no code generation step
and no schema to download.
They read from this client by default. To point part of your app somewhere
else, such as a preview deployment's endpoint or a fake client in tests, call
provideGraft(other) in a component's setup, or install
graftPlugin(other) on the app.
Show one document
useContent reads a document by collection and slug. It returns data,
error and loading as refs, and a refresh function to try again.
The slug can be a plain string, a ref or a getter. Pass a getter and the read follows it, so this component reads again when the route changes:
<script setup lang="ts">
import { useRoute } from "vue-router";
import { useContent } from "./graft";
const route = useRoute();
const { data: post, loading, error, refresh } = useContent("posts", () => String(route.params.slug));
</script>
<template>
<p v-if="loading">Loading…</p>
<p v-else-if="error">Could not load this post. <button @click="refresh">Try again</button></p>
<p v-else-if="!post">Post not found.</p>
<article v-else>
<h1>{{ post.data.title }}</h1>
</article>
</template>data is null when the collection has no document with that slug, so a
missing post is not an error. When the slug changes, data clears in the same
tick, so the page never shows the old post under the new address. An answer
that arrives after the slug changed, or after the component is gone, is
dropped.
Add a search box
useContentSearch runs a full-text search in one collection, best match first.
Pass the input's ref straight in. An empty query returns no results and sends no
request:
<script setup lang="ts">
import { ref } from "vue";
import { useContentSearch } from "./graft";
const query = ref("");
const { data: hits, loading, error } = useContentSearch("docs", query, { limit: 10 });
</script>
<template>
<input v-model="query" type="search" />
<p v-if="loading">Searching…</p>
<p v-else-if="error">Search failed.</p>
<ul>
<li v-for="hit in hits ?? []" :key="hit.slug">
<a :href="`/docs/${hit.slug}`">{{ hit.data.title }}</a>
</li>
</ul>
</template>Each change to a non-blank query sends one request. For a busy endpoint, debounce the input before you pass it on.
Each hit also has a snippet with each match wrapped in <b>…</b>. The rest of
the snippet is your content, so do not render it with v-html unless you trust
everyone who can write content.
Render MDX with Vue components
A document's body is its MDX source as written. Rendering it takes two steps:
compile it on your server with prepareMdx, then render the result in the page
with MdxContent. The compiler stays on the server, and the page only runs its
output, so the same component renders on the server and hydrates in the browser.
Compile in a server route. prepareMdx comes from its own entry,
@usegraft/sdk-vue/server, so the compiler never reaches the browser bundle:
import { prepareMdx } from "@usegraft/sdk-vue/server";
// In any server route: read the document, compile its body, send the string.
export async function compiledBody(body: string): Promise<{ code: string }> {
return { code: await prepareMdx(body) };
}Then render it, passing every component the body uses:
<script setup lang="ts">
import { MdxContent } from "@usegraft/sdk-vue";
import Callout from "./Callout.vue";
defineProps<{ code: string }>();
</script>
<template>
<MdxContent :code="code" :components="{ Callout }" />
</template>A component receives the content between its tags as its default slot, so
<Callout tone="warn">Careful.</Callout> in the body renders Callout.vue
with Careful. in its <slot />.
Use Nuxt's data fetching instead
The composables keep the latest result in refs, and nothing more. During server
rendering they return their loading state, so the first HTML has no content.
In Nuxt, use @usegraft/sdk-nuxt instead. Its useGraftContent,
useGraftList and useGraftSearch read on the server, put the result into the
page, and register MdxContent as <GraftMdx>. See Nuxt.
What the browser cannot do
- Reach the database. The client only speaks HTTP. It has no option for a database connection, so a connection string cannot end up in your bundle.
- Choose a branch. Each Content API endpoint serves one branch, set by the
server. On an endpoint-backed handle, passing
branchtocreateGraft, to a read, or to a read throughgraft.clientthrowsCONFIG_INVALID, evenbranch: "main". A handle built on your ownindextakesbranchas usual. To read a preview branch, point a second client at the preview deployment's endpoint. - Compile MDX. Compiling stays on the server.
MdxContentonly runs whatprepareMdxproduced.
Next steps
- Content API shows how to serve the endpoint, with
graft serveor mounted in your own backend. - MDX safety explains the trust levels.
- SDK reference lists every method on the client.