Frameworks
Vue
Render published content in Vue 3 with a credential-free browser loader.
PeopleTide Content · HTTP integration guides
1. Prepare your project.
Use Vue 3 with Vite. Save the shared helper as src/delivery.js and configure VITE_CONTENT_ORIGIN, VITE_CONTENT_ORGANIZATION_ID, and VITE_CONTENT_SITE_ID in .env.local. These settings are public.
2. Add the shared delivery helper.
Copy this helper into the path named above. It follows a single published release without exposing authoring credentials.
// delivery.js — no SDK or API key required. Node 22+ or a modern browser.
export async function getArticle({ origin, organization, site, slug }) {
const base = new URL(origin);
if (base.protocol !== 'https:' && !['localhost', '127.0.0.1'].includes(base.hostname)) {
throw new Error('Use an HTTPS Content origin');
}
if (![organization, site].every(id => /^[a-f0-9-]{36}$/.test(id))) {
throw new Error('Set your organization and site UUIDs');
}
const prefix = '/delivery/v1/' + organization + '/' + site + '/';
async function read(path) {
const url = new URL(path, base);
if (url.origin !== base.origin || !url.pathname.startsWith(prefix) || url.search || url.hash) {
throw new Error('Unexpected delivery URL');
}
const response = await fetch(url, {
credentials: 'omit', cache: 'no-store', redirect: 'error',
signal: AbortSignal.timeout(8000),
});
if (!response.ok) throw new Error('Content delivery returned ' + response.status);
return response.json();
}
const current = await read(prefix + 'current.json');
const manifest = await read(current.url);
const entry = manifest.entries.find(item => item.type === 'article' && item.slug === slug);
if (!entry) return null;
const document = await read(entry.url);
return { releaseId: current.releaseId, document };
}Connect and render.
Publish an article of type article with slug welcome and a body field before running the example. Split the labelled files into the paths shown.
<!-- src/App.vue -->
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { getArticle } from './delivery.js';
const article = ref(null), loading = ref(true), failed = ref(false);
let active = true;
onUnmounted(() => { active = false; });
onMounted(async () => {
try {
const result = await getArticle({ origin: import.meta.env.VITE_CONTENT_ORIGIN,
organization: import.meta.env.VITE_CONTENT_ORGANIZATION_ID,
site: import.meta.env.VITE_CONTENT_SITE_ID, slug: 'welcome' });
if (active) article.value = result?.document ?? null;
} catch { if (active) failed.value = true; }
finally { if (active) loading.value = false; }
});
</script>
<template>
<p v-if="loading" role="status">Loading article…</p>
<p v-else-if="failed" role="alert">Content temporarily unavailable.</p>
<article v-else-if="article"><h1>{{ article.title }}</h1>
<p style="white-space: pre-wrap">{{ article.fields.body }}</p></article>
<h1 v-else>Article not published</h1>
</template>Choose when content refreshes.
This is a client-rendered example. Refetch when you want a fresh release; keep one resolved result per render. Use Nuxt for server-rendered HTML and server HTTP error responses.
Verify success and failure.
Run the Vite dev server and check loading, content, missing-slug, and offline states. Vue interpolation escapes text; do not replace it with v-html for untrusted body content.