Partial Fallbacks

Hey folks!

Intro

So I’m having a templating problem. I’m not sure if this is a regression with the new system, a docs issue, or if I’m simply misunderstanding. This issue exists in a new theme I’m creating for my Linodians website. The repo is here. The branch, linked, is classic-theme and the theme I’m working on is linodians-classic. I will include snippets below.

The Problem

So in my theme I am using a partial for the header. That partial is located at _partials/header.html. What I want to happen is, that partial is used unless a more specific partial exists. I have a section called jsonfeed and when pages from that section are rendered, I want to use the partial _partials/header.jsonfeed.html. Despite this, only _partials/header.html is being used.

In short, I want to do the same general thing that the person in this closed topic wanted to do. In that topic, they were told it doesn’t work that way, to use renders. The problem is, in this doc it is suggested that it does work that way. Here’s the relevant snippet:

Hugo uses this lookup order to find a matching template:

layouts/_partials/footer.section.de.html
layouts/_partials/footer.section.html
layouts/_partials/footer.de.html
layouts/_partials/footer.html

So is @jmooring (:waving_hand: ) right and the doc is wrong? Vice-versa? I’m assuming he’s correct as I haven’t gotten this to work yet.

Extra Information

If you don’t want to dig into the repo (linked above), here’s snippets of what I’m doing.

ls -lah src/themes/linodians-classic/layouts/_partials/

header.html
header.jsonfeed.html
header.section.html

The last file was me just throwing things against the wall to see what sticks. :man_shrugging:

From my base template, layouts/baseof.html I have this snippet calling the partial:


 27     {{ $headerTpl := "header" }}                                                                                                                 
 28     {{ with .Section }}{{ $headerTpl = printf "%s.%s.html" $headerTpl . }}{{ end }}                                                              
 29     <p>DEBUG: {{ $headerTpl }}</p>                                                                                                               
 30     {{ partial $headerTpl . -}}

The debug paragraph I have there shows me that the correct partial string is being generated on each page. Despite that, only the bare header partial is being displayed. To make sure I know what actual partial is being used when pages are rendered, I’ve put some debugging words in front of the site title in the header for each partial.

It’s late for me so I’ll head to bed. In the morning I’ll try to do this with renders instead but I still think something (code or docs) is off here.

Hugo version string: hugo v0.152.2-6abdacad3f3fe944ea42177844469139e81feda6+extended linux/arm64 BuildDate=2025-10-24T15:31:49Z VendorInfo=snap:0.152.2

please recheck

  • which branch/theme and hugo version we should use
  • matches your current state where the problem is reproducable
  • and make sure it builds.

Will save some time


with hugo 0.152.2:

  • trunk → does not build

  • classic-theme → dose not build

  • renewed-theme → builds

    • uses theme “linodians-2025” the other is not available in that branch
    • uses pre 0.146 layout structure
    • and the mentioned path is not correct
      dir src/themes/linodians-classic/layouts/_partials/
      > src\themes\linodians-classic\layouts\_partials\' because it does not exist
      

  • your linked issue is from Jan 2024 for the old system
  • 146.0 was released Apr 2025

the 146+ docs for templates is here: New template system in Hugo v0.146.0 and the partial section has been adjusted (imho)

and an issue discussing that one: (New template lookup order also with _partials possible?)

dunno if this is the best approach, but with that one in your baseof:

  <header>
    {{ $header := add .Section "/header.html" }}
    {{ if templates.Exists (add "_partials/" $header) }}
      {{ warnf "FOUND: %s - %s for %s -> using specialized header" .Section $header .Path }}
      {{ partial $header . }}
    {{ else }}
      {{ warnf "MISS: %s - %s for %s -> falling back to global header" .Section $header .Path }}
      {{ partial "header.html" . }}
    {{ end }}
  </header>

and a header in layouts/_partials/SECTION/header.html

<h1>{{ site.Title }}</h1>
<h4>{{ .Section }} HEADER: {{ templates.Current.Name }} - {{ .Page.Path }}</h4>
{{ partial "menu.html" (dict "menuID" "main" "page" .) }}

works at least with v0.152.2

Hey irkode, thank you for taking the time to respond. I’ll walk through your comments.

Not true. It absolutely builds. The website is live, trunk is built every morning automatically, and builds on my machine. You can look at the GitHub Actions tab and see every time trunk or classic-theme has built. Here’s a direct example of the classic-theme branch being built just as I opened this topic on Discourse: Trying to figure out fallback partials · linodians/www.linodians.com@692f3b3 · GitHub

I know. I mentioned that it was old and that the topic was closed. I’m linking to it to further explain my need, which is the same as that person’s.

Again, I know. I linked to the partial doc, which includes a reference to _partials which shows me it has been updated for the new system, as least partially. My issue is that what it says doesn’t seem to be working.

THIS one here did not come up in my search. Thank you for surfacing that. Reading through that topic, it seems like this is the same issue. My topic is a continuation of that one, as that was left unresolved.

I’m starting to think that there’s a bug in the code somewhere. In the docs, you can read layouts/_partials/footer.section.de.html as it will go through for various languages and sections. That’s not happening. Even if you were to look at this as “kinds” instead of actual sections, which you mentioned in that other topic, I have a partial literally called “_partials/header.section.html” and that one isn’t being called either.

I’m starting to think this is a bug because even if you forget the fallbacks, on my JSON Feed section/page, the partial being called for is header.jsonfeed.html yet it is being ignored and goes straight to header.html. That means some sort of template lookup is happening there.

Thank you for this example. With regard to my website, I will try this out along with the render function and see what makes the most sense for me. With regard to Hugo, I don’t want to let this issue go stale again. I will continue to test and have a look at the code is necessary.

mmh, I’m on windows and here is the log

Summary
PS C:\_repos\github\clone> git clone https://github.com/linodians/www.linodians.com.git
Cloning into 'www.linodians.com'...
remote: Enumerating objects: 440, done.
remote: Counting objects: 100% (120/120), done.
remote: Compressing objects: 100% (70/70), done.
remote: Total 440 (delta 53), reused 78 (delta 42), pack-reused 320 (from 1)
Receiving objects: 100% (440/440), 1.32 MiB | 5.45 MiB/s, done.
Resolving deltas: 100% (165/165), done.
PS C:\_repos\github\clone> cd www.linodians.com

PS C:\_repos\github\clone\www.linodians.com> git branch
* trunk

PS C:\_repos\github\clone\www.linodians.com> hugo -s src
Start building sites …
hugo v0.152.2-6abdacad3f3fe944ea42177844469139e81feda6+extended windows/amd64 BuildDate=2025-10-24T15:31:49Z VendorInfo=gohugoio

ERROR render of "C:/_repos/github/clone/www.linodians.com/src/content/blog/_index.md" failed: "C:\_repos\github\clone\www.linodians.com\src\themes\linodians-2025\layouts\blog\list.html:14:24": execute of template failed: template: blog/list.html:14:24: executing "main" at <$origImg.Resize>: error calling Resize: resize /img/feature/json-feed-introduction--2000x1250.jpg: image: unknown format
Total in 1075 ms
Error: error building site: render: failed to render pages: render of "C:/_repos/github/clone/www.linodians.com/src/content/blog/providing-a-new-home-to-jsonfeed.md" failed: "C:\_repos\github\clone\www.linodians.com\src\themes\linodians-2025\layouts\_default\baseof.html:19:4": execute of template failed: template: blog/single.html:19:4: executing "blog/single.html" at <partial "open-graph" .>: error calling partial: "C:\_repos\github\clone\www.linodians.com\src\themes\linodians-2025\layouts\partials\open-graph.html:8:23": execute of template failed: template: _partials/open-graph.html:8:23: executing "_partials/open-graph.html" at <$origImg.Fill>: error calling Fill: fill /img/feature/json-feed-introduction--2000x1250.jpg: image: unknown format



PS C:\_repos\github\clone\www.linodians.com> git checkout classic-theme
branch 'classic-theme' set up to track 'origin/classic-theme'.
Switched to a new branch 'classic-theme'
PS C:\_repos\github\clone\www.linodians.com> hugo -s src
Start building sites …
hugo v0.152.2-6abdacad3f3fe944ea42177844469139e81feda6+extended windows/amd64 BuildDate=2025-10-24T15:31:49Z VendorInfo=gohugoio

ERROR render of "C:/_repos/github/clone/www.linodians.com/src/content/blog/_index.md" failed: "C:\_repos\github\clone\www.linodians.com\src\themes\linodians-classic\layouts\blog\list.html:16:24": execute of template failed: template: blog/list.html:16:24: executing "main" at <$origImg.Resize>: error calling Resize: resize /img/feature/json-feed-introduction--2000x1250.jpg: image: unknown format
Total in 693 ms
Error: error building site: render: failed to render pages: render of "C:/_repos/github/clone/www.linodians.com/src/content/blog/providing-a-new-home-to-jsonfeed.md" failed: "C:\_repos\github\clone\www.linodians.com\src\themes\linodians-classic\layouts\baseof.html:19:4": execute of template failed: template: blog/single.html:19:4: executing "blog/single.html" at <partial "open-graph" .>: error calling partial: "C:\_repos\github\clone\www.linodians.com\src\themes\linodians-classic\layouts\_partials\open-graph.html:8:23": execute of template failed: template: _partials/open-graph.html:8:23: executing "_partials/open-graph.html" at <$origImg.Fill>: error calling Fill: fill /img/feature/json-feed-introduction--2000x1250.jpg: image: unknown format



PS C:\_repos\github\clone\www.linodians.com> git checkout renewed-theme
branch 'renewed-theme' set up to track 'origin/renewed-theme'.
Switched to a new branch 'renewed-theme'
PS C:\_repos\github\clone\www.linodians.com> hugo -s src
Start building sites …
hugo v0.152.2-6abdacad3f3fe944ea42177844469139e81feda6+extended windows/amd64 BuildDate=2025-10-24T15:31:49Z VendorInfo=gohugoio


                  │ EN
──────────────────┼────
 Pages            │ 42
 Paginator pages  │  0
 Non-page files   │  0
 Static files     │ 17
 Processed images │ 18
 Aliases          │  1
 Cleaned          │  0

Total in 258 ms

Weird. It seems to be complaining about the image filetype. It’s a jpeg. Completely standard stuff. I’m running on Ubuntu 25.04 locally and the GitHub Actions VM is running on Ubuntu 24.04. Both seem to work. This might be a completely different issue though then the templating one. I will spin up a Windows VM (it’s been awhile) and see if I can reproduce. I may open a different Discourse topic or GitHub issue if this is a OS specific problem.

That is not possible:
https://github.com/gohugoio/hugo/pull/13614#issuecomment-2805977008

there is no “page context” when we look for partial

And we would need to know the “page context” to determine if a more specific one exists. If your use case doesn’t require passing additional context, perhaps you could use a content view instead.

The partial template lookup order example in the docs attempts to demonstrate this:

This means it tries to find the most specific match first, then progressively looks for more general versions if the specific one isn’t found.

And that matching is based purely on the name.

What you linked to for partial matching in the docs, which I linked to above in my original post, is not working.

My partial call is: {{ partial $headerTpl . -} On the homepage, $headerTpl contains the value header. On the JSON Feed section page, it contains header.jsonfeed.html. On the blog page, it contains header.blog.html. In the _partials directory, I have the following, relavent files:

_partials/header.html
_partials/header.jsonfeed.html

On all three pages, the partial _partials/header.html is used. That would be correct for the homepage and blog page, but not the jsonfeed page. On the jsonfeed page, it is ignoring an exact filename match and still using the more generic match.

So let’s focus, for the moment, on whether the documented example is right or wrong.

git clone --single-branch -b hugo-forum-topic-56157 https://github.com/jmooring/hugo-testing hugo-forum-topic-56157
cd hugo-forum-topic-56157
hugo 

Look at the console to see which template was used. Now delete that one, build again, and look at the console. Rinse and repeat. The lookup order is what is shown in the docs.

Thank you for putting together that example. I now see the problem I am having. The partial lookup is literally looking for the string “section” in the filename. Whether that specific string is there or not.

That seems very unintuitive to me. Change the string section to anything else and the example doesn’t work. It’s more confusing since, if I change the language in the config file to English, _partials/footer.section.html is rendered instead of _partials/footer.section.de.html even though 1) That file exists and 2) that is what is being called.

This is also confusing/not ideal because let’s go back to the example in my website. I am calling the partial header.jsonfeed.html and instead header.html is being rendered. Forget fallbacks. That’s a literal filename match and it is not loading the template I called. My guess now is that it’s parsing jsonfeed and a potential language code, to which there’s no match, and throwing it away. That feels like incorrect functionality.

You have mismatch between output format and media type suffix:

header.jsonfeed.html

The jsonfeed output format has media type “application/json” which by default has one suffix: json.

Either change the template name and call, or redefine the media type:

[mediaTypes]
  [mediaTypes.'application/json']
    suffixes = ['json','html']

I’m not using output formats here. I thought that might be the issue too. That just happens to be the name of one of my sections. Swap jsonfeed for “blog” or “docs” and it’s the same issue.

I’m not using output formats here.

That output format is defined in your repo. Or are you talking about a different repo?

It is but that’s a red herring. Replace the entire example with the blog. I have _partials/header.blog.html locally. It will never load. Only _partials/header.html.

We are failing to communicate.

Redefining the media type, as I suggested above, does what you want. The “jsonfeed” segment of the partial filename is now valid.

It doesn’t work. Forget my site for a second. Let’s go back to your test repo (which I love btw, thank you). I made a commit that added a new partial, _partials/footer.green.html. I also told the all template to load that partial: {{ partial footer.green.html }}. It’s an exact filename match. If you run Hugo, that partial isn’t loaded, _partials/footer.html is instead. That doesn’t seem right to me.

Here’s the PR diff if what changes I made: Partial name doesn't load by FelicianoTech · Pull Request #12 · jmooring/hugo-testing · GitHub

In this scenario, I’m calling a partial exactly by it’s name. I expect it to load. It’s not.

Yes, it does. I’ve updated the test site:

git clone --single-branch -b hugo-forum-topic-56157 https://github.com/jmooring/hugo-testing hugo-forum-topic-56157
cd hugo-forum-topic-56157
hugo 

Now update the site config, uncommenting the media type re-definition, and build again.


2025-10-27T08:49:07-07:00
EDIT: I forgot to push the last change. Please pull changes.

Did you see what I wrote and my PR?


As for your example, I pulled your latest commit and ran it. It’s not working. Here’s a screenshot:

The same partial, _partials/header.html is used each time. I even ran a command to spit out the commit hash first so you can see it’s the correct commit I’m running. My assumption of how your example is suppose to work is that both example 1 and 3 use header.html but example 2 should have used header.jsonfeed.html?

Really?

WARN  Example 1: {{ partial "header" . }}
WARN  "_partials/header.html" [1]

WARN  Example 2: {{ partial "header.jsonfeed.html" . }}
WARN  "_partials/header.jsonfeed.html" [2]

WARN  Example 3: {{ partial "header.blog.html" . }}
WARN  "_partials/header.html" [3]

And yes, I looked at your PR. Same problem. “green” is not a valid file name segment. Perhaps that’s the source of the conceptual disconnect.


I tested all of the above with hugo v0.152.2-6abdacad3f3fe944ea42177844469139e81feda6+extended linux/amd64 BuildDate=2025-10-24T15:31:49Z VendorInfo=gohugoio.

Not happening for me. Very weird. What OS are you using?

I need to step away for a few minutes but I will try on another machine. I’m using Ubuntu currently but I’ll try on macOS. Maybe there’s an OS issue, maybe it’s because I’m using the snap version of Hugo? Something. I’m running the same commit as you and getting different output.