Skip to content

Status pages: a real page instead of plain text before the first publish (and other site states) #117

Closed

Description

@Madraka

Today

A new Cloudhal site answers every page with plain text until its first publish:

503  This site has not been published yet.

It comes from the theme: starters/blog/src/pages/[...path].astro:117, plus the same line in rss.xml.ts, sitemap.xml.ts, llms.txt.ts and [...path]/rss.xml.ts. A visitor, or the owner opening their new domain, sees an unstyled line of English in the browser's default font. That is the first impression of every new install: the Deploy button, npm create cloudhal, try.cloudhal.com.

Other site states have the same gap:

State Today Where
Not published yet plain text 503 theme routes
Hostname with no site Worker (multi-site) no site Worker for this site, 503 packages/cloudflare/src/site-routes.ts
Render failure default runtime error site Worker
Expired or invalid preview link depends on the route renderer preview
A page that does not exist ✅ already themed: the site's /404 page, else the theme's, in the request's locale theme

Proposal

1. Owned by the renderer, not the theme

  • @cloudhal/astro-renderer serves the status pages before Astro, the same way /media/… is served (cloudflare-entry.ts, plus the dev middleware for cloudhal dev).
  • Why the renderer:
  • Theme override (optional, additive): a theme can export statusPage(state, ctx) from src/theme.ts to render its own. Without it, the built-in page is used. The theme API stability rules apply: only add, never break.

2. One component, a closed set of states

type SiteState = "not-published" | "unavailable" | "error" | "preview-expired";
State Status Headers Message (en)
not-published 503 Retry-After: 3600 "Coming soon. This site isn't published yet."
unavailable 503 "This site is temporarily unavailable."
error 500 "Something went wrong" + the cf-ray id
preview-expired 410 "This preview link has expired. Ask for a new one."
  • Every state: cache-control: no-store, x-robots-tag: noindex, no JavaScript.
  • 503 keeps search engines from indexing or dropping the domain before launch; robots.txt already disallows everything before publish.
  • Later (not now): a maintenance state would reuse the same component.
  • Media keeps its short plain responses (404/410/451), since nobody views those as a page.
  • Feeds, sitemap and llms.txt stay plain-text 503: they are read by machines.

3. Design

  • One self-contained HTML document.
    • inline CSS that works under the current CSP (style-src 'self' 'unsafe-inline');
    • system font stack, no web fonts and no requests;
    • light and dark via prefers-color-scheme.
  • Layout: a centered card with the site name (when known), the message, and a small "Built with Cloudhal" footer. The footer can be turned off later.
  • Accessibility: lang and dir set, one <h1>, sufficient contrast. RTL works for ar, fa and he.
  • What it shows:
    • The site name comes from the site node. It is set at init and is not draft content.
    • Brand colours and logo are only used after a first publish (states other than not-published), because before that they are unpublished.
    • No link to Studio or the admin host: the public page must not advertise the admin URL. The owner sees "publish your site" in Studio itself.

4. Language

  • Order of choice:
    1. the URL's locale prefix, if it is one of the site's locales (/tr/…);
    2. the site's defaultLocale;
    3. en.
  • No Accept-Language negotiation. It matches the project's default-English rule, and it avoids Vary: Accept-Language on edge-cached hosts.
  • Strings: a small catalog in the renderer (about 10 strings), for the same 19 languages as Studio. They are drafted with the translate flow and only English is hand-edited (ADR-0034). Themes that override get the state and locale, and bring their own words.

5. Shape of the change (needs a short RFC first: it changes renderer behaviour and adds a theme hook)

  • packages/astro-renderer/src/status-page.ts: statusPage(state, { site?, locale, rayId? }) → Response, pure and Worker-safe.
  • The site Worker and the dev middleware answer not-published when there is no published pointer. error comes from a top-level catch in cloudflare-entry.ts.
  • site-routes.ts uses unavailable.
  • The starter's [...path].astro:117 is left as a fallback for runtimes without the middleware. It is unreachable on Cloudflare and in dev.
  • Tests:
    • every state × status code and headers;
    • the locale order, and RTL;
    • CSP and no external requests;
    • snapshot HTML;
    • the old-theme compat test: frozen themes get the page before their first publish.

Done when

  • A new site shows a styled, translated "Coming soon" page (503, noindex, no-store) on Cloudflare and in cloudhal dev.
  • An unknown multi-site host, a render failure and an expired preview use the same page.
  • A theme can override the page through statusPage in theme.ts.

Next step: a short RFC (.agents/skills/write-rfc), then one PR for the renderer and the starter, then translations.

Activity

  1. Madraka commented on Nov 8, 2026

    @Madraka
    ContributorAuthor

    RFC-0043 is accepted (#118, with the 14 decisions recorded) and rollout PR 1, the status pages served by the renderer before Astro, is open as #162. PR 2 will make cloudhal build --published write the status page as the static output for self-hosting (#143, #150).

  2. Madraka commented on Nov 10, 2026

    @Madraka
    ContributorAuthor

    Done. Status pages shipped in 0.6.4 (#162) and were corrected in 0.6.5 (#171): not-published, unavailable, error, preview-expired and the rest, as translated self-contained pages, plus /__cloudhal/health. RFC-0043 PR 2 (cloudhal build --published for static self-host output) is still to do and goes with #150.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions