@strand-cms/core
v0.3.2
Published
Strand core: content schema, MDX loader, and SEO/GEO generators.
Downloads
298
Maintainers
Readme
@strand-cms/core
The core of Strand, an agent-first publishing system: the validated content schema, the MDX-in-Git loader, and the SEO/GEO generators every Strand site emits. No database, no CMS UI — posts are MDX files with strict frontmatter, and everything here is a pure function over them.
npm install @strand-cms/coreSchema (@strand-cms/core/schema)
Zod schemas that define the content contract agents write against:
PostFrontmatter— the post contract: slug, title, description (50–160 chars), status;SiteConfig.descriptionis 25–160 (draft/scheduled/published), tags, authors, SEO fields, cited sources, FAQ, and AI-search targeting:contentType(guide/comparison/roundup/news/explainer),primaryKeyword, and per-articlekeywords(5–8).AuthorFrontmatter— author entities for E-E-A-T markup.SiteConfig/RoutesConfig— site identity and URL layout, withpostPath,postUrl,tagPath,authorPathhelpers, and the opt-ingenerateOgImagesflag for theme-generated per-post social cards (off by default — a post with no image source still publishes, just withoutog:image).SourcePolicy+checkSourcePolicy(sources, policy)— mechanical enforcement of editorial sourcing rules; returns one error string per violating source.TopicsPolicy(site.topics) +indexableTag(tag, postCount, policy)+checkTagPolicy(tags, policy)— mechanical defense against thin tag-page sprawl (v0.3.0). Tag pages that aren't a declared pillar, don't have acornerstonesentry, and sit belowindexMinPostsshould rendernoindex,follow;buildSitemapdrops them from the sitemap automatically.aliasesnames known duplicate tags (usa→us) so site validators can warn on them. Omitsite.topicsentirely for the old index-every-tag behavior.
Each schema is exported as both the Zod value and the inferred TypeScript type.
Client vs server (Next.js)
The @strand-cms/core barrel includes the filesystem loader (node:fs), so it
is server-only. Importing it from a "use client" component makes Turbopack
try to bundle node:fs for the browser and the app fails at runtime.
- Client-safe:
import { postPath, tagPath, authorPath } from "@strand-cms/core/schema"(SiteConfig,RoutesConfigtypes too — purezod, nofs). - Server-only:
loadPosts/loadPost/loadAuthors/validatePostFile(from@strand-cms/coreor@strand-cms/core/server). - Pattern: compute link lists in a Server Component (or
lib/*.tsserver module) and pass plain{ href, label }objects into"use client"components. Never import the barrel — orlib/strand.tsif it wraps the loader — from client code.
Loader
Filesystem in, typed content out:
import { loadPosts, loadPost, loadAuthors, isLive } from "@strand-cms/core";
const posts = loadPosts("content/posts"); // live posts, newest first
const post = loadPost("content/posts", "my-slug"); // one post, drafts includedloadPosts(dir, opts)/loadPost(dir, slug)— parse + validate MDX files; eachPostcarries frontmatter, body, and computed reading time.loadAuthors(dir)/loadAuthor(dir, id)— author files.isLive(frontmatter)— published, or scheduled with a publish time in the past.validateFrontmatter(data)/validatePostFile(path)— field-levelValidationResults; these backstrand validateand the MCPvalidate_posttool.
SEO generators
import { buildMetadata, postGraph, sitemapXml, buildRss, buildRobots } from "@strand-cms/core";buildMetadata(post, site, routes)— title, description, canonical, per-article keywords, fullrobots/googlebotpreview directives (max-snippet:-1, max-image-preview:large, max-video-preview:-1),ai-content-type/ai-topichint tags,article:modified_time(fromupdatedAt, falling back topublishedAt), and OpenGraph + Twitter cards as framework-agnosticPageMetadata. Descriptions are clamped withmetaDescription()(Bing SEO/GEO hard window 25–160).metaDescription(text)— word-boundary clamp helper for site/tag/dynamic templates.articleJsonLd/faqJsonLd/breadcrumbJsonLd/websiteJsonLd/postGraph— JSON-LD (Article/NewsArticle/BlogPosting,FAQPage, breadcrumbs) ready to embed.sitemapXml,buildRss,buildRobots— the classic crawl surface.
GEO generators (AI search)
For ChatGPT / Perplexity / Claude / AI Overviews:
buildLlmsTxt/buildLlmsFullTxt— thellms.txtindex and full-content variant.mdxToMarkdown(body)— strip MDX to clean markdown (source text, not hydrated DOM).renderPostMarkdown(post, …)— the content-negotiated.mdversion of a post page.
All generators are deterministic over the MDX, so their output belongs in the static build — emit at build time, rebuild on merge.
Used by
@strand-cms/cli (MCP server +
validation), @strand-cms/content-api
(headless JSON layer), and the default Next.js theme scaffolded by
create-strand.
MIT.
Crawl surface (hard rule)
Never platform-redirect /robots.txt / /sitemap.xml / /llms.txt across hosts
(Bing Soft 404). Use CRAWL_SURFACE_PATHS, isCrawlSurfacePath, and
hostCanonicalRedirectUrl from this package inside Next.js proxy.ts.
site.url must be an https origin with no trailing slash.
