# Deploy to Cloudflare

> Put the site online on Cloudflare, static or on demand, build on your computer or from GitHub, set the secrets and the domain, and keep the site up to date with Shopify's webhooks.

Status: Ready (Smootify 2.0)  
URL: https://www.smootify.io/docs/astro/start/deploy  
Updated: 2026-10-21

Smootify sites run on Cloudflare, as a Worker. This page puts the site online, adds your domain, and covers how a change in the store reaches the site.

> [!NOTE]
> You need a Cloudflare account and a project with the `@astrojs/cloudflare` adapter, which Smootify Studio's project has: see [Create the project with Smootify Studio](https://www.smootify.io/docs/astro/start/studio-scaffold). Studio's **Deploy** tab does the steps on this page for you, with Claude and your Cloudflare sign-in. The page says what happens, so you can do it by hand.

## 1. Static or on demand

- **Static.** Every page is a file made at build time. Nothing runs when a visitor opens a page, so it is the fastest and the cheapest. A change in the store shows after the next build.
- **On demand.** The product, collection and vendor pages are rendered when asked, and cached with tags. The home and 404 pages are still made ahead. Pages read the live collections: see [Content from Shopify](https://www.smootify.io/docs/astro/start/content).

The only difference in `astro.config.mjs` is the cache provider of the on-demand mode:

```js
import { defineConfig } from "astro/config";
import cloudflare from "@astrojs/cloudflare";
import { cacheCloudflare } from "@astrojs/cloudflare/cache";

export default defineConfig({
  output: "static",
  adapter: cloudflare(),
  cache: { provider: cacheCloudflare() },
});
```

## 2. Put the site online

Choose how the site is built:

- **Build on your computer and upload.** Run `pnpm build`, then upload the result with `wrangler`. It saves Cloudflare build minutes.
- **Cloudflare builds from GitHub.** Connect the project’s repository to the Worker in Cloudflare (Workers Builds). Each push to the branch starts a build. The project has `wrangler` as a dependency, because the build runs it from a clean install.

The first deploy gives the site an address on `*.workers.dev`. It works on the Free plan.

To sign in to Cloudflare, run `pnpm exec wrangler login` (`npx wrangler login` with npm).

## 3. Add your domain

1. In Cloudflare, add your domain to the Worker.
2. In the Smootify dashboard, set the same domain as the site’s domain, and set `site` in `astro.config.mjs` to it.
3. Make sure the site has the plan the domain needs: a registered domain needs Lite, and server add-ons need Server. Free works only on `localhost` and `*.workers.dev`.

## 4. Keep the site up to date

When a product, a collection or a page changes in Shopify, the site can follow without you. Shopify calls your site directly at `/_smootify/webhook/<token>/`, and the Smootify server only registers the webhooks. The route then does one of two things:

- **On demand:** it invalidates the cache of the pages that the change touches, by tag. A product change refreshes that product’s page and the pages that list products.
- **Static:** it asks Cloudflare to build the site again, through a deploy hook. This needs Cloudflare to build from GitHub, because the deploy hook belongs to Workers Builds.

Events are merged: the events of one window, 60 seconds by default, become one build or one purge, so a bulk edit does not start a build for each product.

On a static site built from GitHub, a change in Shopify is online in about two minutes, with no step on your side: the webhook arrives at once, the 60 second window closes, Cloudflare builds the site and publishes the new version.

Turn the route on in the config:

```js
smootify({ siteId, webhook: true }); // or webhook: { debounce: 30 } for a 30 second window
```

It needs these pieces. Studio’s Deploy tab sets all of them:

- a worker entry that hands requests to Astro and exports the debouncer, and its binding in `wrangler.jsonc`:

```ts
// src/worker.ts
import { handle } from "@astrojs/cloudflare/handler";
import { withSmootifyWebhook } from "@smootify/astro/cloudflare";
export { SmootifyWebhookDebouncer } from "@smootify/astro/cloudflare";
export default { fetch: withSmootifyWebhook(handle) };
```

```jsonc
// wrangler.jsonc
{
  "main": "./src/worker.ts",
  "durable_objects": { "bindings": [{ "name": "SMOOTIFY_WEBHOOK_DEBOUNCER", "class_name": "SmootifyWebhookDebouncer" }] },
  "migrations": [{ "tag": "smootify-v1", "new_sqlite_classes": ["SmootifyWebhookDebouncer"] }]
}
```

- `"workers_dev": true` in `wrangler.jsonc` when it also sets a custom domain;
- `wrangler` among the devDependencies, which Cloudflare’s deploy step uses;
- two secrets on the Worker:
   - `SMOOTIFY_WEBHOOK_TOKEN` is the token Smootify gives when it registers the webhooks. The token in the address must equal it. A wrong token gets a plain 404.
   - `SMOOTIFY_DEPLOY_HOOK` is the deploy hook address from Cloudflare (**Settings, Builds, Deploy Hooks**). Static sites only.
- the tags on your pages, for an on-demand site. A live entry’s `cacheHint` has them. A page that lists products carries `productListCacheTags()`.

Put a secret on the Worker with `pnpm exec wrangler secret put <NAME>`, or in `.dev.vars` for a local preview. Never commit a secret.

The debouncer is a SQLite Durable Object, available on Cloudflare’s Free plan. Without its binding the route works, but acts on every event, and `smootify()` warns at build time.

Invalidating the cache makes Astro render the page again, but a build collection still holds the data of the last build. For fresh data on an on-demand site, read the live collections.

On a static site every change in the store costs a Cloudflare build, counted in your build minutes. To build only on your own computer, use on-demand pages or update the site by hand.

Studio registers only the webhooks the project needs: products and collections always, blogs, articles, pages and metaobject types only when a loader reads them.

## 5. Forms and email

A form that Shopify and Smootify do not take, such as a contact form, has two ways:

1. **Smootify’s metaobject creator**, when the data belongs in Shopify. No backend. It is a server add-on, so it needs the Server plan.
2. **An API route on the Worker** that sends the email with Cloudflare Email Service. This needs Cloudflare’s Workers Paid plan, about 5 USD a month, and a sender address on a verified domain. Add a `send_email` binding to the wrangler config and read it from `cloudflare:workers`. Protect the route with a honeypot field or Cloudflare Turnstile.

The success and error messages of the form are elements with `form-done` and `form-fail`: see [Write components](https://www.smootify.io/docs/astro/build/components).

## Before you publish

> [!STUCK]
> - **Cloudflare cannot install `@smootify/astro`.** The dependency points to a path or a file on your computer instead of the version on npm. Set it to the npm version, such as `^0.1.0`, and install again with the project's package manager, so the lockfile follows.
> - **The deploy step fails.** `wrangler` is not in the project. Add it as a dev dependency with the project's package manager.
> - **The webhook route answers 404.** The token in the address is not the one in `SMOOTIFY_WEBHOOK_TOKEN`, or the secret is not on the Worker.
> - **A static site does not rebuild.** There is no `SMOOTIFY_DEPLOY_HOOK`, or the site is not built from GitHub. The route answers 200 with `ok: false` and does nothing.
> - **An on-demand page shows old data.** It reads a build collection. Use `getLiveEntry` and set `cacheHint`.
> - **Pages look different after a deploy.** If something minifies the HTML after the build, keep the comments that begin `<!--smootify`. On Cloudflare, turn off Auto Minify for HTML.
> - **Smootify does not load full features on your domain.** The domain is not set in the Smootify dashboard, or the plan is Free: a registered domain needs Lite.
