Skip to content

Architecture

Every request to a portal site flows through a single Astro middleware that decides, based on auth state, whether the response is a zero-JavaScript viewer page or a fully hydrated editor shell.

HTTP request
middleware.ts (packages/core/src/middleware.ts)
├─ /api/media/* ──► pass through (public; session resolved but not required)
├─ /edit/* or /api/* ──► authenticated? ──► editor path
│ │ no
│ └──► redirect /edit/login
├─ public viewer route ──► authenticated session? ──► editor viewing viewer route
│ │ no
│ └──► audience cookie valid? ──► viewer path
│ │ no
│ └──► redirect /login
└─ public routes (login, callbacks) ──► pass through

Viewers receive a response with zero JavaScript. React section components are rendered server-side in Astro’s SSR pass — the browser gets plain HTML and CSS. No React runtime is shipped to the client. There are no client:* directives on any component in the viewer layout.

Editors reach /edit, which renders packages/core/src/pages/edit/index.astro. That page mounts the editor shell with client:load:

<EditorShell
headSha={headSha}
draftHeadSha={draftHeadSha}
siteId={siteId}
audiences={audiences}
capabilities={capabilities}
currentUser={currentUser}
client:load
/>

client:load tells Astro to hydrate the component immediately on page load. The result is a fully interactive React application running in the browser — TipTap inline editing, drag-to-reorder, media library, save-to-GitHub — all driven by the same section component tree used for viewer rendering, but wrapped in editor controls.

packages/core/src/middleware.ts runs on every request. The key branching logic:

  • /edit or /edit/* or /api/* — requires a valid session. Missing session redirects to /edit/login (UI) or returns 401 (API). Certain /api/auth/* management routes additionally require the owner role.
  • /api/media/* — publicly reachable (URLs are opaque content hashes, not guessable). Editor sessions receive draft-branch media; viewers receive published media.
  • Viewer routes — if no editor session, checks for a signed audience cookie issued by /api/auth/verify-audience. Invalid or absent cookie redirects to /login.
  • Public routes (/login, /edit/login, auth callbacks) — pass through with no auth check.

The locals.isEditor boolean set by middleware is the signal components use to decide whether to render editor chrome.