Getting started
Astro
Start an Astro site on a space with npx @obelism/content init, render its pages by path on the server, and show drafts in the canvas.
This sets up an Astro site that renders every page entry at its path on the server and shows drafts in the canvas. It uses the JS SDK only; the page reloads after each save in the editor.
1. Start the repository on a space
Create a management token under Settings → API tokens, then run in the site's root:
export CONTENT_MANAGEMENT_TOKEN=cms_m_…
npx @obelism/content initinit writes the model file (content/content-model.json), an example of what the delivery API answers for every content type (content/examples/), installs @obelism/content, and adds the token variables to .env.example. Put a delivery token in .env (never commit it):
CONTENT_DELIVERY_TOKEN=cms_d_…
# The space and environment it reads (Space settings), so the canvas's
# token for another space reads nothing here.
CONTENT_SPACE_ID=space_…
CONTENT_ENVIRONMENT=production2. Render on the server
Drafts depend on the request, so the pages render on the server: set output: 'server' and add the adapter for where the site runs (Node, Cloudflare, Netlify, Vercel).
import node from '@astrojs/node'
import { defineConfig } from 'astro/config'
export default defineConfig({
output: 'server',
adapter: node({ mode: 'standalone' }),
})3. The clients
One client for published content. In the canvas the page's URL carries cms_token, a token for the person editing that reads drafts; that request gets a client of its own.
import { ContentClient } from '@obelism/content'
export const delivery = new ContentClient({
token: import.meta.env.CONTENT_DELIVERY_TOKEN,
})
// Drafts in the canvas, with the token it adds to the URL. Anyone can
// add `cms_preview=1` to a URL, so that alone shows nothing. The space
// and environment make a token for another space read nothing here.
export const getContent = (url: URL) => {
const token = url.searchParams.get('cms_token')
return token
? new ContentClient({
token,
space: import.meta.env.CONTENT_SPACE_ID,
environment: import.meta.env.CONTENT_ENVIRONMENT,
})
: delivery
}4. Every page by path
---
import {
ContentApiError,
editable,
richTextToHtml,
type Block,
type Entry,
type RichTextNode,
} from '@obelism/content'
import { delivery, getContent } from '../lib/content'
type HeroBlock = Block<{ heading: string | null }, 'hero'>
type TextBlock = Block<{ text: RichTextNode | null }, 'text'>
type PageEntry = Entry<
{ title: string | null; body: (HeroBlock | TextBlock)[] | null },
'page'
>
const path = Astro.params.slug || 'home'
const entry = await getContent(Astro.url)
.getPage<PageEntry>(path, { depth: 2 })
.catch((error: unknown) => {
// A made-up, expired or other space's `cms_token`: show what's published.
if (error instanceof ContentApiError && error.status === 401) {
return delivery.getPage<PageEntry>(path, { depth: 2 })
}
throw error
})
if (!entry) return new Response(null, { status: 404 })
// Let the canvas show the site in its frames.
Astro.response.headers.set(
'Content-Security-Policy',
"frame-ancestors 'self' https://content.obelism.studio",
)
const { fields } = entry
---
<html lang="en">
<head>
<title>{fields.title}</title>
</head>
<body>
<main {...editable(entry)}>
<h1>{fields.title}</h1>
{fields.body?.map((block) =>
block._type === 'hero' ? (
<section {...editable(block)}>
<h2>{block.heading}</h2>
</section>
) : (
<section {...editable(block)} set:html={richTextToHtml(block.text)} />
),
)}
</main>
<script is:inline src="https://content.obelism.studio/bridge.js"></script>
</body>
</html>content/examples/ shows what each of your content types looks like; adjust PageEntry to your own model.
editablemarks the entry and its blocks (data-cms-uid), so editors can click them.- The bridge script does nothing outside the editor. Inside it, the page reloads after every save; to re-render on each keystroke instead, listen for
ObelismBridge.on('update', ({ entry }) => …)(see the canvas). - The
frame-ancestorsheader lets the CMS frame the site. Set it wherever your site sets headers if it sends a stricter policy, and dropX-Frame-Options: DENY.
Then set the environment's preview URL (Settings) to where the site runs, such as http://localhost:4321, and open the Canvas.
Next steps
- Rebuild or revalidate on publish with a webhook.
- Images: resized and re-encoded copies with a query on the URL.
- A/B tests in content, with Obelism Improve.