Connecting a website
A client website shows Headra content by fetching it from the API with JavaScript (or at build time) and putting it into the page. The website can be plain HTML on any hosting, a Bootstrap Studio export, or a React, Vue, Next or Nuxt app.
How it fits together
- Headra (
https://app.headracms.com) stores the content and answers API requests with JSON. - The client website loads two small scripts, asks the API for content with a public key, and renders it into its own HTML and CSS.
- Search engines read the sitemap and robots.txt, which Headra serves on the client's API domain. Nothing extra is uploaded to the client's hosting.
What you need
| API address | https://app.headracms.com |
|---|---|
| Public API key | Create one under API keys (kind: public). It's read-only and only sees published content, so it's safe in website code. |
| Files | config.js and headra.js, plus your own HTML pages. The starter kit below has everything. |
Option A: start from the starter kit
A complete, working mini-site in plain HTML and JavaScript, with no build step and no framework: a magazine-style blog (cards, category chips, search, pages, articles with related posts) with a light/dark switcher, styled in one plain CSS file.
| File | What it does | |
|---|---|---|
index.html |
Blog page: latest posts with search, category filter and pages. | Download |
post.html |
One post: title, author, image, body, categories and tags. Sets SEO tags and counts the view. | Download |
collection.html |
A list of any content type (collection.html?type=…). | Download |
item.html |
One item of any type, every field rendered (item.html?type=…&slug=…). | Download |
config.js |
Settings: API address, public key, menu and which fields are rich text. Edit this one. | Download |
headra.js |
The Headra client: fetches content, renders menus, sets SEO tags, counts views. No changes needed. | Download |
styles.css |
Basic styles. Replace with your own design. | Download |
README.md |
Notes for developers. | Download |
- Put the files in the website's folder.
- Open
config.jsand paste the public key in place ofhcms_pub_REPLACE_ME. - Open
index.htmlin a browser. Posts, the menu and the footer links to the sitemap appear. - Restyle
styles.cssand the HTML to match the design, or copy the parts you need into the real site (Option B).
Option B: add content to an existing site (e.g. Bootstrap Studio)
Every page that shows content needs the two scripts, in this order, just before </body>:
<script src="config.js"></script>
<script src="headra.js"></script>
<script src="latest-posts.js"></script> <!-- your own code for this page -->
In Bootstrap Studio, add config.js, headra.js and your own script under Design → JavaScript, in that order. Then add an empty element with an id where the content should go, e.g. a div with id latest-posts.
Publishing on Bootstrap Studio's own hosting with a custom domain? Bootstrap Studio hosting covers the whole setup, including Cloudflare and the sitemap.
A list of posts: latest-posts.js
(async () => {
const { api, esc, image, field, date } = Headra;
const result = await api('/api/posts', { per_page: 6, fields: 'excerpt,featured_image' });
document.querySelector('#latest-posts').innerHTML = result.data.map((post) => `
<div class="col-md-4">
<a href="post.html?slug=${encodeURIComponent(post.slug)}">${image(post.fields.featured_image, 'medium')}</a>
<h3>${esc(post.title)}</h3>
<p class="text-muted">${esc(date(post.published_at))}</p>
${field('excerpt', post.fields.excerpt)} // rich text (basic): inserted as HTML
</div>`).join('');
})();
One post, with SEO tags and view counting: post.js
(async () => {
const { api, esc, image, field, seo, track, query } = Headra;
const result = await api(`/api/posts/${encodeURIComponent(query.get('slug'))}`);
if (!result) { document.querySelector('#post').innerHTML = '<p>Not found.</p>'; return; }
const post = result.data;
seo(post); // <title>, description, author, Open Graph, Twitter, JSON-LD
document.querySelector('#post').innerHTML = `
<h1>${esc(post.title)}</h1>
${image(post.fields.featured_image, 'large')}
${field('body', post.fields.body)}`; // rich text: inserted as HTML
track('posts', post.slug); // counts a view for Analytics
})();
A menu
Headra.chrome(); // renders config.menu into <nav data-menu> and the footer into <footer data-footer>
To build your own markup instead, use (await Headra.api('/api/menus/header')).data.items. Each item has label, url, target and children.
What headra.js gives you
api(path, params) | GET from the API with the key. Returns JSON, or null on 404. |
esc(value) | Escapes text for HTML. Use it on every value except rich text. |
image(media, size) | An <img> with srcset and lazy loading. Sizes: thumbnail, medium, large. |
field(name, value) | Renders any field value (rich text, images, galleries, links, lists). |
seo(item) | Sets the page's title, meta and social tags, and its structured data. |
track(type, slug) | Counts a page view. Free: it doesn't use the API allowance. |
chrome() | Renders the configured menu and the footer. |
query, date(iso), link(type, slug) | Small helpers for the page's URL parameters, dates and links. |
Option C: React, Vue, Next, Nuxt or a build step
No library is needed: it's a JSON API. Send the key in the X-Api-Key header.
const res = await fetch('https://app.headracms.com/api/posts?per_page=10', {
headers: { 'X-Api-Key': process.env.HEADRA_PUBLIC_KEY },
});
const { data, meta } = await res.json();
For static generation (Next getStaticProps, Nuxt generate, Eleventy, …), fetch at build time and rebuild when content changes. Never use a private key in browser code.
Useful queries
/api/posts?page=2&per_page=12 | Paging (meta.total_pages in the reply) |
?search=penalty | Full-text search |
?category=news, ?tag=launch | Filter by category or tag slug (comma = any of) |
?sort=-published_at, ?sort=title | Sort (- = descending) |
?filter[field]=value | Filter on a custom field (also [gte], [lt], [in] …) |
?fields=excerpt,featured_image | Only return these fields (smaller, faster) |
/api/categories, /api/tags, /api/authors | Taxonomy and authors |
/api/redirects/resolve?path=/old-page | On a 404 page: is there a redirect? |
The full reference is docs/api.md in the Headra repository.
Rules to follow
- Escape everything except rich text fields. Plain text can contain
<and must be shown as text (esc(), or your framework's normal binding). Rich text is cleaned by Headra and is safe to insert as HTML. - Videos in rich text arrive as
<figure class="video video-full">(orvideo-half, plus any classes the editor added) around a 16:9 box (<div class="ratio ratio-16x9 embed-responsive embed-responsive-16by9">) holding a YouTube or Vimeo<iframe>. Bootstrap 5 and 4 sites size the box already, so they only need.video-half { width: 50%; }. Other sites add:.video .ratio { position: relative; padding-top: 56.25%; } .video iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0; } .video-half { width: 50%; } - Only public keys belong in website code.
- Links: items carry
url, built from the website's site URL and the type's URL pattern. Make the site's real page addresses match those patterns, so the sitemap and canonical links point to real pages. Hosting that only serves plain files can use a query string, e.g./post.html?slug={slug}. - Caching: responses are cached by Headra and refresh within seconds of a change (scheduled posts within 5 minutes). Browsers may reuse a response for 60 seconds.
- Errors come back as
{"error": {"status", "message"}}. A 404 means not found or not published.
Launch checklist
- The website's site URL is set to the live address (Account → Websites).
- Content types that have pages have a URL pattern matching the site's real addresses.
- A public key is in
config.js(and no other kind of key is anywhere in the site). - An API domain is added, and
sitemap.xmlloads on it. The sitemap is submitted to Search Console and Bing. - Pages call
seo()(or set the tags themselves) andtrack(). - Check a page in the browser's developer tools: no errors in the console, and the API requests return 200.