Insights / Technical notes
Designing an Astro + Cloudflare Website That Can Grow Feature by Feature
How we combined Astro and Cloudflare Pages with an AI contact chat, Sveltia CMS, multilingual blog publishing, service CTA handoff, safe Markdown rendering, and Cloudflare-only comments as one extensible website architecture.

Table of contents
- The Short Version
- Start With Static Strength
- Keep Dynamic Features at Small API Boundaries
- CMS Is an Editing Surface, Not the Runtime Database
- Localization Is Generated Content, Not UI Translation
- Contact Paths Have Different Jobs
- AI Output Is Not Trusted HTML
- Comments Stay Inside Cloudflare
- Searchable Content and Interaction Are Separate
- Start by Goal
- Implementation Order
- Summary
- Supplement: separate Worker environments and build configuration
- Supplement: bound feed retries and isolate failures by source
- October 6, 2026 update: API contracts and attachment verification
Before adding CMS or search to Astro and Cloudflare, distinguish editors, public data and per-user processing. Start with static article rendering and Functions for submissions or external APIs, then check only the connections you need in Cloudflare Pages: Bindings to keep scope manageable.
Update, September 26, 2026: This article records the architecture as it stood in June 2026. Later source code saves CMS edits directly to main through a GitHub App after permission, content, and HEAD checks; localization uses OpenAI Batch and translation PRs; and the contact AI calls a shared Worker through a Service Binding. References below to Copilot translation, PR-based CMS saves, and direct AI API calls describe the earlier implementation. See the updated CMS guide, localization guide, and AI guide for the individual paths.
When you start with Astro and Cloudflare Pages, fast static delivery is usually enough.
As the site grows, you may want browser-based editing, localized pages, AI chat guidance, form handoff from service pages, and comments.
This article is an implementation index for deciding which layer each feature belongs to, which order to add them in, and which detailed guide to read next. The Acecore website is the example, but the pattern applies to other Astro + Cloudflare sites.
The Short Version
The important split is this:
| Layer | Responsibility |
|---|---|
| Astro | Pages, blog, OGP, RSS, sitemap, UI |
| Cloudflare | Pages delivery, Pages Functions, D1, Turnstile |
| GitHub | PR review, CMS diffs, translation diffs, history |
| Sveltia CMS | Japanese source content, authors, tags, images |
| OpenAI API | AI contact chat responses |
| Pagefind | Search indexing for reviewed static HTML |
Static content stays static. Dynamic behavior is added only where a request-time boundary is actually useful.
Start With Static Strength
Most of a company website does not need a server on every request.
Services, company information, case studies, blog posts, author pages, tag pages, RSS, sitemap, and OGP can all be generated at build time.
Astro owns that static surface. Cloudflare Pages delivers it.
Only features such as AI chat and comment posting need runtime APIs. Those go to Cloudflare Pages Functions.
Keep Dynamic Features at Small API Boundaries
The AI contact chat and the comment API share the same pattern:
| Feature | UI | API Boundary | Storage or External API |
|---|---|---|---|
| AI chat | Astro | /api/ai-contact |
OpenAI API |
| Comments | Astro | /api/comments |
Cloudflare D1 |
| Bot checks | UI | Pages Functions | Cloudflare Siteverify |
Astro renders the interface. Secrets, API keys, D1 bindings, Turnstile secrets, hash salts, Origin checks, and rate limits stay server-side.
The site does not become a full application server. It remains static by default.
CMS Is an Editing Surface, Not the Runtime Database
Sveltia CMS was added so content can be edited in a browser, but the CMS is not the runtime database.
It creates Git changes:
- Japanese blog source
- authors
- tags
- Japanese source JSON
- images
Those changes go through GitHub PRs, build checks, and review before reaching production.
That design keeps static-site operations reviewable.
Localization Is Generated Content, Not UI Translation
The multilingual workflow keeps Japanese as the source and generates localized Markdown through PRs.
This creates actual URLs, titles, descriptions, OGP metadata, JSON-LD, RSS entries, sitemap entries, and hreflang links for each locale.
That is different from translating the UI at display time.
Contact Paths Have Different Jobs
The contact area is not one button repeated in several places.
| Visitor state | Path |
|---|---|
| Unsure which service fits | AI chat |
| Already reading a specific service | Service CTA |
| Ready to submit details | Form |
| Wants a lightweight conversation | LINE |
The AI chat helps visitors choose. Service CTAs preserve context. The form records the actual request.
AI Output Is Not Trusted HTML
The AI chat can return Markdown-like text, but that output is not trusted HTML.
The renderer only supports the Markdown needed for the chat, trims href values, checks them against an allowlist, and creates links through DOM APIs. Unsafe links fall back to text.
This is part of the architecture, not a cosmetic detail.
Comments Stay Inside Cloudflare
The blog comments are intentionally not an embedded third-party widget.
Cloudflare Pages Functions handle GET and POST, D1 stores comments, Turnstile protects submissions, and host allowlists/rate limits decide what is accepted.
For a small company blog, that is enough without turning the site into a community platform.
Searchable Content and Interaction Are Separate
Reviewed article content is indexed. Comments are not included in Pagefind.
That separation matters. User submissions, AI chat logs, forms, and admin screens are not the same thing as reviewed public content.
The architecture decides what is part of the public knowledge surface and what remains interaction or operation.
- Reviewed static content Publish reviewed articles as static HTML and include them in the Pagefind site index.
- Visitor submissions Comments use a dynamic API and store; keep form and other visitor input out of static search. Indexing requires moderation and regeneration.
- Admin and environment checks Keep admin surfaces outside public search. Check Preview and production separately; configuration alone is not runtime evidence.
Start by Goal
You do not need to read everything first. Start from the feature you are trying to add.
| Goal | Read first |
|---|---|
| Edit articles and images from the browser | Sveltia CMS Setup Guide |
| Publish multilingual pages that search can index | How to Run a Multilingual Blog with Sveltia CMS |
| Guide visitors with AI chat | Technical Design for Adding an AI Contact Chat to an Astro Site |
| Render safe links in AI answers | Safe Markdown Link Rendering for AI Chat Answers |
| Carry service-page context into the form | Passing Service CTA Context to a Contact Form |
| Add comments without an external comment service | Build Astro Blog Comments with Cloudflare Only |
Implementation Order
For a similar site, the practical order is:
- Build static pages, blog, RSS, sitemap, and OGP with Astro.
- Add Sveltia CMS for the Japanese source content.
- Generate localized pages as static HTML.
- Add AI chat guidance and service CTAs.
- Lock down Markdown links, form prefill, Origin checks, and rate limits.
- Add comments inside Cloudflare only when comments are actually needed.
Summary
Astro + Cloudflare can support much more than a simple company brochure without giving up the strengths of static delivery.
The key is to split responsibilities clearly: Astro builds reviewed public HTML, Cloudflare owns delivery and small API boundaries, Sveltia CMS edits source content, GitHub PRs review changes, OpenAI helps with contact guidance, and comments stay within Cloudflare when that is enough.
Use this page as the entry point, then add only the pieces your site actually needs without weakening the static foundation.
Supplement: separate Worker environments and build configuration
Added September 30, 2026. This generalizes related build-configuration work without service names or internal settings. For multiple Workers in one repository, map each Wrangler configuration to its source root, build and deployment target. Separate files alone do not prove environment isolation.
Check variables, secrets and D1, R2 and Service Binding destinations for production and testing. Worker environment bindings and variables are not inherited automatically; declare them per environment. Missing required configuration should stop processing instead of silently using production resources. Persistent staging and branch or PR Previews are distinct workflows.
Choose one production release path and require pre-release checks. Non-production builds or version uploads are for validation; success alone does not promote them to production. For Git builds, verify the branch, commit, root, selected configuration and environment, and deployment command. A successful Pages release does not establish that a separate Worker was deployed. Check CI, production builds, active versions and bindings, and custom-domain behavior separately. These are adaptation checks, not proof that every service has verified isolation.
See Cloudflare’s Worker environments, multi-Worker Builds setups and build configuration. Treat Pages Functions configuration separately.
Supplement: bound feed retries and isolate failures by source
Added September 30, 2026. Ingesting public feeds such as RSS is a different operation from publishing RSS. Bound request time and retry attempts so that checking a transient failure does not keep a job running indefinitely.
Handle sources independently. A failed fetch should not stop other inputs that can be updated separately. Do not report partial success as complete success: retain per-source outcomes and unresolved failures in the job report. Check retrieval, generated data, production builds and public pages separately. These are general design checks, not proof of every failure condition or future source integration.
October 6, 2026 update: API contracts and attachment verification
The anonymized changes fixed paths where frontend/backend session or limit contract mismatches could turn a response into a 503 even after processing succeeded. Reconcile HTTP results, saved state, and UI display separately, handling client retries so they do not duplicate writes.
An attachment field being displayed differs from verifying real file transfer, storage, and retrieval. UI and API changes alone do not establish the latter. See dashboard access and aggregation for private management boundaries, and webhook state management for current payment-provider state.
Site Architecture
Feature Layers for a Growing Website
Keep the site static by default, then add dynamic behavior only where it belongs.
Deliver
Generate static HTML with Astro and serve it on Cloudflare Pages.
Edit
Edit Japanese source content in Sveltia CMS and review it through GitHub PRs.
Localize
Keep translation work in PRs instead of exposing every locale in the CMS UI.
Guide
Use AI chat and service CTAs to guide visitors toward the right form.
Accept
Use Pages Functions for APIs, with D1 and Turnstile only where needed.
Protect
Control Markdown rendering, Origin checks, rate limits, noindex areas, and Pagefind indexing.
Adding isolated features vs designing layers
Feature by feature
- AI, CMS, comments, and forms each get their own design assumptions
- External scripts and admin tools spread operational responsibility
- Localized URLs, search indexes, and preview environments drift easily
- It becomes harder to explain how the whole website works
Layer by layer
- Astro, Cloudflare, GitHub, and OpenAI API each have a clear role
- Dynamic APIs are kept at Pages Functions, while storage stays in D1 where it fits
- CMS updates, localization, RSS, sitemap, and search share one content model
- The article set becomes a readable index by goal and implementation order
Design checklist for adapting this architecture to another site
- Separate what can be generated statically from what requires an API
- Separate the CMS as the editing entry point, translation as pull requests, and publishing decisions as builds
- Do not pass personal information to the inquiry AI; let it guide visitors using only published information
- Pass context through URL parameters in form journeys, while keeping submitted values in stable categories
- Explicitly define the physical D1 database name and binding for submitted data such as comments
- Treat AI output and user submissions as untrusted HTML and handle them with allowlists
Related pages
- Technical Design for an AI Contact ChatAPI boundaries and response controls for guiding visitors with site information.
- Sveltia CMS Setup GuideAdding CMS editing, GitHub backend, OAuth, and PR-based operations to a static site.
- Running a Multilingual Blog with Sveltia CMSGenerating localized static pages instead of relying on UI-only translation.
- Passing Service CTA Context to a Contact FormCarrying service context from a service page into form category and subject fields.
- Safe Markdown Link Rendering for AI ChatRendering only trusted Markdown links instead of treating AI output as HTML.
- Build Astro Blog Comments with Cloudflare OnlyComments without a third-party comment service, using Pages Functions, D1, and Turnstile.
FAQ
Where should I start?
Start with Astro static pages, blog, RSS, sitemap, and OGP. Then add CMS editing and localization. Add AI chat, service CTAs, and comments only when the contact flow or community workflow needs them.
Should every feature be Cloudflare-only?
No. The AI contact chat uses the OpenAI API. The point is to keep delivery, API boundaries, storage, and bot protection inside a clear Cloudflare-centered architecture, while being deliberate about outside services.
Is this necessary for a small site?
Not all at once. But if a site may later add CMS editing, localization, contact automation, or comments, it helps to decide URLs, storage, preview behavior, and search indexing early.