Example Hugo Project Structure
Example Hugo Project Structure#
Here’s a typical Hugo project layout:
/my-hugo-site
├── archetypes/ # Default front matter templates
├── assets/ # Processed assets (SCSS, JS, images)
├── content/ # Site content (Markdown)
│ ├── _index.md # Homepage content
│ ├── blog/
│ │ ├── _index.md # Blog section
│ │ ├── post-1.md # Blog post 1
│ │ ├── post-2.md # Blog post 2
├── data/ # Structured data (JSON, YAML, TOML)
├── layouts/ # Custom layouts and overrides
│ ├── _default/
│ │ ├── baseof.html # Base template
│ │ ├── single.html # Layout for single pages
│ │ ├── list.html # Layout for section pages
│ ├── partials/
│ │ ├── header.html # Header template
│ │ ├── footer.html # Footer template
│ ├── index.html # Custom homepage layout
├── static/ # Unprocessed static files
│ ├── img/ # Images
│ ├── js/ # JavaScript files
├── themes/ # Installed themes
│ ├── mytheme/ # Theme folder
├── config.toml # Site configuration
├── public/ # Generated site output
Understanding Key Concepts#
1. Base URL (baseURL
)#
Defines the root URL of your site. Example:
baseURL = "https://example.com/"
Used to generate absolute URLs for pages, images, and assets.
If you’re working locally, you can override it:
hugo server --baseURL=http://localhost:1313/
2. Permalinks (permalinks
)#
Controls how URLs are structured. Example:
permalinks = {
blog = "/blog/:slug/"
}
/blog/my-first-post/
instead of/blog/my-first-post.md
.- Other placeholders:
:year
→/blog/2025/my-first-post/
:title
→/blog/my-first-post/
3. Menu & Navigation (menu
)#
Defines site navigation. Example:
[menu]
[[menu.main]]
identifier = "home"
name = "Home"
url = "/"
weight = 1
[[menu.main]]
identifier = "blog"
name = "Blog"
url = "/blog/"
weight = 2
weight
determines menu order.Displayed in
layouts/partials/header.html
:<nav> {{ range .Site.Menus.main }} <a href="{{ .URL }}">{{ .Name }}</a> {{ end }} </nav>
4. baseof.html
(Base Template)#
baseof.html
acts as a wrapper for all pages.
<!DOCTYPE html>
<html>
<head>
<title>{{ .Title }}</title>
<link rel="stylesheet" href="{{ "css/main.css" | relURL }}">
</head>
<body>
{{ partial "header.html" . }}
<main>{{ block "main" . }}{{ end }}</main>
{{ partial "footer.html" . }}
</body>
</html>
{{ block "main" . }}
→ Content goes here based onsingle.html
orlist.html
.{{ partial "header.html" . }}
→ Inserts a reusable header.
How Themes Work & Customisation Without Ejecting#
How Themes Work in Hugo#
Themes live in /themes/
and provide:
- Layouts (
themes/mytheme/layouts/
) - Static files (
themes/mytheme/static/
) - Config settings (
themes/mytheme/config.toml
)
If a theme is used (theme = "mytheme"
in config.toml
), Hugo looks for files in this order:
- Your project (
layouts/
) → Custom overrides - The theme (
themes/mytheme/layouts/
) → Default files
Customizing a Theme Without Ejecting#
Instead of modifying theme files directly:
Override layouts:
Copy a file from
themes/mytheme/layouts/
tolayouts/
and edit it.Example: Override
themes/mytheme/layouts/_default/single.html
cp themes/mytheme/layouts/_default/single.html layouts/_default/single.html
Override CSS/JS:
Add custom styles in
assets/css/custom.css
:body { background-color: #f5f5f5; }
Load it in
baseof.html
:<link rel="stylesheet" href="{{ "css/custom.css" | relURL }}">
Override partials:
- Copy and modify
themes/mytheme/layouts/partials/header.html
- Place it in
layouts/partials/header.html
- Copy and modify
Handling Image Optimisation in a Large Hugo Site (1000+ Images)#
Problem#
If your site has 1000 images for example, pushing all of them every time you update a post is very inefficient.
Solution: Separate Image Storage & Processing#
1. Use static/
or External Storage#
Store images outside the repo (
static/images/
).Git ignores them (
.gitignore
):static/images/*
2. Optimize with Hugo Pipes#
Example: Compress images before deployment.
{{ $img := resources.Get "images/large-photo.jpg" | images.Resize "800x" | images.Optimize }}
<img src="{{ $img.RelPermalink }}" alt="Optimized Image">
3. Use an External CDN#
Upload images to Cloudinary, BunnyCDN, or S3.
Store URLs in Markdown instead of raw images:

4. Deploy Site Without Re-uploading Images#
Instead of deploying images every time:
Host images externally → Use a CDN
Ignore images in Git → Avoid repo bloat
Automate uploads → Sync only new images with
rsync
rsync -av --ignore-existing static/images/ remote:/var/www/images/