Deploying This Blog: From Push to Live

Static files need a place to live, accessible from anywhere in the world.

Platform: Cloudflare Pages

Cloudflare Pages over other platforms, three reasons:

  1. Global CDN: edge nodes worldwide, visitors load from nearest location
  2. Zero-config HTTPS: automatic SSL certificate issuance and renewal
  3. Free tier: enough bandwidth and build minutes for a personal blog

Setup is simple: create a project in Cloudflare Pages dashboard, link your GitHub repo. Build commands and output directory are controlled in GitHub Actions, no need to configure in the dashboard.

One gotcha: Cloudflare Pages assigns a default domain <project>.pages.dev. The project name can’t be changed after creation. If you want to rename it, you have to delete and rebuild the project. I picked a random name at first and got stuck with it.

CI/CD Flow

Full deployment managed by GitHub Actions:

name: Deploy to Cloudflare Pages

on:
  push:
    branches: [master]
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: peaceiris/actions-hugo@v3
        with:
          hugo-version: "0.164.0"
          extended: true
      - uses: actions/setup-node@v4
        with:
          node-version: 24
          cache: npm
      - name: Install dependencies
        run: npm ci
      - name: Patch theme for Hugo compatibility
        run: bash scripts/patch-theme.sh
      - name: Build
        run: npm run build
      - name: Deploy to Cloudflare Pages
        run: npx wrangler pages deploy public --project-name=time-friend
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

Triggered by push to master or manual dispatch.

The theme patch step handles compatibility between the old theme and newer Hugo:

#!/bin/bash
find themes/hugo-brewm/layouts -type f -exec perl -i -pe'
s/site\.Language\.Locale/site.LanguageCode/g;
s/\.Language\.Locale/\.Language\.LanguageCode/g;
s/site\.Language\.Label/site.Language.LanguageName/g;
s/\.Language\.Label/\.Language\.LanguageName/g;
' {} +

Replaces deprecated .Locale and .Label with .LanguageCode and .LanguageName. Without this, the old theme throws errors on new Hugo.

Environment Variables

Cloudflare API Token and Account ID stored in GitHub Secrets:

CLOUDFLARE_API_TOKEN: API token with Pages deploy permission
CLOUDFLARE_ACCOUNT_ID: Cloudflare account ID

Configured in GitHub repo Settings > Secrets and variables > Actions. Auto-injected during CI runtime, never appears in logs.

First time setting this up, run a manual workflow to verify. Don’t ask how I know — spent an afternoon debugging a wrong token permission.

Domain

Domain time-friend.com DNS is managed by Cloudflare:

  • A record pointing to Cloudflare Pages load balancing IP
  • CNAME for www pointing to time-friend.pages.dev

Cloudflare Pages handles domain mapping automatically. HTTPS certificate auto-issued and renewed.

Redirects

static/_redirects handles URL redirection:

/archive/*  /zh/archive/:splat  301

Old URL structure was /archive/xxx. After switching to bilingual, Chinese articles moved to /zh/archive/xxx. The redirect ensures old external links and search engine indexes still work.

Performance

Cloudflare’s global CDN is the key to performance. Static files cached at edge nodes, users fetch from nearest location, latency significantly reduced.

Caching is controlled client-side via Workbox. CDN-level caching doesn’t need extra config, Cloudflare Pages sets appropriate cache headers automatically. HTML files have short cache duration for content updates. Static assets (JS, CSS, images) have longer cache times.

Deploy to live takes about two minutes. Build is ~10 seconds, the rest is dependency installation and wrangler deployment.

Article Link:

/en/archive/blog-architecture-deployment/

# Related Articles