Quickstarts
React, Vue and Angular, each in one small file. One fetch, shared across your components, with the key read from the environment.
Before you start
There is no SDK. Every framework talks to the same endpoint the same way: a GET to /v1/content with your Client key in the x-api-key header, which returns every published asset keyed by tag. Three things hold across all of them.
Use the Client key in the browser, and only the Client key. It returns published content and cannot write. The Secret key returns drafts and allows writes, and belongs on a server. Put the Client key in your environment file rather than in source: VITE_DUGGIE_CLIENT_KEYfor Vite projects,environment.duggieClientKeyfor Angular. See API Keys.Fetch once and share it. The feed is one response with every asset in it, so a provider, a store or a service loads it once and each component reads a tag. A fetch per component multiplies requests for no new information, and on a free project every request counts against the monthly allowance, cached or not. Images come with their alt text. An image asset is an object of variant URLs plus meta.alt,meta.widthandmeta.height. Readimage.large ?? image.original, because small originals have no large variant, and pass the alt text through; it is always a string, empty for a decorative image.
A shared feed and one component
Each sample is the same two pieces: a module that loads the feed once and hands out assets by tag, and a component that renders a heading and an image from it. Pass a language to get that locale's text and alt text, with the asset's fallback for anything not translated.
// src/duggie.jsx
import { createContext, useContext, useEffect, useState } from 'react';
const DuggieContext = createContext(null);
export function DuggieProvider({ children, language }) {
const [assets, setAssets] = useState(null);
useEffect(() => {
const url = new URL('https://api.duggiecms.com/v1/content');
if (language) url.searchParams.set('language', language);
fetch(url, { headers: { 'x-api-key': import.meta.env.VITE_DUGGIE_CLIENT_KEY } })
.then(res => res.json())
.then(data => setAssets(data.assets));
}, [language]);
return <DuggieContext.Provider value={assets}>{children}</DuggieContext.Provider>;
}
/** One asset by tag, or undefined until the feed has loaded. */
export function useAsset(tag) {
return useContext(DuggieContext)?.[tag];
}
// src/Hero.jsx
export function Hero() {
const heading = useAsset('hero-heading');
const image = useAsset('hero-image');
return (
<header>
<h1>{heading}</h1>
{image && <img src={image.large ?? image.original} alt={image.meta.alt} width={image.meta.width} height={image.meta.height} />}
</header>
);
}Fetching in the browser means fetching per visit
The samples above fetch when the page loads, which is the simplest thing and right for a small site. Every visit is one request, so a busy site on a free project should read the allowance on Limits and consider one of these.
Fetch on the server
Next.js, Nuxt and Angular SSR can fetch once per render and cache the result, so visitors share a copy rather than each asking the API. The Next.js tab on Getting Started shows the shape.
Fetch at build time
A static build reads the API once per deploy and visitors never touch it. A paid project can call a webhook on publish to trigger the rebuild.
What the API caches on its side, and how fresh a response can be, is on Caching and reliability.
Seeing drafts on your site
The samples fetch published content. To show a draft on the real site before it goes live, the dashboard opens your site with a short-lived token, and your code passes it back as a header. The browser-side pattern is a dozen lines and is on Live Preview.