Skip to content
Victor BI
Go back

Deploying My Astro Blog to Cloudflare Pages from GitHub

Background

I wanted to host a lightweight personal blog built with Astro. The blog source code was already stored in a GitHub repository, and my goal was simple:

At first, I deployed the site as a Cloudflare Worker. It worked, but the default URL looked like this:

https://victorbi.spearfishingchina.workers.dev/

That was not what I wanted for a simple blog. I wanted a Cloudflare Pages project instead, because the default Pages URL is cleaner:

https://victorbi.pages.dev/

My Project Structure

My Astro project was inside the src directory of the repository.

The important part looked like this:

repo-root/
├── README.md
├── deploy/
│   └── cloudflare/
│       └── website/
│           └── ...
└── src/
    ├── package.json
    ├── pnpm-lock.yaml
    ├── astro.config.ts
    ├── public/
    └── src/
        ├── content/
        ├── pages/
        ├── layouts/
        └── components/

The deploy/cloudflare/website/ directory contained already-built static files, such as:

index.html
404.html
_astro/
pagefind/
rss.xml
sitemap-index.xml

That directory was useful for testing, but it was not the right long-term source for Cloudflare Pages. The better approach is to let Cloudflare build the site directly from the Astro source code.

Why I Changed the Deployment Approach

There are two possible ways to deploy a static Astro blog:

Option 1: Build locally and upload the static files

This means running the build on my own machine and uploading the generated files.

For example:

cd src
pnpm run build

Then I would upload the generated dist folder manually.

This works, but it is not ideal because every update requires manual steps.

Option 2: Let Cloudflare build from GitHub

This is the better long-term setup.

The workflow becomes:

Write blog post
Commit to GitHub
Push to GitHub
Cloudflare Pages builds the Astro site
Cloudflare Pages deploys the generated dist folder

This is cleaner because GitHub stores the source code, and Cloudflare handles the build and deployment automatically.

Clean Up the Repository

The repository should not track generated files or local dependencies.

I added or updated the root .gitignore file:

node_modules/
src/node_modules/
dist/
src/dist/
deploy/cloudflare/website/
.wrangler/
.dev.vars
.env

Then I removed generated files from Git tracking without deleting them locally:

git rm -r --cached src/node_modules || true
git rm -r --cached deploy/cloudflare/website || true
git rm -r --cached src/dist || true
git rm -r --cached dist || true

After that, I committed the cleanup:

git add .gitignore
git commit -m "Clean up generated files for Cloudflare Pages deployment"
git push

Check the Astro Build Script

Inside src/package.json, I confirmed there was a build script.

The important part is:

{
  "scripts": {
    "build": "astro build"
  }
}

For my project, Cloudflare Pages only needs to run the build command. Astro will generate the static site into the dist directory by default.

Test the Build Locally

Before configuring Cloudflare, I tested the build locally.

cd src
pnpm install
pnpm run build

After a successful build, Astro generated:

src/dist/

That dist folder is the final static website.

It contains files such as:

index.html
404.html
_astro/
posts/
tags/
rss.xml
sitemap-index.xml

Finding the Cloudflare Pages Option

In the Cloudflare dashboard, I went to:

Workers & Pages
→ Create application

The confusing part was that the main screen showed Worker options first, such as:

Continue with GitHub
Connect GitLab
Start with Hello World!
Select a template
Upload your static files

Those options create a Worker application.

For Pages, the correct link was at the bottom:

Looking to deploy Pages? Get started

I clicked Get started there.

Create the Cloudflare Pages Project

After entering the Pages setup, I selected:

Import an existing Git repository

Then I connected my GitHub repository.

Cloudflare Pages Build Settings

I used the following Pages configuration:

Project name:
victorbi

Production branch:
main

Framework preset:
Astro

Root directory:
src

Build command:
pnpm run build

Build output directory:
dist

The important detail is this:

Root directory = src
Build output directory = dist

Because my Astro project lives inside the repository’s src folder, Cloudflare must run the build from there.

So Cloudflare sees the project like this:

repo-root/src/package.json
repo-root/src/astro.config.ts
repo-root/src/pnpm-lock.yaml

After the build, Astro outputs the website here:

repo-root/src/dist

From Cloudflare Pages’ point of view, because the root directory is already src, the output directory is simply:

dist

Not:

src/dist

Set the Node Version

I also added a build environment variable:

NODE_VERSION = 22

This keeps the build environment stable and avoids Node version problems with modern Astro versions.

Deploy the Site

After saving the Cloudflare Pages configuration, I triggered the first deployment.

The expected process is:

Cloudflare pulls the GitHub repository
Cloudflare enters the src directory
Cloudflare installs dependencies
Cloudflare runs pnpm run build
Astro generates the dist folder
Cloudflare Pages deploys the dist folder

After deployment, the site is available on a Pages URL like:

https://victorbi.pages.dev/

Worker vs Pages

I originally created a Worker deployment, and it produced a URL like this:

https://victorbi.spearfishingchina.workers.dev/

That happened because Workers use this default URL format:

worker-name.account-subdomain.workers.dev

For a blog, that was not what I wanted.

Cloudflare Workers can host static sites, but for a simple Astro blog, Cloudflare Pages feels more natural. Pages gives a cleaner default domain and a simpler mental model:

GitHub repository
→ Cloudflare Pages build
→ Static website

Final Setup

My final setup is:

Source code:
GitHub

Hosting:
Cloudflare Pages

Framework:
Astro

Package manager:
pnpm

Project root:
src

Build command:
pnpm run build

Output directory:
dist

Node version:
22

Final Workflow

Now the publishing workflow is simple:

cd src
pnpm run build

If the local build works, I write or edit a blog post, then push the change:

git add .
git commit -m "Add new blog post"
git push

Cloudflare Pages automatically builds and deploys the updated blog.

Notes for Future Me

Do not commit these folders:

node_modules/
src/node_modules/
dist/
src/dist/
deploy/cloudflare/website/

Do not use the old local build output folder as the Cloudflare project root.

The correct Cloudflare Pages project root is:

src

The correct Cloudflare Pages build output directory is:

dist

That means Cloudflare builds from source and deploys the generated Astro static site automatically.


Share this post:

Next Post
Welcome to Victor BI