WORK · PUBLIC DISCLOSURE

Separating the Public Site from the CMS

Keeping a static public site and an admin-only React editor inside clear runtime, dependency, and authentication boundaries.

Architecture and engineering

An open cream paper structure and a sheltered charcoal structure joined by one terracotta passage.

PROBLEM

What needed attention

A browser-based editor needed authenticated repository access without turning the public site into a continuously running application.

CONSTRAINTS

What narrowed the decision

  • Keep React and Decap CMS code out of public routes and bundles.
  • Never place OAuth secrets or token exchange logic in static site output.
  • Isolate the required OAuth runtime from the static site and restrict it to the exact CMS origin.
  • Preserve a simple static deployment model for visitors.

RESPONSIBILITIES

Where the contribution sat

  • Define the build and import boundary between public and admin code.
  • Configure the admin shell and its content previews under the admin route.
  • Keep authentication credentials outside the repository and generated output.
  • Give the public and admin surfaces distinct caching, crawler, and security policies.

IMPORTANT DECISIONS

What was chosen and why

  • Allow React only inside the administration source and output boundary.
  • Use Decap’s GitHub backend with a dedicated, hardened Cloudflare OAuth Worker.
  • Deploy static output through Cloudflare Pages Git integration.
  • Give public and admin routes separate security and caching headers.

One repository, two different responsibilities

AR/146 contains a public site and a content editor. They live near each other, but they should not behave like one application.

The public site reads approved content and produces static files. The editor provides an authenticated interface for changing repository content. Each part has different users, risks, dependencies, and delivery needs.

Treating them as separate modules keeps those differences visible.

The public site stays static

Visitors do not need a React runtime to read an essay, move through a trail, or look at a photo story. Astro renders those pages during the build. Small scripts add theme preference, search filters, the command menu, and focus mode, but they do not own the page content.

This keeps the main experience simple. It also prevents administration dependencies from entering public bundles by convenience. Imports from React and Decap remain below the admin source boundary.

The build checks the output for this rule. Architecture is stronger when it can be verified, not only remembered.

The editor is allowed to be an application

Editing has different needs. A content author benefits from forms, live previews, validation messages, editorial states, and authentication feedback. React is reasonable in that protected area because the interaction is the product there.

The important choice is not to avoid a dependency everywhere. It is to put the dependency where its cost and behavior match the job.

The editor writes Git-backed content. It does not become the public rendering engine, and the public site does not need a database to support it.

Authentication stays outside the static build

A static page cannot safely hold a client secret. The production editor therefore opens a dedicated Cloudflare Worker that owns the GitHub callback and authorization-code exchange outside the Pages build.

The static configuration contains only the repository identifier, canonical site identifier, and Worker URL. GitHub and state secrets remain in Cloudflare Worker bindings. Signed short-lived state, PKCE, exact origins, and exact popup messaging narrow the dynamic boundary while keeping the browser bundle free of credentials.

Headers express the same separation

Public pages and administration pages do not share the same trust needs. Their content security policies, caching rules, robots behavior, and privacy boundaries are configured separately.

The public surface can be cached as static content. The admin surface allows only the provider and GitHub connections needed for editing and must not be treated as ordinary indexable content.

A boundary is useful when it guides change

This architecture does not claim that separate parts can never affect each other. A new content field may require schema, CMS, preview, and public-rendering updates. The boundary instead says how that change should travel.

Public rendering stays static. Admin dependencies stay under the admin route. Authentication secrets stay outside the repository. When these rules remain clear, each part can change without quietly taking ownership of the others.

OUTCOME & LESSONS

What changed or became clearer

  • Public pages remain static and do not ship the admin React runtime.
  • The editor can evolve without changing the public rendering architecture.
  • OAuth secrets stay in Cloudflare bindings and never enter the static site or repository.
  • Merges to main trigger static deployments without a repository deployment workflow.

TECHNICAL CONTEXT

Tools and technologies

  • Astro
  • React
  • Decap CMS
  • Cloudflare Pages
  • Cloudflare Workers
  • TypeScript

PUBLIC SEARCH

Find a path

Start typing to search the public index.