How the Blog Build Pipeline Works

After writing articles, you don’t want to deploy them manually every time. The goal of automation is simple: push code and forget about it.

Two things: turn Markdown into static files and put them on the internet. Plus, automate the whole build process.

Gulp Tasks

Two Gulp tasks running in series:

gulp.task("default", gulp.series('build', 'generate-service-worker'));

build

gulp.task('build', () => gulp.src('./').pipe(shell(['hugo --buildFuture --minify'])));

One line. Runs Hugo build, --buildFuture allows future-dated content, --minify handles HTML/CSS/JS compression at build time.

Why use gulp-shell instead of calling Hugo directly? Because I need to do post-processing after building. Gulp chains them together nicely.

generate-service-worker

gulp.task('generate-service-worker', () => {
    return workbox.generateSW({
        globDirectory: './public',
        globPatterns: ['**/*.{woff2,woff,js,css,png.jpg}'],
        globIgnores: ['sw.js'],
        swDest: `./public/sw.js`,
        clientsClaim: true,
        skipWaiting: true,
        runtimeCaching: [
            { urlPattern: /.*\.js/,    handler: 'NetworkFirst' },
            { urlPattern: /.*\.css/,   handler: 'StaleWhileRevalidate' },
            { urlPattern: /.*\.(?:png|jpg|jpeg|svg|gif)/,
              handler: 'CacheFirst',
              options: { cacheName: 'images', expiration: { maxEntries: 50 } } },
            { urlPattern: /.*\.html/,  handler: 'NetworkFirst' }
        ]
    })
    .then(() => console.info('Service Worker generated.'))
    .catch(error => console.warn('SW generation failed: ' + error));
});

This task uses Workbox to auto-generate the SW. The details are covered in PWA & Service Worker.

CI/CD

Automated build and deploy via GitHub Actions. Config file .github/workflows/deploy.yml:

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 }}

Triggers on push to master or manual trigger.

npm ci is faster than npm install but requires the lock file to exist and be consistent with package.json. If you forget to commit the lock file, CI will fail. Been there.

CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID are stored in GitHub Secrets.

Performance

Build to deploy takes about two minutes. Build itself is under 10 seconds, most time is spent on dependency installation and deployment.

SW caching strategy directly affects page load performance. First visit depends on NetworkFirst resources, but since static HTML is tiny (5-10KB), network overhead is minimal. Subsequent navigation loads most resources from cache, feels near-native.

The generated sw.js is about 5KB, negligible impact on overall load size.

Article Link:

/en/archive/blog-architecture-build-pipeline-pwa/

# Related Articles