How to host a SaaS clone on Cloudflare

How to host a SaaS clone on Cloudflare with the AHA stack. Astro pages, D1 rows, R2 files, and a Worker cron. This catalog already ships that way.

  • getting-started
  • vibe-coding
  • hosting

How to host a SaaS clone on Cloudflare is the AHA path. Astro pages and D1 for rows. R2 only if you store a file. A Worker when something has to run later. This catalog already ships on Cloudflare, so that is the host I point the agent at.

Last time I wrote about vibe coding your own Ghost. That was a teardown. This one is a regular backlog topic. The stack post is which tab you copy. This post is where that AHA tab lives.

Every product page already names the pieces. Typeform says Astro, HTMX, D1. Buffer says Astro, Workers, R2. You do not need a second host tour. You need those names in wrangler.jsonc.

How to host a SaaS clone on Cloudflare

Open the card for the app you pay for. Copy the AHA prompt. The technical paragraph already prefers Cloudflare D1 for tables, R2 for objects, Workers for endpoints and scheduled work, and Durable Objects only for a coordinated room.

That paragraph is the host plan. Pin the first estimate next to it, the same way the prompt post does. Hosting the marketplace is still the third row.

const typeform = {
  product: 'Typeform',
  host: 'goodform.flaviocopes.com',
  pages: 'Astro forms',
  rows: 'D1 submissions',
  files: 'none',
  cron: 'none',
}

Typeform is $29–$99 a month. $348–$1,188 a year. Two hours for one question at a time and a table of answers. The table is D1. There is no file and no later job, so you skip R2 and cron.

Cloudflare’s Astro guide is the public path for a live app. A static export cannot use bindings. A form that writes a row needs the adapter.

BROWSER goodform.flaviocopes.com ASTRO ON A WORKER Pages, forms, HTMX fragments Bindings only after the adapter D1 Submissions Incidents, clicks R2 PDFs, images Signed downloads CRON, IF THE CARD ASKS Buffer publish, UptimeRobot ping scheduled() on UTC Typeform never reaches this box
The browser hits Astro. Rows go to D1. Files go to R2. Cron only if the card names later work.

AHA stack on Cloudflare

Astro renders the pages. HTMX posts the form and swaps the fragment. Alpine stays on local state, like a card you drag on Trello. Persistence is D1, which is SQLite you query from a Worker. Cloudflare’s D1 guide creates the database with wrangler d1 create and puts a binding in the Wrangler file.

I would name that binding DB. Then the agent has one place to prepare a statement.

{
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "goodform",
      "database_id": "replace-after-wrangler-d1-create"
    }
  ]
}

npx wrangler d1 create goodform prints the id. Paste it. Run the migration against local first, then remote when you publish.

Statuspage is the same shape with different tables. $29–$399 a month. $348–$4,788 a year. Three hours for a public page, component states, and incident posts. Components and incidents are D1 rows. The Statuspage walkthrough is that board.

Bitly is Workers, D1, Astro. $35–$300 a month. 90 minutes for a short URL, a redirect, and a click count. The Worker does the 302. D1 stores the slug and the count. The link shortener post is that loop.

Cloudflare D1 for a SaaS clone

D1 is the default table. If the first estimate is a form, a board, a queue of drafts, or a status page, start there.

The AHA prompt already lists entities. Typeform wants Form, Question, Submission, Answer. Statuspage wants Component, Incident, IncidentUpdate. Copy those names into the first migration. Do not invent a second schema for a marketplace.

Prepared statements with bound values are the guardrail. Typeform wants server-side validation, a rate limit, and a honeypot. Those run in the Worker before env.DB.prepare writes the row.

I would keep one database per clone. Mixing Goodform submissions with Upboard incidents in the same file looks tidy on day one, and it is a mess the first time you restore.

Cloudflare R2 for files

R2 shows up when the product stores a file you cannot stuff in a row. DocSend is Astro, R2, D1. $65–$180 a month. $780–$2,160 a year. Four hours for a PDF, a gated link, and a view count. The PDF is the object. The link and the view are rows.

Cloudflare’s R2 Workers API creates a bucket and binds it. The DocSend guardrail is a signed download, not a public object URL. Put the file through the Worker. Do not paste an r2.dev link into the email.

Buffer uses R2 for the one image on a queued post. The capability line is Astro, Workers, R2. $18–$36 a month. $216–$432 a year. Eight hours for a post queue, one network, scheduled publish. The Buffer teardown is why the other networks wait.

Dropbox Transfer is the same object-plus-expiry job. $20–$32 a month. Three hours for an upload and an expiring download link. The card already says signed URLs and automatic delete.

Workers cron for scheduled publish

Buffer still has to publish later, so a drafts page is not enough.

Cron Triggers map a cron expression to a scheduled() handler. They run on UTC. Put the expression in Wrangler. Cloudflare says changes can take several minutes to spread, so do not debug a missing send by redeploying every thirty seconds.

export default {
  async scheduled(controller, env, ctx) {
    await publishDuePosts(env.DB, env.MEDIA)
  },
}

publishDuePosts reads queued rows whose time has passed, posts once, and writes published or failed. The Buffer guardrail is encrypted tokens and an idempotent job, because a retry that posts twice will publish the same tweet again.

UptimeRobot is the other cron card. $24–$69 a month. Six hours for HTTP checks and email alerts. The integration line is Cloudflare Cron Triggers. Ping https://flaviocopes.com/health, store the result, mail Ada when two checks fail. The third row is global probe regions. Skip it.

You can fire the handler locally. Wrangler exposes /cdn-cgi/local/scheduled during wrangler dev. Hit that before you wait for a real minute on the network.

TYPEFORM Astro, HTMX, D1 A form and a submissions table No R2, no cron BUFFER Astro, Workers, R2 Queue, one image, scheduled publish Cron is the product TINYPNG PRO Astro, Alpine, browser API Compress hero-home.jpg in the tab Static host is enough
Read the capability line. Typeform needs D1. Buffer needs R2 and cron. TinyPNG never uploads.

When the clone stays static

TinyPNG Pro is Astro, Alpine, and the browser. $25–$39 a month. $300–$468 a year. 60 minutes. The originals never leave the device. The TinyPNG walkthrough is that path.

A static host is enough there. This catalog is the same kind of site. astro.config.mjs has no Cloudflare adapter. npm run deploy builds dist/ and runs wrangler pages deploy. That is fine for pages that do not write.

If the agent adds a D1 binding to a TinyPNG v1, stop it. The non-goal says no accounts, no cloud storage, no image CDN. A Worker that accepts hero-home.jpg is the third row.

Durable Objects wait for a room. The AHA paragraph says use them only for coordinated real-time state, which is Slack or a workshop board, not a status page. Workflows and Queues wait until the card names durable background jobs, which is Zapier, not Typeform.

The scope post is which row you pin. Hosting follows that row. A 2-hour form does not need a queue.

How this site is hosted

I ship this catalog as static Astro on Cloudflare Pages. The Wrangler file sets pages_build_output_dir to ./dist. There is no D1 on this repo because the posts are markdown in git.

A clone is a different app. It needs the adapter, a binding, and a domain you own. I would put Typeform on goodform.flaviocopes.com and Statuspage on status.flaviocopes.com. Keep this catalog on its own project.

Copy the AHA tab, not a mix of two hosts. If you already run Laravel on a VPS, the stack post says take that tab. This post is for the tab that names D1.

Paste four extra lines under the copied prompt.

Build only the first estimate: a one-question form and a response table.
Host it on Cloudflare with the AHA adapters from the Typeform page.
Use D1 for submissions. Do not add R2, cron, or Durable Objects.
Stop when I can publish one form on a URL I own and download a CSV.

Those lines keep the agent off Pages Functions folklore and off a second database. The first three already lived in the prompt post. The host line is the new one.

Start now

Go to the audit. Open the app you already pay for.

Copy the AHA prompt. Read the capability line. Create only the bindings that line names.

Ship that slice on a hostname you own. Use it as the real tool. Cancel when the paid tab has been closed for a week.