This example fetches the English root page and its menu, handles JSON:API errors, and renders basic heading and text content without inserting untrusted HTML.
<main id="app"></main>
const endpoint = new URL('/cms/pages', window.location.origin);
endpoint.searchParams.set('filter[tag]', 'root');
endpoint.searchParams.set(
'filter[lang]',
globalThis.document.documentElement.lang || 'en',
);
endpoint.searchParams.set('include', 'menu');
endpoint.searchParams.set('fields[pages]', 'title,content,menu');
endpoint.searchParams.set(
'fields[navs]',
'parent_id,path,name,title',
);
const response = await fetch(endpoint, {
headers: {Accept: 'application/vnd.api+json'},
credentials: 'same-origin',
});
const payload = await response.json();
if (!response.ok) {
const message = payload.errors?.[0]?.detail
?? payload.errors?.[0]?.title
?? `Request failed with ${response.status}`;
throw new Error(message);
}
const page = payload.data?.[0];
if (!page) {
throw new Error('The root page was not found.');
}
const app = globalThis.document.querySelector('#app');
const pageTitle = globalThis.document.createElement('h1');
pageTitle.textContent = page.attributes.title;
app.append(pageTitle);
for (const element of page.attributes.content ?? []) {
let node;
if (element.type === 'heading') {
const level = Math.min(
6,
Math.max(2, Number(element.data.level) || 2),
);
node = globalThis.document.createElement(`h${level}`);
node.textContent = element.data.title ?? '';
} else if (element.type === 'text') {
node = globalThis.document.createElement('p');
node.textContent = element.data.text ?? '';
} else {
continue;
}
app.append(node);
}
const included = new Map(
(payload.included ?? []).map(resource => [
`${resource.type}:${resource.id}`,
resource,
]),
);
const menu = (page.relationships?.menu?.data ?? [])
.map(reference => included.get(`${reference.type}:${reference.id}`))
.filter(Boolean);
console.log('Navigation resources', menu);
In a real frontend, map every content type to a component. The text field can contain Markdown; use a Markdown renderer that fits your application and sanitize any HTML before inserting it into the DOM. See JSON:API Navigation to turn the flat menu resources into a tree.