Contents

Build a Modern Hugo Website with GitHub, Cloudflare, and Resend

Contents

1. Introduction

:::info This guide shows how to build and deploy a modern static website using a fully open-source and serverless stack. :::

We will use the following tools:

  • Hugo โ€“ fast and flexible static site generator
  • GitHub Pages โ€“ free web hosting with Git-based deployment
  • Cloudflare โ€“ DNS management, CDN, and HTTPS
  • Resend โ€“ transactional email API for contact forms

โœ… Who is this for?

This setup is ideal for:

  • Community and non-profit projects
  • Personal websites and portfolios
  • Lightweight, multilingual static sites

๐Ÿ’ก Why this stack?

  • Free and easy to maintain
  • Fast performance with global CDN
  • No backend or database needed
  • Clean separation between content and infrastructure

๐Ÿ“ฆ What will you learn?

This guide covers:

  1. Setting up a Hugo site
  2. Hosting it on GitHub Pages
  3. Securing it with Cloudflare (DNS + HTTPS)
  4. Adding a contact form with Resend via Cloudflare Worker

Letโ€™s get started.


2. Setup

๐Ÿ› ๏ธ Requirements

Make sure the following tools are installed:


๐Ÿงฑ Create a new Hugo site

1
2
hugo new site mysite
cd mysite

๐ŸŽจ Install Hugo Blox theme

Add the Hugo Blox theme as a Git submodule:

1
2
git init
git submodule add https://github.com/HugoBlox/hugo-blox-builder.git themes/hugo-blox-builder

Copy the example site content:

1
cp -r themes/hugo-blox-builder/exampleSite/* .

Install dependencies (optional):

1
npm install

โš™๏ธ Basic configuration

Edit the following files:

  • config/_default/config.yaml โ€“ site language, base URL
  • config/_default/params.yaml โ€“ branding, theme settings
  • config/_default/menus.yaml โ€“ navigation menu
  • assets/media/ โ€“ upload your logo and favicon

Example baseURL (for GitHub Pages):

1
baseURL: "https://yourusername.github.io/mysite/"

๐Ÿš€ Run local server

1
hugo server

Visit http://localhost:1313 to preview your site.


:::tip Use GitHub Desktop or CLI to version control all changes. :::


3. Content Management

Managing content in Hugo is simple and efficient. All pages and posts are written in Markdown and organized in folders under the content/ directory.


๐Ÿ—‚๏ธ Content structure

Each language has its own folder:

1
2
3
4
5
6
7
8
content/
โ”œโ”€โ”€ en/
โ”‚   โ”œโ”€โ”€ _index.md
โ”‚   โ””โ”€โ”€ about.md
โ”œโ”€โ”€ de/
โ”‚   โ””โ”€โ”€ about.md
โ””โ”€โ”€ fr/
    โ””โ”€โ”€ about.md

Each Markdown file represents a page and includes a front matter block at the top.

Example about.md:

1
2
3
4
5
6
---
title: "About Us"
date: 2025-08-01
type: page
layout: page
---

โœ๏ธ Creating a new page

Use the hugo new command:

1
hugo new en/about.md

This will create a file in content/en/about.md with a pre-filled front matter.


๐Ÿ“„ Page types

There are two common types of content:

  • Page (type: page) โ€“ standalone pages like โ€œAboutโ€ or โ€œContactโ€
  • Post (type: post) โ€“ blog/news entries shown in lists

Use different layouts if needed: page, post, or custom layouts.


๐Ÿงญ Navigation menu

Define menus in config/_default/menus.yaml:

1
2
3
4
main:
  - name: About
    url: /about/
    weight: 10

To add multilingual labels, use Hugoโ€™s built-in i18n system:

1
2
3
4
i18n/
โ”œโ”€โ”€ en.yaml
โ”œโ”€โ”€ de.yaml
โ””โ”€โ”€ fr.yaml

Example en.yaml:

1
2
- id: about
  translation: "About Us"

๐ŸŒ Multilingual setup

In config/_default/config.yaml, define the site languages:

1
2
3
4
5
6
7
8
defaultContentLanguage: en
languages:
  en:
    languageName: English
    weight: 1
  de:
    languageName: Deutsch
    weight: 2

Use relLangURL in templates to link between languages.


๐Ÿ‘๏ธ Preview your content

Run the local server:

1
hugo server

Check the site in your browser at http://localhost:1313.

Use hugo server --navigateToChanged to auto-scroll to the last edited page.


:::tip Use clear folder structure and consistent naming. Avoid spaces and uppercase letters in filenames. :::


4. GitHub Deployment

You can deploy your Hugo site automatically to GitHub Pages using GitHub Actions. This approach works for both personal and project sites.


๐Ÿง‘โ€๐Ÿ’ป 1. Create a GitHub repository

Create a new repository on GitHub.
Do not initialize it with a README or .gitignore.

Connect your local project:

1
2
3
git remote add origin https://github.com/yourusername/yourrepo.git
git branch -M main
git push -u origin main

โš™๏ธ 2. Add GitHub Actions workflow

Create the file .github/workflows/deploy.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
name: Deploy Hugo site to GitHub Pages

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout source
        uses: actions/checkout@v3

      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v2
        with:
          hugo-version: 'latest'
          extended: true

      - name: Build site
        run: hugo --minify

      - name: Deploy to GitHub Pages
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./public

๐Ÿ—‚๏ธ 3. Configure config.yaml for deployment

In config/_default/config.yaml, set the correct baseURL:

1
baseURL: "https://yourusername.github.io/yourrepo/"

Make sure this path matches your actual repository name.


๐Ÿ›ก๏ธ 4. Enable GitHub Pages

Go to your repository โ†’ Settings โ†’ Pages
Set Source to Deploy from a branch โ†’ choose gh-pages branch and / (root) directory.

GitHub will host your site at:

1
https://yourusername.github.io/yourrepo/

๐Ÿงช 5. Test the deployment

Every time you push to main, GitHub will:

  • Build your site
  • Deploy it to the gh-pages branch
  • Make it available via GitHub Pages

Check the Actions tab for logs and status.


:::tip If you’re using a custom domain, configure it later via Cloudflare and set it in config.yaml under baseURL. :::


5. Cloudflare Setup

Cloudflare acts as a global CDN, DNS provider, and HTTPS enabler for your static website. It improves performance, reliability, and security.


๐ŸŒ 1. Add your domain to Cloudflare

  1. Go to https://dash.cloudflare.com
  2. Click โ€œAdd a Siteโ€
  3. Enter your custom domain (e.g. example.com)
  4. Select the Free Plan

๐Ÿงพ 2. Update your domainโ€™s nameservers

Cloudflare will show two nameservers (e.g. sue.ns.cloudflare.com, tim.ns.cloudflare.com).
Update your domain registrar (e.g. Swizzonic, GoDaddy) to use these nameservers.

โš ๏ธ DNS changes may take a few hours to propagate.


๐Ÿ“ 3. Set DNS records

Go to DNS โ†’ Records and add:

  • Type: CNAME
  • Name: www
  • Target: yourusername.github.io.
  • Proxy status: Proxied

Optional: add a redirect from root domain to www using Page Rules.


๐Ÿ”’ 4. Configure SSL/TLS

Go to SSL/TLS โ†’ Overview and set:

  • SSL Mode: Full or Full (Strict)
  • Auto HTTPS rewrites: โœ… enabled
  • Always Use HTTPS: โœ… enabled

Use Full (Strict) only if GitHub Pages presents a valid certificate for your domain.


๐Ÿ“„ 5. Set custom domain in Hugo

In config/_default/config.yaml, update:

1
baseURL: "https://www.example.com/"

If using multilingual setup, set baseURL under each language.


โš™๏ธ 6. Optional: Configure additional features

Cloudflare offers many extras:

  • Caching rules
  • WAF and security level
  • Custom error pages
  • Analytics and bot protection

You can manage these via the Cloudflare dashboard as needed.


:::tip If you’re using www.example.com, GitHub Pages should serve from gh-pages branch, and Cloudflare will handle HTTPS and DNS. :::


6. Resend Contact Form Integration

You can send emails from a static site by using a contact form that submits to a Cloudflare Worker, which then forwards the data via the Resend API.


๐Ÿ“ฌ 1. Create a Resend account and API key

  1. Go to https://resend.com
  2. Sign up and verify your email
  3. Add your domain (e.g. allegra-march.ch)
  4. Set up DNS records (SPF, DKIM, DMARC)
  5. Create an API key under API โ†’ Create API Key

๐Ÿงฐ 2. Create a Cloudflare Worker

Install Wrangler (CLI for Cloudflare Workers):

1
npm install -g wrangler

Login to your Cloudflare account:

1
wrangler login

Generate a new Worker:

1
2
wrangler init contact-worker --no-git
cd contact-worker

Edit src/index.ts (or index.js if using JS) with the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
export default {
  async fetch(request: Request): Promise<Response> {
    if (request.method !== "POST") {
      return new Response("Only POST allowed", { status: 405 });
    }

    const data = await request.json();

    const res = await fetch("https://api.resend.com/emails", {
      method: "POST",
      headers: {
        "Authorization": "Bearer YOUR_RESEND_API_KEY",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        from: "Form <form@yourdomain.com>",
        to: "your@email.com",
        subject: "New Contact Form Submission",
        text: `Name: ${data.name}\nEmail: ${data.email}\nMessage: ${data.message}`,
      }),
    });

    return new Response("Email sent", { status: 200 });
  },
};

Replace YOUR_RESEND_API_KEY and email addresses.


๐Ÿ” 3. Set your Resend key as a secret

1
wrangler secret put RESEND_API_KEY

In your code, replace the key with:

1
"Authorization": `Bearer ${RESEND_API_KEY}`

๐Ÿš€ 4. Deploy the Worker

1
wrangler publish

Note the deployed URL (e.g. https://contact-worker.yourname.workers.dev)


๐Ÿ–Š 5. Add the contact form to your Hugo site

1
2
3
4
5
6
<form method="POST" action="https://contact-worker.yourname.workers.dev">
  <input type="text" name="name" required placeholder="Your Name">
  <input type="email" name="email" required placeholder="Your Email">
  <textarea name="message" required placeholder="Your Message"></textarea>
  <button type="submit">Send</button>
</form>

Style with CSS as needed.


๐Ÿงช 6. Test your form

  • Fill out the form on your site
  • Check your inbox
  • Monitor delivery in the Resend dashboard

:::tip To prevent abuse, consider adding a simple honeypot field or reCAPTCHA. You can also validate requests in the Worker. :::


7. Security & Spam Protection

To prevent abuse of your contact form, it’s important to implement basic security and anti-spam measures. Static sites have no backend, so the protection must happen inside the Cloudflare Worker.


๐Ÿ”’ 1. Enforce request method and content type

Make sure your Worker only accepts POST requests with JSON payload:

1
2
3
4
5
6
7
if (request.method !== "POST") {
  return new Response("Method Not Allowed", { status: 405 });
}

if (!request.headers.get("Content-Type")?.includes("application/json")) {
  return new Response("Invalid content type", { status: 400 });
}

๐Ÿงช 2. Validate user input

Always sanitize and validate user input in the Worker:

1
2
3
4
5
6
7
8
9
const { name, email, message } = await request.json();

if (!name || !email || !message) {
  return new Response("Missing fields", { status: 400 });
}

if (!email.includes("@")) {
  return new Response("Invalid email", { status: 400 });
}

๐Ÿงฑ 3. Honeypot field (invisible trap)

Add a hidden field in your HTML form:

1
<input type="text" name="nickname" style="display:none">

In the Worker, check if it’s filled:

1
2
3
if (data.nickname) {
  return new Response("Spam detected", { status: 403 });
}

Bots will likely fill hidden fields; real users wonโ€™t.


โฑ 4. Rate limiting (basic)

Cloudflare Workers donโ€™t provide built-in rate limiting, but you can add logic using IP address + KV:

  • Track timestamp of last submission per IP
  • Block repeated requests within N seconds
  • Example: use Cloudflare KV or durable objects (advanced)

๐Ÿงฉ 5. reCAPTCHA (optional)

You can integrate Google reCAPTCHA v2/v3:

  1. Add a reCAPTCHA widget to the form
  2. Get the token on submit
  3. Send the token to the Worker
  4. Verify token via Google API inside the Worker

Requires server-side verification and is more complex to implement.


โœ‰๏ธ 6. Email protections via Resend

In your Resend dashboard:

  • Enable SPF, DKIM, and DMARC on your domain
  • Monitor bounce/spam reports
  • Use a verified from: address to avoid spam filters

:::tip Start simple with a honeypot and basic validation. Add rate-limiting or CAPTCHA only if spam becomes a problem. :::


8. Conclusion & Resources

By following this guide, youโ€™ve built a modern, fast, and secure static website using:

  • Hugo for content generation
  • GitHub Pages for hosting
  • Cloudflare for DNS, CDN, and HTTPS
  • Resend for handling contact form emails via API

This stack is fully serverless, free to use, and easy to maintain. Itโ€™s suitable for personal projects, non-profits, and multilingual community websites.


โœ… Summary of key benefits

  • Fast and optimized delivery via CDN
  • No backend or database required
  • Secure contact form with spam protection
  • Easy to version, update, and collaborate via Git
  • Zero hosting cost (if using free tiers)

๐Ÿ“š Useful resources


๐Ÿ™Œ Final note

You now have a powerful foundation for your website โ€” one that is fast, scalable, and privacy-respecting.

Extend it further with:

  • Hugo shortcodes, partials, and custom layouts
  • Cloudflare KV for storing form submissions
  • Resend Webhooks for tracking delivery status
  • GitHub Actions for content automation

:::info This guide is maintained by [Your Name] and used in real-world non-profit and volunteer projects. :::