Public Pages
Public pages are standard Astro pages that fetch content from the local API. The CMS provides helpers to keep them simple.
Basic page
Section titled “Basic page”---import { findContent, cacheTags } from "@/cms/core/content";
export const prerender = false;
const { doc, isPreview } = await findContent("posts", Astro.params.slug, Astro.url);if (!doc) return Astro.redirect("/404");if (!isPreview) Astro.cache.set({ tags: cacheTags("posts", doc._id) });---
<h1>{doc.title}</h1><p>{doc.excerpt}</p>What findContent does
Section titled “What findContent does”- Detects
?preview=truein the URL - Queries with
status: "any"in preview mode,"published"otherwise - Parses block fields from JSON
- Returns
{ doc, isPreview, blocks }
What cacheTags does
Section titled “What cacheTags does”Generates cache tag arrays for Astro’s route caching. When content changes, hooks invalidate these tags automatically.
cacheTags("posts", doc._id);// → ["posts", "post:abc123"]Route structure
Section titled “Route structure”The recommended structure separates each content type into its own route file:
src/pages/ index.astro # home page blog/[slug].astro # posts [...slug].astro # pages (catch-all)Post page
Section titled “Post page”---import PublicLayout from "@/layouts/PublicLayout.astro";import RichTextContent from "@/components/RichTextContent.astro";import { findContent, cacheTags } from "@/cms/core/content";import { cmsImage, cmsSrcset } from "@/cms/core/image";
export const prerender = false;
const { doc, isPreview } = await findContent("posts", Astro.params.slug, Astro.url);if (!doc) return Astro.redirect("/");if (!isPreview) Astro.cache.set({ tags: cacheTags("posts", doc._id) });---
<PublicLayout title={doc.title}> <h1>{doc.title}</h1> { doc.image && ( <img src={cmsImage(doc.image, 1024)} srcset={cmsSrcset(doc.image)} sizes="(max-width: 640px) 100vw, 640px" alt={doc.title} /> ) } {doc.excerpt && <p>{doc.excerpt}</p>} <RichTextContent content={doc.body} /></PublicLayout>Page with blocks
Section titled “Page with blocks”---import PublicLayout from "@/layouts/PublicLayout.astro";import BlockRenderer from "@/components/BlockRenderer.astro";import { findContent, cacheTags } from "@/cms/core/content";
export const prerender = false;
const slug = (Astro.params.slug ?? "").split("/").filter(Boolean).join("/");if (!slug) return Astro.redirect("/");
const { doc, isPreview, blocks } = await findContent("pages", slug, Astro.url);if (!doc) return Astro.redirect("/");if (!isPreview) Astro.cache.set({ tags: cacheTags("pages", doc._id) });---
<PublicLayout title={doc.title}> <h1>{doc.title}</h1> {doc.summary && <p>{doc.summary}</p>} <BlockRenderer blocks={blocks} /></PublicLayout>Blocks
Section titled “Blocks”<BlockRenderer> renders blocks automatically. Each block type maps to an Astro component in src/components/blocks/:
src/components/blocks/ Hero.astro ← renders "hero" blocks Text.astro ← renders "text" blocks Faq.astro ← renders "faq" blocks Image.astro ← renders "image" blocksThe component name maps to the block type (PascalCase → camelCase). Block fields are passed as props.
Custom block component
Section titled “Custom block component”---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 "@/cms/core/content";
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 are rendered generically — no code needed for basic blocks.
Images
Section titled “Images”Use cmsImage and cmsSrcset for optimized images:
---import { cmsImage, cmsSrcset } from "@/cms/core/image";---
<!-- Single optimized image --><img src={cmsImage(doc.image, 800)} alt={doc.title} />
<!-- Responsive with srcset --><img src={cmsImage(doc.image, 1024)} srcset={cmsSrcset(doc.image)} sizes="(max-width: 640px) 100vw, 640px" alt={doc.title}/>Images are transformed on-demand (Sharp) and cached to disk. See Assets for details.
Caching
Section titled “Caching”Content pages use Astro’s route caching with tag-based invalidation. When you save or publish content in the admin, lifecycle hooks automatically invalidate the relevant cache tags.
---// Cache this page, tagged with the collection and document IDif (!isPreview) { Astro.cache.set({ tags: cacheTags("posts", doc._id) });}---Preview requests (?preview=true) skip caching so editors always see the latest saved content.
Querying directly
Section titled “Querying directly”You can also query the local API directly without findContent:
---import { cms } from "@/cms/.generated/api";
// List published postsconst posts = await cms.posts.find({ status: "published", sort: { field: "_createdAt", direction: "desc" }, limit: 10,});
// Find by slugconst post = await cms.posts.findOne({ slug: "hello-world" });
// Find by IDconst post = await cms.posts.findById("abc123");---See Local API for the full query API.