Back to the changelog

Release

The content platform behind this site: articles built as code

The Syzygy and Astrolix sites now run a shared static content platform: a strict zod frontmatter contract, zh/en slug pairing, and build-time generation of sitemap, RSS, and a search index. No CMS, no database.

Syzygy TeamPublished 7 min read
  • #content platform
  • #static site
  • #SEO
  • #i18n
  • #build
On this page

One commit in the early hours of August 31 gave syzygysync.com a blog, a changelog, and guides, and generated the RSS feed, a site search index, and an updated sitemap in the same build. The first batch shipped seven bilingual product articles, a quick-start guide, and the changelog entry you are reading. Before that, the site had a product home page, pricing, downloads, and an llms.txt for AI crawlers, but the articles themselves had no home: the navigation entries pointed at links that did not resolve, and the only crawlable pages were marketing surfaces.

This changelog entry documents the engineering decisions behind that content platform. It serves both the Syzygy product site and the Astrolix company site; the two share one content platform package while each owns its article tree. The whole selection came down to one judgment: content is code, and the build is the release. Articles are Markdown files in Git. The Docker build scans them, validates them, and produces every static page. No blog API, no CMS, no database.

Why not a CMS

Start with the constraints. The Astrolix site has no API, not one dynamic endpoint. If publishing depended on a runtime service, the company site would either grow a backend out of nowhere or never publish an article. The Syzygy site does have a server, but that server handles accounts, entitlements, activation codes, and pair authorization; having it also mind the blog would couple public content to a commercial state machine.

The returns from static builds are concrete. Crawlers get complete, stable HTML with no JS rendering requirement. Pages cache cleanly on a CDN. The whole site image deploys offline and rolls back as a unit. Every article change has a Git diff, a review trail, and authorship. Publishing an article and shipping a release are now the same pipeline: commit, review, build, deploy.

We also drew one security line: the build never fetches remote pages or images. That keeps builds reproducible and keeps the publishing pipeline from becoming an SSRF surface. Remote images must use stable HTTPS addresses, and production articles should commit auditable local assets.

Frontmatter is a contract, not a suggestion

The platform package defines a strict zod schema, and every Markdown file's frontmatter has to pass it:

  • title, description, slug, pubDate, author, category, and lang are required;
  • slug accepts lowercase letters, digits, and hyphens only;
  • pubDate must be a YYYY-MM-DD string;
  • lang must be en or zh;
  • the schema runs in strict mode, so any unknown field is an error.

The important part is where errors surface. A parse failure, an invalid field, a duplicate slug within the same type and language, or a relative cover path that does not resolve to a real file next to the article: all of these throw during the build and fail it. Nothing waits to break on a rendered page. Drafts follow the same discipline: an article with draft: true stays out of listings, static route params, the sitemap, the RSS feed, and the search index, and its metadata emits noindex, nofollow. Drafts live in Git for review; they do not ship in the public image.

Same slug, two languages, machine-generated alternates

Bilingual here is one slug with two language variants: p2p-pairing-sync.md and p2p-pairing-sync.zh.md become a linked pair at build time. Language pairs are allowed to be asymmetric: if one side is missing, the build still passes and alternates are generated only for the sides that actually exist; adding the missing one later automatically completes the routes and the index. Because every article is parsed into a structured record with type, language, and date at load, the builder can generate mechanically:

  • hreflang alternates (en-US, zh-CN, plus x-default pointing at the default language);
  • a canonical URL per page;
  • Open Graph article metadata with published time, modified time, author, and tags;
  • previous/next navigation within the same content type.

Site roles are decided at build time too. www.syzygysync.com is the global canonical host, English by default. cn.syzygysync.com is the China site, Chinese by default, carrying filing semantics. en.syzygysync.com survives only for old bookmarks and old clients; no new links point at it. Page language resolves as URL locale, then saved user preference, then browser locale, then the build default. Global versus China pricing isolation belongs to the host and the deploy target; canonical URLs are never rewritten based on a visitor's IP.

More than HTML comes out of the build

Beyond the pages, the build produces three artifacts. sitemap.xml lists published articles only. rss.xml emits one properly escaped XML entry per article, so a special character in a title cannot break the feed. /search-index.json serves site search: each record carries the title, description, URL, type, category, and tags, plus plain text extracted from the body; the extractor strips code blocks, images, link syntax, and Markdown punctuation, leaving searchable words.

The loader itself is a pure function over the file tree. It scans .md and .mdx files under the blog, changelog, and guides directories, sorts by publish date descending with title as the tiebreaker, and the listings, RSS, and search index all consume that same sorted result. An article file outside the known content directories is a hard error; the type is determined by the first path segment, and there is no gray zone outside the convention.

The rendering boundaries are documented too. Body images use standard Markdown syntax and render as lazy-loaded <img> elements; every image must carry a meaningful alt, so a failed load still leaves the heading, caption, and link context intact; an image is never the sole information carrier. SVG works well for architecture and flow diagrams and can be referenced directly.

Article images pass through their own build gate. A script scans every image reference in the Markdown (both Markdown syntax and HTML <img>), and every local path it finds must resolve to a real file or the build fails. Images go into the public asset truth source first and sync into the app from there, because an app's public/ directory is a generated copy that gets cleared and rebuilt on every build; an image that lives only there looks fine in local preview and goes missing in production artifacts. We hit that once and turned it into a gate so it cannot recur.

The current edges

The platform stays deliberately small. Rendering is react-markdown with remark-gfm: GFM tables, task lists, code blocks, and lazy-loaded images. Arbitrary React components inside article bodies are not enabled yet; .mdx files are discoverable, but their content must stay Markdown-compatible until we have a component whitelist, build isolation, and tests worth trusting.

No admin UI is also a deliberate choice. If an editor becomes necessary, it should appear as a content production backend: the editor emits Markdown, publishing lands as a Git commit or a controlled build trigger, never public pages reading a database. The API keeps handling accounts, entitlements, and payments; publishing stays auditable, reversible, and independent of runtime services.

The platform package carries its own unit tests covering the validation failure paths and the language-linking rules, and both sites' gates include type checks, lint, and full builds. A bad frontmatter, slug, or cover makes itself known as a failed build, never as a quietly broken page.

The entry you are reading is the first changelog produced by this pipeline; the first bilingual batch that shipped alongside the platform covered the product introduction, clipboard content types, index search and PDF OCR, OCR translation and the text workbench, P2P pairing and sync, preview popups and the command palette, and tags with trash. The next entry is the full story of Syzygy 1.0.0, our first stable release.

Frequently asked questions

Does the Syzygy website use a CMS?

No. Articles are Markdown files in Git. The Docker build scans, validates, and produces static HTML plus sitemap, RSS, and a search index. There is no blog API, CMS, or database.

How are English and Chinese articles linked?

Both language versions share the same slug within a content type and differ only in their lang field. The build generates hreflang alternates, canonical URLs, and localized routes from that pairing. Drafts never reach the sitemap, RSS, or the search index.

What happens if frontmatter is wrong?

The build fails. Unknown fields, invalid slugs, malformed dates, a missing local cover file, and duplicate slugs are all build-time errors. Nothing is deferred to a runtime page.