I wanted a blog that is simple to write, fast to load, and cost effective to host. Here is what I needed:
- A single site for everything — articles, notes, quotes, brain dumps — all public, all at one domain
- Content spanning topics from technology (Flutter, Golang, microservices) to management to hardware (Arduino)
- A hierarchical navigation that mirrors how I think:
tech → flutter,tech → golang,management, etc. - Markdown as the writing format so I can use any editor (VS Code) or generate content with AI tools like Claude
- Support for code blocks, mermaid diagrams, tags, and categories
- A minimalistic UI that stays out of the way
- A workflow as simple as: write markdown locally, git push, site updates
After evaluating several static site generators — Hugo, Astro, Eleventy, Quartz — I landed on a stack that checks every box:
- Hugo — static site generator that turns markdown into a website, mature and actively maintained
- GitHub — version control and storage for all content
- Cloudflare Workers — free hosting with automatic deployments
The result: I write markdown files in VS Code, push to GitHub, and the site updates itself. No databases, no servers, no CMS logins, no Obsidian sync scripts.
Here is exactly how I set it up.
Why This Stack? #
| Concern | Solution |
|---|---|
| Writing format | Markdown — plain text, portable, works with any editor |
| Site generation | Hugo — fast builds, mature ecosystem, great taxonomy support |
| Version control | GitHub — track every change, rollback anytime |
| Hosting | Cloudflare Workers — free, fast CDN, auto-deploy on push |
| Domain | Cloudflare DNS — already managing my domain here |
No monthly costs. No vendor lock-in on content. If Hugo dies tomorrow, my posts are still plain markdown files.
Step 0: Buy a Domain Name #
You do not strictly need a custom domain — Cloudflare gives you a free *.workers.dev subdomain. But a custom domain looks professional and costs very little.
Here is how the major registrars compare:
| Feature | GoDaddy | Namecheap | Cloudflare |
|---|---|---|---|
.com renewal price |
~$22/year | ~$13/year | ~$10/year (at cost) |
| First year pricing | Low (promo) then jumps | Low (promo) then jumps | Same as renewal — no tricks |
| WHOIS privacy | Paid add-on | Free | Free |
| Upsells at checkout | Aggressive | Moderate | None |
| DNS management | Basic | Good | Excellent (you will use it anyway) |
| Domain transfers | Possible but slow | Easy | Easy |
My recommendation: Cloudflare Registrar. They sell domains at wholesale cost with zero markup — what they pay to the registry is what you pay. No introductory pricing gimmicks, no hidden fees, free WHOIS privacy. And since you will be using Cloudflare for hosting, your domain and hosting are in the same dashboard with DNS pre-configured.
To register a domain on Cloudflare:
- Log in to the Cloudflare Dashboard
- Go to Domain Registration → Register Domains
- Search for your desired domain and complete the purchase
If you already bought your domain elsewhere (GoDaddy, Namecheap, etc.), you can transfer it to Cloudflare later, or just point the nameservers to Cloudflare — both work fine.
Setup #
Before starting, you need:
- Git installed
- Hugo extended edition installed
- A GitHub account
- A Cloudflare account
- A domain pointed to Cloudflare DNS (optional but recommended)
Installing Hugo (Extended Edition) #
Hugo comes in two editions: standard and extended. You need the extended edition — it includes support for SCSS/SASS processing which most themes (including Blowfish) require.
macOS (using Homebrew):
|
|
Homebrew installs the extended edition by default. If you do not have Homebrew, install it first:
|
|
Windows (using Chocolatey):
|
|
Or using Winget:
|
|
Linux (Debian/Ubuntu):
|
|
If your distro ships an older version, install the latest from the GitHub releases. Download the hugo_extended_*_linux-amd64.deb file and install:
|
|
Verify the installation:
|
|
The hugo version output should include the word extended. For example:
|
|
If you see extended in the output, you are good to go.
Step 1: Create the Hugo Site #
|
|
This creates the project skeleton:
|
|
Step 2: Install a Theme #
I chose Blowfish for its clean design, built-in navigation, mermaid diagram support, and taxonomy pages. Install it as a git submodule:
|
|
Using a submodule keeps the theme as a separate concern — your content and the theme code never mix. You can update the theme independently or swap it entirely without touching your posts.
Step 3: Configure the Site #
Blowfish does not work with a single hugo.toml file. It expects a multi-file configuration structure under config/_default/. If you try using a single hugo.toml, the site will build but render an empty page — this is the most common gotcha when setting up Blowfish.
Delete the root hugo.toml that Hugo generated and create the config directory:
|
|
Now create the following configuration files:
config/_default/hugo.toml — core Hugo settings:
|
|
config/_default/languages.en.toml — site title and language settings:
|
|
config/_default/params.toml — Blowfish theme parameters:
|
|
The [homepage] section is critical. Blowfish defaults to a profile layout that shows an author card — if you have not configured author info, the page renders empty. Setting layout = "page" with showRecent = true gives you a straightforward homepage that lists your latest posts.
config/_default/menus.en.toml — navigation menu:
|
|
config/_default/markup.toml — code highlighting and markdown rendering:
|
|
config/_default/module.toml — tells Hugo to load Blowfish:
|
|
The [taxonomies] block in hugo.toml tells Hugo to generate tag and category pages automatically. Every post tagged with hugo gets listed at /tags/hugo/.
Add a Custom Favicon #
Blowfish defaults to generic favicon files. To use your own, create an SVG favicon in static/ and a partial to load it:
static/favicon.svg:
|
|
layouts/partials/favicons.html:
|
|
Blowfish checks for a custom layouts/partials/favicons.html before falling back to its default PNG favicons. This override keeps it simple — one SVG file that scales to any size.
Step 4: Set Up the Content Hierarchy #
Hugo uses your folder structure as the site’s URL structure. First, create a root _index.md — this controls what appears on your homepage. Without it, the homepage title defaults to the site title, which creates a duplicate “Built by Nikhil” heading:
|
|
Now create the tech section for your first post:
|
|
Create an _index.md file to define the section’s listing page:
|
|
As you write more, you add new sections by simply creating folders — content/management/, content/arduino/, content/tech/flutter/, etc. Each folder with an _index.md becomes a navigable section on your site. The hierarchy grows organically with your content — no need to set everything up upfront.
Step 5: Write Your First Post #
Create a new post:
|
|
Open the file and write using standard markdown. Hugo supports everything you would expect:
Code blocks with syntax highlighting:
|
|
Mermaid diagrams — Blowfish uses a shortcode, not the standard markdown fenced code block:
|
|
Note: Replace
/* mermaid */withmermaid— the/* */wrapping above is just Hugo’s escape syntax to display the shortcode as text.
Which renders as:
graph LR
A[Write Markdown] --> B[Git Push]
B --> C[Cloudflare Builds]
C --> D[Site Live]
Frontmatter at the top of every post controls metadata:
|
|
Step 6: Test Locally #
Run the development server:
|
|
The -D flag includes draft posts. Open http://localhost:1313 in your browser. Hugo watches for file changes and reloads the page automatically — you see updates as you type.
Step 7: Push to GitHub #
Create a new repository on GitHub (e.g., builtbynikhil). Then push:
|
|
Your entire site — config files, content, theme reference — lives in this single repository.
Step 8: Deploy on Cloudflare #
Cloudflare now deploys static sites through Workers (not the legacy Pages flow). This uses Workers Builds for CI/CD and Workers Static Assets to serve your site from the edge.
Add a Wrangler Config #
Create a wrangler.jsonc file in your project root — this tells Cloudflare where your static files are:
|
|
The name must match the project name you set in the Cloudflare dashboard. The assets.directory points to Hugo’s default output folder.
Commit and push this file before setting up the deployment.
Create the Worker #
- Log in to the Cloudflare Dashboard
- Go to Workers & Pages → Create → Worker → Import from Git
- Connect your GitHub account and select the
builtbynikhilrepository - Configure the build settings:
| Setting | Value |
|---|---|
| Project name | builtbynikhil |
| Build command | hugo |
| Deploy command | npx wrangler deploy |
| Non-production branch deploy command | npx wrangler versions upload |
| Path | / |
| API token | Auto-generated by Cloudflare |
- Add an environment variable:
HUGO_VERSION=0.159.1(or your installed version — runhugo versionto check). This is important because the default Hugo version on the build image is older - Click Save and Deploy
Cloudflare runs a two-step process on every git push: first it runs hugo to build your site into public/, then it runs npx wrangler deploy to push those static files to the edge. For non-production branches, npx wrangler versions upload creates a preview version without promoting to production.
Step 9: Connect Your Domain #
Since your domain is already on Cloudflare:
- In your Worker project settings, go to Custom domains
- Add
builtbynikhil.com - Cloudflare sets up the DNS record automatically
- SSL is provisioned within minutes
Your site is now live at https://builtbynikhil.com.
Step 10: The Daily Workflow #
Once everything is set up, publishing a new post is three commands:
|
|
That is it. Cloudflare picks up the push, rebuilds the site, and your post is live within a minute.
Step 11: Repository Structure #
Here is how the single repository keeps concerns separated:
|
|
- Content is just markdown in
content/— portable, editor-agnostic - Theme is a git submodule — update or replace without touching content
- Config is split across files in
config/_default/— each file handles one concern
No build scripts, no sync tools, no separate repositories to manage. Note the absence of a root hugo.toml — Blowfish requires the config to live in config/_default/.
Bonus: Using Claude Code to Manage Content #
Claude Code is Anthropic’s CLI tool that can read, write, and manage files in your project. Once you initialize it in your Hugo repository, it understands your site’s structure and can create content that follows your conventions automatically.
Set Up Claude Code #
Run claude in your project root to start a session, then initialize it:
|
|
On first run, Claude Code scans your project and creates a CLAUDE.md file — a project context file that tells it how your site works. Add instructions like this to your CLAUDE.md:
|
|
Generate Content with Claude #
Now you can ask Claude Code to create posts, and it will place them in the right section with correct frontmatter — creating new folders and index files if the section does not exist yet:
|
|
|
|
Claude reads the existing folder structure, follows the conventions in CLAUDE.md, and handles the hierarchy for you. You review the output, then push:
|
|
This turns content creation into a conversation. You focus on what to write about — Claude handles where it goes and how it is structured.
What is Next #
From here, I plan to:
- SEO optimizations — author info, structured data, Google Search Console, Cloudflare performance
- Customize the theme colors and layout to match my style
- Set up archetypes so
hugo newgenerates posts with my preferred frontmatter template - Add an about page and a landing page
- Write more posts
The entire setup took under an hour. The best part: the writing experience is just editing markdown files in VS Code. No context switching, no web interfaces, no databases.
If you are looking for a simple, fast, and free way to publish your writing — this stack is hard to beat.
The full source for this site is on GitHub: nnjoshi14/builtbynikhil. Feel free to use it as a reference.