Alright, let’s talk about baseof.html. This is it. The big one. The master layout file. If your Hugo site were a stage play, this file would be the stage, the curtains, the lighting rig, and the seating plan. The actual page templates? Those are just the actors who walk on, deliver their lines into the main spotlight, and walk off. They don’t have to worry about the HTML scaffolding, the <head>, the meta tags, the footer—that’s all handled backstage by the master layout.

Think of it as the single source of truth for the structure of your final, rendered HTML. Without it, you’d be copying and pasting the same boilerplate code into every single template file like some kind of animal. We’re better than that.

Here’s the skeleton of a typical baseof.html, living in your layouts directory. It uses Hugo’s block system, which is its way of letting child templates inject content into specific named slots in this master template.

<!DOCTYPE html>
<html lang="{{ .Site.Language.Lang }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{{ block "title" . }}{{ .Site.Title }}{{ end }}</title>
    {{ hugo.Generator }}
    {{ partial "head/css.html" . }}
    {{ partial "head/favicon.html" . }}
</head>
<body>
    <header>
        {{ partial "header/site-header.html" . }}
    </header>
    <main>
        {{ block "main" . }}{{ end }}
    </main>
    <footer>
        {{ partial "footer/site-footer.html" . }}
    </footer>
    {{ partial "footer/scripts.html" . }}
</body>
</html>

The Anatomy of a Block

See those {{ block "main" . }} statements? A block defines a named area that a child template (like single.html) can override. The magic is in the {{ end }}—anything you put between the block declaration and its {{ end }} is the default content. If a child template doesn’t define that block, Hugo falls back to this default. In the title block above, the default is just the site title. For the main block, the default is… nothing. Which makes sense; a page with just a header and footer but no main content is pretty useless.

A child template, like layouts/_default/single.html, uses the {{ define }} action to override these blocks. Its entire job is to fill in the blanks.

{{ define "title" }}
    {{ .Title }} &middot; {{ .Site.Title }}
{{ end }}

{{ define "main" }}
    <article class="post">
        <h1>{{ .Title }}</h1>
        <div class="content">
            {{ .Content }}
        </div>
    </article>
{{ end }}

Notice what’s not here? There’s no <html>, no <head>, no <body>. The child template isn’t standalone; it’s just providing chunks of code to be slotted into the already-defined structure of baseof.html. Hugo handles the merging. This is the absolute core concept to grasp.

The Scoping Dot

You probably noticed the dot in {{ block "main" . }} and {{ define "main" }}. This is crucial. That dot is the current context—the Page data, the Site variables, all the good stuff. When you pass that dot into the block declaration, you’re ensuring that the context is passed along to the block definition in the child template. If you forget it ({{ block "main" }}), the context inside that block will be empty and you’ll be staring at a bunch of empty variables wondering what you did wrong. Don’t forget the dot.

Pitfalls and Power Moves

One common “gotcha” is the order of execution. The templates in your layouts directory have an order of specificity. But baseof.html is special. When Hugo renders a page, it always starts with baseof.html. Then it looks for the most specific template for that page type (e.g., layouts/posts/single.html) to provide the blocks. This is why you can’t define a standalone single.html that ignores the base template—unless you delete baseof.html, which is a bad idea.

Another power move is defining blocks within blocks in your base template. Need a spot for page-specific JavaScript that belongs in the <head>? Define a head-custom block with an empty default.

<head>
    ... all the standard head stuff ...
    {{ block "head-custom" . }}{{ end }}
</head>

Then, in a template for a page that requires a special script, just override that block:

{{ define "head-custom" }}
    <script src="https://example.com/fancy-map-library.js"></script>
{{ end }}

This keeps your baseof clean and lets you inject code exactly where it needs to go, without messy conditional statements or partials that check for the existence of parameters. It’s elegant, powerful, and exactly how this system was meant to be used. Embrace the block.