Frameworks

React / Vite

Fetch public delivery JSON in a React client with explicit loading and failure states.

PeopleTide Content · HTTP integration guides

1. Prepare your project.

Use a React/Vite JavaScript project. Save the helper as src/delivery.js. Prefix the three public settings with VITE_: VITE_CONTENT_ORIGIN, VITE_CONTENT_ORGANIZATION_ID, and VITE_CONTENT_SITE_ID. Vite exposes them to browsers; never add an agent key.

2. Add the shared delivery helper.

Copy this helper into the path named above. It follows a single published release without exposing authoring credentials.

JavaScript
// 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.

JSX
// src/App.jsx
import { useEffect, useState } from 'react';
import { getArticle } from './delivery.js';
export default function App() {
  const [state, setState] = useState({ loading: true });
  useEffect(() => {
    let active = true;
    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' })
      .then(result => { if (active) setState({ result }); })
      .catch(() => { if (active) setState({ error: true }); });
    return () => { active = false; };
  }, []);
  if (state.loading) return <p role="status">Loading article…</p>;
  if (state.error) return <p role="alert">Content temporarily unavailable.</p>;
  if (!state.result) return <h1>Article not published</h1>;
  return <article><h1>{state.result.document.title}</h1>
    <p style={{ whiteSpace: 'pre-wrap' }}>{state.result.document.fields.body}</p></article>;
}

Choose when content refreshes.

Retrieval runs on mount; immutable URLs stay pinned for that render. Refetch explicitly if you need live updates. Client rendering shows an unavailable state, not an HTTP status for the surrounding HTML page.

Verify success and failure.

Run npm run dev and inspect the three delivery requests. Verify no Authorization header or agent key appears. Test a missing slug, an unpublished site, an outage, and unmounting before retrieval finishes.