Public Pages
Public pages are standard Astro pages that fetch content from the local API.
The scaffold
Section titled “The scaffold”New projects ship a minimal route structure:
src/pages/ index.astro # home page [slug].astro # pages by slug[slug].astro fetches a document from the pages collection and renders its body with <ContentRenderer>. The pattern, with cache tags added:
---import PublicLayout from "@/layouts/PublicLayout.astro";import ContentRenderer from "@/components/ContentRenderer.astro";import { cms } from "@/cms/.generated/api";import { cacheTags } from "@kidecms/core";
const isPreview = Astro.url.searchParams.has("preview");const doc = await cms.pages.findOne({ slug: Astro.params.slug!, status: isPreview ? "any" : "published" });if (!doc) return Astro.redirect("/404");if (!isPreview) Astro.cache.set({ tags: cacheTags("pages", doc._id) });---
<PublicLayout title={doc.title}> <h1 data-cms="title">{doc.title}</h1> <div data-cms="body"> <ContentRenderer content={doc.body} /> </div></PublicLayout>As you add collections, give each content type its own route file (blog/[slug].astro for a posts collection, and so on) following the same pattern.
Live preview
Section titled “Live preview”The edit form’s Preview link opens the page with ?preview=<collection>:<id>. While that tab is open, it updates as the editor types, before saving. Pages opt in with the parts shown in the snippet above:
- Fetch with
status: isPreview ? "any" : "published"so drafts render in preview.?previewrequires a signed-in session; it is stripped for everyone else. - Add
data-cms="{fieldName}"to the elements that render CMS fields. Text fields update in place;richText,content, andblocksfields are re-rendered on the server (contentandblocksthrough yourContentRendererandBlockRenderercomponents). Elements without the attribute update when the editor saves, which reloads the preview tab.
Preview responses are never cached: the middleware turns caching off and sends Cache-Control: no-store for any ?preview request.
The preview script is injected on every page automatically and does nothing unless the URL has a ?preview=<collection>:<id> key. A bare ?preview shows drafts without live updates.
The Preview link appears for collections with pathPrefix, preview: true, or preview: "/url"; see Preview.
Querying
Section titled “Querying”Fetch content through the typed local API, for example a listing page:
---import { cms } from "@/cms/.generated/api";
const posts = await cms.posts.find({ sort: { field: "_createdAt", direction: "desc" }, limit: 10 });---All query options are in Local API.
Caching
Section titled “Caching”Content pages use Astro’s route caching with tag-based invalidation. Three parts: the Astro config that enables caching, the tags a page sets, and the hooks that invalidate them.
Astro config
Section titled “Astro config”Route caching needs a cache provider and route rules in astro.config.mjs — without them, Astro.cache.set() is a no-op:
import { defineConfig, memoryCache } from "astro/config";
export default defineConfig({ // ... cache: { provider: memoryCache(), }, routeRules: { "/": { maxAge: 86400, swr: 3600 }, "/blog/**": { maxAge: 86400, swr: 3600 }, },});Tagging pages
Section titled “Tagging pages”---Astro.cache.set({ tags: cacheTags("posts", doc._id) });---cacheTags("posts", doc._id) returns ["posts", "post:abc123"]: the collection tag for listings, the document tag for its own page. Preview requests are excluded from caching automatically.
Invalidating on content changes
Section titled “Invalidating on content changes”Invalidation is code you write: nothing invalidates per-document tags automatically. Add after* hooks to the collection and call context.cache?.invalidate():
import { cacheTags } from "@kidecms/core";
hooks: { afterPublish(doc, context) { context.cache?.invalidate({ tags: cacheTags("posts", String(doc._id)) }); },},Add the same call to afterUpdate, afterUnpublish, and afterDelete as needed. The only built-in invalidation is deleteMany, which clears the collection tag. Hook signatures are in Hooks.
Block-driven pages (optional pattern)
Section titled “Block-driven pages (optional pattern)”For landing-page-style content, define a collection with a blocks field and render it with <BlockRenderer>:
---// e.g. src/pages/[...slug].astroimport PublicLayout from "@/layouts/PublicLayout.astro";import BlockRenderer from "@/components/BlockRenderer.astro";import { cms } from "@/cms/.generated/api";import { cacheTags, parseBlocks } from "@kidecms/core";
const isPreview = Astro.url.searchParams.has("preview");const doc = await cms.landing.findOne({ slug: Astro.params.slug!, status: isPreview ? "any" : "published" });if (!doc) return Astro.redirect("/404");if (!isPreview) Astro.cache.set({ tags: cacheTags("landing", doc._id) });
const blocks = parseBlocks(doc.blocks);---
<PublicLayout title={doc.title}> <h1>{doc.title}</h1> <BlockRenderer blocks={blocks} /></PublicLayout><BlockRenderer> maps each block type to an Astro component in src/components/blocks/ (PascalCase filename → camelCase block type — Hero.astro renders hero blocks). Block fields are passed as props:
---const { eyebrow, heading, body, ctaLabel, ctaHref } = Astro.props;---
<section> {eyebrow && <p>{eyebrow}</p>} <h2>{heading}</h2> {body && <p>{body}</p>} {ctaLabel && ctaHref && <a href={ctaHref}>{ctaLabel}</a>}</section>For fields that store JSON arrays (repeaters, image lists), use the parseList helper:
---import { parseList } from "@kidecms/core";
const { heading, items: rawItems } = Astro.props;const items = parseList<{ title?: string; description?: string }>(rawItems);---
<h2>{heading}</h2>{ items.map((item) => ( <div> <p>{item.title}</p> <p>{item.description}</p> </div> ))}Block types without a matching component render generically. No code is needed for basic blocks.
Images
Section titled “Images”Render image fields with <CmsImage>, which applies the asset’s focal point, emits responsive AVIF and WebP sources, and prevents layout shift:
---import CmsImage from "@/components/CmsImage.astro";---
<CmsImage src={doc.image} alt={doc.title} preset="card" />For a URL string (an og:image tag, a CSS background), use cmsImageUrl(). Presets, art direction, and the full prop list are in Image optimization.