Skip to content
Victor BI
Go back

How I Structured and Deployed This Blog

Why I Wrote This Down

This repository is not only a place to keep my blog source code. It is also a small record of how I want the publishing workflow to work: where the source lives, what Cloudflare builds, and which files should never become part of the source of truth.

The goal is simple:

The Repository Shape

The project lives inside the repository’s src directory, not directly at the repository root.

The important structure looks like this:

repo-root/
├── README.md
├── .gitignore
└── src/
    ├── package.json
    ├── pnpm-lock.yaml
    ├── astro.config.ts
    ├── astro-paper.config.ts
    ├── public/
    └── src/
        ├── content/
        │   ├── pages/
        │   └── posts/
        │       ├── en/
        │       │   └── blog.md
        │       └── zh/
        │           └── blog.md
        ├── pages/
        ├── layouts/
        └── components/

That nested src/src path looks a little strange at first, but it has a clear reason.

The first src is the application root from Cloudflare’s point of view. It contains package.json, astro.config.ts, and the lockfile. The second src is the normal site source directory where pages, layouts, components, and content live.

So when I deploy this site, Cloudflare should enter:

repo-root/src

Then the build creates the static website in:

repo-root/src/dist

Because Cloudflare’s project root is already src, the build output directory in Cloudflare Pages should be:

dist

Not:

src/dist

That distinction is small, but it is the kind of detail that can break a deployment.

Source Code vs Build Output

I want Git to track the source of the website, not the generated website.

The source includes Markdown posts, pages, layouts, components, configuration files, and the lockfile. The generated output is different: it is the dist folder created after a build.

That means these directories should stay out of Git:

node_modules/
src/node_modules/
dist/
src/dist/
.wrangler/

The root .gitignore also keeps older generated deployment folders out of the repository, such as:

deploy/cloudflare/website/

This keeps the repository clean. If I want to rebuild the site, I should build from source again instead of treating old generated files as the source of truth.

Local Development

For local development, I work from the application directory:

cd src
pnpm install
pnpm dev

The dev server runs at:

http://localhost:4321/

This is the feedback loop for writing posts, changing layout, and checking the site in a browser.

Local Build

Before deploying, I can run a production build locally:

cd src
pnpm build

The build generates:

src/dist/

That folder is the release-ready static website. It contains the HTML, assets, RSS feed, sitemap, search index, and other generated files that Cloudflare Pages can serve.

Why Cloudflare Pages

At first, I looked at the problem as “how do I get static files onto Cloudflare?” That can lead to a Worker-based approach, and Workers can serve static assets.

But for this blog, Cloudflare Pages is a better fit.

The mental model is simpler:

GitHub repository
Cloudflare Pages build
Generated static website

The site is a static blog. I do not need a custom Worker application to explain that. Pages gives me the cleaner deployment model for this kind of project.

Two Deployment Paths

I keep two deployment paths for this project. They are useful in different situations.

Option 1: Build Locally and Deploy with Wrangler

This path is useful when I want to build the site on my machine and manually upload the generated dist folder.

The command flow is:

cd src
pnpm install
pnpm build
npx wrangler login
npx wrangler pages deploy dist --project-name=victorbi-preview --branch=main

In this workflow, dist is correct because the command is executed inside the src application directory.

The --branch value selects the Cloudflare Pages deployment context. It does not have to mean the name of my local Git branch. For this repository, the manual upload path targets the victorbi-preview Pages project.

Option 2: Let Cloudflare Build from GitHub

This is the cleaner long-term workflow.

The publishing flow becomes:

Write or edit a post
Commit the source code
Push to GitHub
Cloudflare Pages builds the static site
Cloudflare Pages deploys dist

For this repository, the Cloudflare Pages settings should be:

Root directory:
src

Build command:
pnpm build

Build output directory:
dist

Node.js version:
22.12.0 or newer

Environment variable:
NODE_VERSION=22.12.0

The Node version matters because the project declares this engine requirement in src/package.json:

{
  "engines": {
    "node": ">=22.12.0"
  }
}

If Cloudflare builds with an older Node version, the build may fail even though it works locally.

The Domain Detail

The site configuration currently uses:

https://victorbi.pages.dev/

That value lives in:

src/astro-paper.config.ts

The manual deployment target is named:

victorbi-preview

That preview project publishes to:

https://victorbi-preview.pages.dev/

The important rule is: the canonical site.url should match the domain I want search engines, RSS, sitemap, and generated metadata to use. If the production Cloudflare Pages domain changes, I should update src/astro-paper.config.ts.

How I Think About Writing Posts

This blog is bilingual. English posts live in:

src/src/content/posts/en/

Chinese posts live in:

src/src/content/posts/zh/

The frontmatter connects both versions with the same translationKey:

translationKey: "deploy-blog"

Each post also declares its language:

lang: "en"

or:

lang: "zh"

This lets the site treat the English and Chinese articles as separate Markdown files while still understanding that they are translations of the same post.

My Publishing Checklist

When I make a content change, the checklist is intentionally small:

cd src
pnpm build

If the build succeeds, I commit and push:

git add .
git commit -m "Update blog post"
git push

After that, Cloudflare Pages can build and deploy from GitHub. If I need a manual deployment instead, I can use the Wrangler flow above.

References

These are the Cloudflare documentation pages I care about for this setup:

For this project, those references are supporting material. The practical source of truth is still the repository itself: src/package.json for scripts and Node requirements, src/astro-paper.config.ts for site metadata, and the Cloudflare Pages project settings for deployment behavior.


Share this post:

Previous Post
The Age of the Programmer Is Quietly Changing
Next Post
Welcome to Victor BI