DEV Community

LaserEngraverExpert
LaserEngraverExpert

Posted on

I Built a Blazing Fast Website for $0/Month — Here's the Exact Stack (Hugo + Cloudflare Pages)

Page Speed Insights

Page Speed Insights
I was running a WordPress site on shared hosting. $15/month, a plugin that needed updating every other Tuesday, and a Time to First Byte that made me want to apologize to every visitor.

You know the feeling. You install a caching plugin to fix your slow WordPress site. The caching plugin needs a configuration plugin. The configuration plugin conflicts with your SEO plugin. Now you have three plugins doing one job badly and your Core Web Vitals score looks like a participation trophy.

I got fed up and rebuilt everything from scratch. The result is laserengraverexpert.com — a laser engraver review site running on Hugo 0.147.0, hosted on Cloudflare Pages, deploying in ~25 seconds, and costing me exactly $0/month to host.

This article is the full technical breakdown. Stack, config, reasoning, trade-offs, and the one thing I got wrong at first that added two days of debugging. If you're building a content site, affiliate site, documentation, or anything that doesn't need a database — this stack deserves serious consideration.

The Full Stack at a Glance

| Layer | Tool / Cost |
|---|---|
| Static Site Generator | Hugo 0.147.0 — Free |
| Hosting + CDN | Cloudflare Pages free tier — $0/month |
| Analytics | Cloudflare Web Analytics — $0/month |
| Build Config | `wrangler.toml` |
| Images | WebP (converted from source) — Free |
| Domain | Namecheap — ~$10/year |
| Database | None |
| Server runtime | None |
Enter fullscreen mode Exit fullscreen mode

Total infrastructure cost: ~$10/year. That's the domain. Everything else is free.

Why Hugo Over Eleventy, Next.js, or Gatsby

This is the question I spent the most time on before building. Let me give you the honest version, not the 'all tools are great for different use cases' non-answer.

  • Next.js — I considered it. The ecosystem is excellent and I know React. But Next.js is built for apps. Static export works, but you're fighting the framework every time you want pure static output. The build output is heavier than it needs to be for a content site, and Vercel's free tier has bandwidth caps that start to matter if you get traffic.

  • *Gatsby *— It was the right answer in 2019. The GraphQL data layer is powerful but it's overhead I don't need. Build times got notoriously slow on larger sites, and the ecosystem has fragmented. I didn't want to bet a production site on it.

  • Eleventy (11ty) — This was the closest competitor. Eleventy is genuinely excellent. It's JavaScript all the way down, zero opinions, highly composable. If you're already deep in the Node ecosystem and want maximum flexibility, Eleventy is your pick. I came close to using it.

  • Why Hugo won: Speed. Not website speed — build speed. Hugo builds are measured in milliseconds. My current site with all its content builds in under 400ms. With Eleventy I was looking at 3-5 seconds for a comparable site. When you're iterating on layouts and templates, that difference matters a lot during development.

  • Hugo is also a single binary. No node_modules. No package.json. No dependency hell. You install one binary and you're running. On Cloudflare Pages this means the build environment is clean, reproducible, and fast.

  • The trade-off: Go templating. Hugo's template syntax has sharp edges — the pipe syntax, .context scoping, and partial rendering model trip people up. I'm not going to pretend the learning curve doesn't exist. It does. But it pays off.
    ────────────────────────────────────────────────────────────────────────

Cloudflare Pages Setup

Sign up for a Cloudflare account if you don't have one. Connect your GitHub repo. That's the core of it — but there are a few specifics worth knowing.
In the Cloudflare Pages dashboard:
• Click 'Create a project' → 'Connect to Git'
• Select your repo
• Set build command: hugo --minify
• Set build output directory: public
• Add environment variable: HUGO_VERSION = 0.147.0
📌 If you don't pin your Hugo version, Cloudflare uses an old default. My local build was fine, Cloudflare's build was failing because it ran Hugo 0.92 against templates written for 0.147. Two days of confusion. Set the version explicitly.

Free tier gives you 500 builds per month. For a content site with a reasonable publishing cadence you'll never hit that. I've never exceeded 30 in a month.

Every push to main triggers a build and deploy. Every pull request gets a preview deployment with its own URL — great for reviewing changes before merging.
────────────────────────────────────────────────────────────────────────

The wrangler.toml Config — Full Breakdown

This file controls everything about how Cloudflare builds and serves your site.

[build]
  command = "hugo --minify"
  publish = "public"

[build.environment]
  HUGO_VERSION = "0.147.0"

[[headers]]
  for = "/*"
  [headers.values]
    X-Content-Type-Options = "nosniff"
    X-Frame-Options = "DENY"
    Referrer-Policy = "strict-origin-when-cross-origin"
    Permissions-Policy = "camera=(), microphone=(), geolocation=()"
    Strict-Transport-Security = "max-age=31536000; includeSubDomains; preload"
    Content-Security-Policy = "default-src 'self'; script-src 'self' 'unsafe-inline' static.cloudflareinsights.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' cloudflareinsights.com; frame-ancestors 'none';"

[[headers]]
  for = "/css/*"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"

[[headers]]
  for = "/js/*"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"

[[headers]]
  for = "/img/*"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"
Enter fullscreen mode Exit fullscreen mode

────────────────────────────────────────────────────────────────────────

Cache Strategy — Why Immutable Caching Is a Game Changer

max-age=31536000 means browsers cache files for one year. immutable tells the browser not to even bother revalidating — if the file is in cache, use it without a network round trip.

The obvious question: what if you update your CSS? You use content-addressed filenames. Hugo's asset pipeline fingerprints files — adds a hash of the contents to the filename.

{{ $css := resources.Get "css/main.css" | minify | fingerprint }}

When the file changes, the hash changes, the filename changes, and the browser treats it as a new file. A user who visited yesterday loads your page today without downloading a single CSS or JS byte.
────────────────────────────────────────────────────────────────────────

The WebP Image Pipeline

WebP files are 60–80% smaller than JPEG at equivalent visual quality. A 400KB product JPEG becomes a 90KB WebP. Multiply across 30 images and you've saved several megabytes per visit.
cwebp -q 82 source-image.jpg -o converted-image.webp
Quality 82 is the sweet spot for product photography — visually indistinguishable from the source, dramatically smaller. For critical above-the-fold images add loading="eager" and fetchpriority="high". Everything else gets loading="lazy".
────────────────────────────────────────────────────────────────────────

The Build Pipeline

Full flow from git push to live site updating:
• git push origin main — pushes to GitHub
• GitHub webhook fires to Cloudflare Pages (~1 second)
• Cloudflare spins up a build container
• Container installs Hugo 0.147.0 (pinned via env variable)
• Runs hugo --minify (~300–400ms for my site)
• Cloudflare receives the public/ directory output
• Files propagate to Cloudflare's edge network globally
• Live site updated

Total time: approximately 25 seconds end to end.
The JSON output is worth setting up even if you don't use it immediately:
[outputs]
home = ["HTML", "RSS", "JSON"]

This generates an index.json at root — ready for client-side search (Fuse.js, Pagefind) without any future build changes.
────────────────────────────────────────────────────────────────────────

Performance — What to Expect

With this exact stack you get:
• No render-blocking resources
• No JavaScript on most pages (analytics beacon is deferred)
• WebP images with lazy loading
• Immutable cache on all static assets
• HTTP/2 and HTTP/3 via Cloudflare (free, automatic)
• Brotli compression on all responses (free, Cloudflare handles it)

A site built this way should score 95–100 on Lighthouse Performance consistently. TTFB under 200ms globally. FCP under 0.8 seconds on a decent connection.
The only thing that tanks performance on a site like this is unoptimized images. The WebP pipeline is the work you have to do. Everything else the stack handles for you.
────────────────────────────────────────────────────────────────────────

Cost Breakdown

Service Free Limit / Cost
Cloudflare Pages hosting Unlimited bandwidth — $0/month
Cloudflare Pages builds 500 builds/month — $0/month
Cloudflare Web Analytics Unlimited — $0/month
GitHub Public/private repos — $0/month
Domain (Namecheap) ~$10/year

Total: ~$10/year. Cloudflare Pages has no bandwidth cap — unlike Netlify/Vercel free tiers. High-traffic days cost $0.
────────────────────────────────────────────────────────────────────────

The Honest Trade-offs — What This Stack Cannot Do

• No dynamic content — Hugo outputs the same HTML for every visitor.
• No user accounts — Login, saved preferences, personalization require a separate backend.
• No comments — Need Giscus, Disqus, or a serverless function.
• No server-side search — Must use client-side (Fuse.js, Pagefind) or a third party.
• Content updates require a build — No CMS dashboard. Pair with Forestry/CloudCannon for non-technical editors.
────────────────────────────────────────────────────────────────────────

Conclusion

The site is live at laserengraverexpert.com — including the laser engraver comparison guides. Run it through PageSpeed Insights if you want to see what this stack produces in practice.
Cloudflare delivers it from an edge node near your visitor. The browser caches your assets for a year. You pay $10/year for the domain and nothing else.
This isn't the right stack for everything. But for content sites where long-term success depends on fast load times, low overhead, and not paying more as you scale — it's the most honest setup I've found..

Marcus Webb writes about laser engravers and the tools he uses to build around them at laserengraverexpert.com

Top comments (0)