Show navigationHide navigation

Public site and content reads

Public components#

Components for rendering CMS content on public pages are exported from octocms/components/public:

ExportPurpose
MarkdownContentRender a markdown field value using react-markdown + remark-gfm + rehype-sanitize
RichTextContentRender a richtext field AST (RichTextDocument) to React
SearchBoxClient-side search input that calls the /api/search route
PublicSiteShellSample site layout wrapper (navigation, footer)
getPostBlogHref, toPlainText, toExcerpt, getReadingMinutes, getSiteTimeZone, formatPostPublishedLabelContent utility helpers
tsx
import MarkdownContent from 'octocms/components/public/MarkdownContent';
// or via barrel:
import { MarkdownContent, RichTextContent, SearchBox } from 'octocms/components/public';

Query API and cached accessors#

A typical pattern is to create cached helpers that wrap query() with "use cache" and cacheTag():

ts
import { query } from 'cms/__generated__/query';
import { OCTOCMS_PUBLIC_CONTENT_CACHE_TAG } from 'octocms/lib/publicContentCacheTag';
import { cacheTag, cacheLife } from 'next/cache';

export async function getPublishedPosts() {
  'use cache';
  cacheTag(OCTOCMS_PUBLIC_CONTENT_CACHE_TAG);
  cacheLife('hours');
  return query('post')
    .filter((p) => p.fields.publishedAt != null)
    .sort('publishedAt', 'desc')
    .toArray();
}

This keeps data loading in one place and matches what buildJsons() invalidates on save. See Query API — Caching for details.

Note: query() automatically excludes entries with sys.status of draft or archived before any user-defined filters run. Only published, changed, and merged entries are returned. See Entry status filtering.

Draft Mode#

To preview unpublished content, use .includeDrafts() with Next.js Draft Mode:

ts
import { draftMode } from 'next/headers';
import { query } from 'octocms/query';

export default async function BlogPage() {
  const { isEnabled } = await draftMode();
  const q = query('post').sort('publishedAt', 'desc');
  const posts = isEnabled
    ? await q.includeDrafts().toArray()
    : await q.filter((p) => p.fields.publishedAt != null).toArray();
  // ...
}

See Next.js integration guide — Draft Mode for the full setup (enable/disable routes, secret validation).

There is no queries map in cms/octocms.config.ts; public pages choose collections and filters in code.

See Content model for collection and field definitions.

On-demand revalidation endpoint#

GET /api/revalidate/<tag> expires a cache tag (use octocms:content to match OCTOCMS_PUBLIC_CONTENT_CACHE_TAG / buildJsons) for automation and webhooks. It is not needed for normal editing — Save, Add entry, and Delete already invalidate caches automatically.

Media#

GET /media/<uuid>.<ext> serves image bytes (from disk in dev, from GitHub in typical production setups). See Media.

Published time on the public site#

Home and blog pages show each post’s publishedAt using US English (en-US) medium date style, plus short time when the stored value includes a time. Formatting uses the IANA zone from the SITE_TIMEZONE environment variable (see Installation); the default is America/New_York. Invalid zone names fall back to UTC.

YYYY-MM-DD (date-only) values are interpreted as the first minute on that calendar date in the zone (normally local midnight; if a DST transition skips it, the first existing minute on that date). Resolution uses Intl.DateTimeFormat only—no extra date libraries. Full ISO 8601 date-times are absolute instants, then shown in the same zone. No request “wall clock”, so pages prerender normally on Node.js and Vercel.