Repository navigation
Closed
Labels
enhancementNew feature or requestNew feature or request
Description
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-rendererserves the status pages before Astro, the same way/media/…is served (cloudflare-entry.ts, plus the dev middleware forcloudhal dev).- Why the renderer:
- Before the first publish there is no snapshot, so a theme has no data to render with.
- Themes are copied at
cloudhal initand drift (Template and themes drift behind releases: keep cloudhal-template and frozen theme copies current #111). A renderer page reaches every existing site on its next update, including the frozen v0.3.10–v0.5.0 themes in the compat test, with no theme change.
- Theme override (optional, additive): a theme can export
statusPage(state, ctx)fromsrc/theme.tsto 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
maintenancestate 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.txtstay 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.
- inline CSS that works under the current CSP (
- 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:
langanddirset, 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
initand 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.
- The site name comes from the site node. It is set at
4. Language
- Order of choice:
- the URL's locale prefix, if it is one of the site's locales (
/tr/…); - the site's
defaultLocale; en.
- the URL's locale prefix, if it is one of the site's locales (
- No
Accept-Languagenegotiation. It matches the project's default-English rule, and it avoidsVary: Accept-Languageon 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
translateflow 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-publishedwhen there is no published pointer.errorcomes from a top-level catch incloudflare-entry.ts. site-routes.tsusesunavailable.- The starter's
[...path].astro:117is 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
statusPageintheme.ts.
Next step: a short RFC (.agents/skills/write-rfc), then one PR for the renderer and the starter, then translations.
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request
Activity
docs(rfc): accept RFC-0043 status pages and maintenance mode
Madraka commentedon Nov 8, 2026
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 --publishedwrite the status page as the static output for self-hosting (#143, #150).Madraka commentedon Nov 10, 2026
Done. Status pages shipped in 0.6.4 (#162) and were corrected in 0.6.5 (#171):
not-published,unavailable,error,preview-expiredand the rest, as translated self-contained pages, plus/__cloudhal/health. RFC-0043 PR 2 (cloudhal build --publishedfor static self-host output) is still to do and goes with #150.