From Grav to Hugo
Rebuilding this blog as a static site on Cloudflare
On this page
For years this blog ran on Grav , a lovely flat-file CMS. No database, just Markdown files, which I always liked. But it still needs PHP hosting, updates, and the occasional plugin fix, and I found myself spending more time maintaining the site than writing on it. So I rebuilt it as a static site with Hugo . Here is the why and the how.
Why move at all
- Speed. Static HTML served from a CDN is about as fast as the web gets.
- Cost. Static hosting is effectively free.
- Security. No PHP, no database, nothing to patch or get hacked.
- Simplicity. Deploying is a
git push, nothing else. - Portability. It is just Markdown and a handful of templates. Easy to move again if I ever want to.
Why Hugo specifically? It is a single Go binary, the builds are ridiculously fast, and multilingual is first-class, which matters because this blog is in English and Spanish. It also has render hooks and an asset pipeline, which made the migration much cleaner.
Moving the content
Grav stores each page as a Markdown file with YAML front matter under user/pages. Hugo uses content/ with
page bundles
, so the mapping was mostly mechanical:
- Each Grav page became a bundle (
index.en.md/index.es.md), which is also how Hugo links translations together. - Grav’s
published: falsebecame Hugo’sdraft: true. - Grav’s
taxonomy: category / tagbecamecategoriesandtags. - Page media moved into the bundle and is referenced from the
imagesfront matter.
Search without a backend
Grav did search server-side with a plugin. A static site has no backend, so I switched to Pagefind : it builds a search index at deploy time and runs entirely in the browser. It powers the inline search right here on the blog, with zero server involved.
Hosting on Cloudflare
The site moved from a PHP host to Cloudflare
as a static-assets Worker. The whole
deploy pipeline is now a git push: a build runs Hugo and Pagefind, then uploads the public/ folder to the edge.
_redirectshandles thewww→ apex redirect._headerssets long cache lifetimes for fonts, images and hashed assets.- The custom domain and SSL are automatic.
A few gotchas worth knowing
- Translations link automatically when two files share a bundle (
index.en.md/index.es.md), which also feeds thehreflangtags. - The 404 page: with
defaultContentLanguageInSubdirHugo only emits/en/404.htmland/es/404.html, so the build copies one to the site root for the host to serve on unknown URLs. - Code highlighting uses Chroma with class-based styles, which let me make it theme-aware for dark mode.
A cleaner look, and a voice
While I was at it, I took the opportunity to polish the design into something cleaner and more minimalist, and to lean into a proper light/dark theme.
I also added a little Listen to post button (bottom-right on any article). It reads the post aloud using your browser’s built-in speech, and it is smart enough to skip code blocks, tables and images, swapping them for a quick “there’s a block of code here, take a look at the written version” so you are not subjected to someone spelling out XML. I will be honest: the voice is robotic and a bit awful. But if you struggle to read, or you are just feeling lazy today, it should come in handy. :)
Was it worth it
Absolutely. The site is faster (near-perfect Lighthouse scores), hosting is free, there is nothing to patch, and publishing is just a commit.
If you are running a small PHP-based site mostly to serve content, a static generator like Hugo is well worth a look.
Hope this helps.
If you have any question or made a similar move, feel free to share in the comments below.