Skip to main content

First Boot — Scaffolding an Astro v6 Project

Turning the Astro and Cloudflare decisions into a running project — from bun create astro to first deploy, and knowing when to stop.

By Graham Wright · · 5 min read · Updated February 20, 2026

The previous post covered the reasoning behind choosing Astro and Cloudflare Workers. This one covers what happened next: turning those decisions into a running project.

The goal was narrow on purpose. Get a dev server running, get the deployment target wired up, and stop. No design, no content system, no styling — just confirm that the toolchain works end to end before building on top of it.

The scaffolding command

Astro has a CLI scaffolding tool, and I used Bun as my package manager from the start:

bun create astro

The interactive wizard walks you through a few choices. I went with:

  • Empty project — no starter template. I wanted to understand every file in the repo, not inherit someone else’s opinions.
  • TypeScript, strict mode — on a personal blog this is less about necessity and more about reinforcing habits I rely on in larger projects. It catches real bugs in component props and content schemas, and it gives AI assistants more to work with. They generate better code when they have types to anchor on.
  • No sample files — just astro.config.mjs, tsconfig.json, package.json, and a single src/pages/index.astro.

The result is a project with about five files, running Astro v5 (stable). Since I’d already decided to build on v6, the next step was upgrading.

Neither bun create astro@latest nor npx create astro -- --ref next installed the v6 beta. Both gave me stable v5. The scaffolder doesn’t have a beta channel. The solution was straightforward: scaffold with stable, then upgrade:

bun add astro@beta

This pulls in the latest v6 beta and updates package.json accordingly. The Astro v6 upgrade guide covers what’s changed. From here on out, everything I build is on the version Cloudflare will be optimizing for. The same bet I outlined in the previous post.

The beta added a two-step process: scaffold stable, then upgrade. Minor, but it cost me about thirty minutes I didn’t expect.

Adding the Cloudflare adapter

The Cloudflare decision from the previous post needed to connect to real config. That meant adding the adapter early, before building anything else:

bun add @astrojs/cloudflare@beta

Since the project is on Astro v6 beta, the adapter needs to match. The stable release targets Astro v5. The Cloudflare adapter docs cover the full configuration reference.

And in astro.config.mjs:

import cloudflare from '@astrojs/cloudflare';

export default defineConfig({
  output: 'static',
  adapter: cloudflare(),
});

Two things worth noting here. First, output: 'static'. Astro defaults to static rendering, and that’s what I want. The adapter handles deploying the static output to Cloudflare Workers. Second, I’m adding the adapter before I have anything to deploy. This is deliberate. I’ve seen too many projects build for weeks against a local dev server and then scramble to sort out deployment late in the process. Wiring up the target environment early, even when there’s nothing meaningful to deploy, surfaces integration issues when they’re easier to fix.

The Wrangler config is similarly minimal. A wrangler.jsonc that names the project, points at the build output, and pins the runtime version:

{
  "name": "gtw-astro",
  "main": "@astrojs/cloudflare/entrypoints/server",
  "compatibility_date": "2025-01-01",
  "compatibility_flags": ["nodejs_compat"],
  "assets": {
    "directory": "./dist",
    "html_handling": "drop-trailing-slash"
  }
}

The compatibility_date locks the Workers runtime behavior to a known version, which matters for reproducibility. And html_handling: "drop-trailing-slash" tells Cloudflare to serve clean URLs (/about instead of /about/), which is a small detail worth getting right early. The production domain and route configuration will come later.

First boot

bun run dev

Astro’s dev server starts, Vite spins up, and there’s a page at localhost:4321 with whatever I put in index.astro. At this point that’s a single <h1> tag. Not much to look at, but it confirms the toolchain works: Bun resolves dependencies, Vite serves the project, Astro renders the page, and TypeScript is checking types.

A working dev server means the foundation is solid. Everything after this builds on something real.

First build

Before deploying anywhere, I wanted to verify the production build locally:

bun run build && bun run preview

astro build generates the static output into dist/, and astro preview serves it locally. Catching build-only issues locally is cheaper than discovering them in production.

First deploy

With the build verified, I added a deploy script to package.json:

"scripts": {
  "dev": "astro dev",
  "build": "astro build",
  "preview": "astro preview",
  "deploy": "astro build && wrangler deploy"
}

Then the first deploy was a single command:

bun run deploy

Wrangler comes along with the Cloudflare adapter, so there’s nothing additional to install. Astro builds the static output and Wrangler pushes the result to Cloudflare Workers. The site goes live at a .workers.dev subdomain. Not a real domain yet, but proof that the full pipeline works from source to production. No GitHub integration needed at this point, just a manual push from my machine to Cloudflare’s edge. I’ll likely experiment with hybrid or server Astro setups in the future, but static is the right starting point.

I’m skipping over the Cloudflare account setup and Wrangler authentication here. Both are straightforward and well-documented.

Even with Astro v6 beta, the total setup was five config files and three commands (scaffold, build, deploy), and the site is live at the edge. The beta adds some version-matching friction, but the core workflow is clean.

Connecting GitHub

With the manual deploy confirmed, the next step was reducing friction for future updates. I connected the GitHub repo to Cloudflare’s deployment integration. Commits to main now trigger automatic deploys.

This isn’t CI/CD in any serious sense. There are no tests, no linting, no checks beyond what astro build does. A broken commit goes live immediately. At some point I’ll want quality gates: type checking, maybe accessibility checks, maybe Lighthouse scores. But fast feedback loops matter more at this stage. I’ll add guardrails later.

What the project looks like at this point

After scaffolding, the project is sparse:

├── astro.config.mjs
├── package.json
├── tsconfig.json
├── wrangler.jsonc
└── src/
    └── pages/
        └── index.astro

No components, no layouts, no styles, no content directory. Just config and a single page. I want to add each layer intentionally, understanding what it does and why it’s there. That’s a luxury of a personal project — at work, scaffolding decisions often get made quickly and lived with for years. Here, I can be deliberate.

What I’m deferring

A few decisions to recap:

  • Static output only — the Cloudflare adapter supports server-side rendering, but starting with output: 'static' keeps things simple. The path to SSR is there if/when I need it later.
  • No real domain yet — the site initially lives at a .workers.dev URL. The deployment pipeline works end to end, which is what mattered at this stage. Domain, DNS, and production routing are a separate set of decisions.
  • No quality gates — commits to main auto-deploy with no checks beyond astro build. Fast feedback loops now, guardrails when there’s something worth guarding.

The next post covers what I did before writing any components or content: setting up the project documentation that shapes every AI-assisted building session.


Tags