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.