The menu generated by `sectionPagesMenu` is not detecting Children items and does not seem to be Recursive in nature

Hi everyone! Hugo noob here, this is my first post so a few words first… Loving the system and finding the documentation to be REALLY robust - so, to all maintainers/advanced users, THANK YOU!

Second thing before I dive into details… This is a LONG post - I tried to make my question as clear and well illustrated as possible. If you’re looking for a TL;DR of this post, scroll to the bottom of the post and read the Conclusion section which outlines a summarised list of the actual questions I’m asking.

Third/last before continuing: THANKS IN ADVANCE!!! to whomever can help me figure things out here! :smile:

Alrighty… I’ve read over the Menu documentation. My question is either straight forward and simple OR, I may be encountering a bug with the latest version of Hugo… :man_shrugging:

What I was hoping: I was hoping that sectionPagesMenu would (with the “default” recursive template provided in the documentation) automatically generate nested menus.

What is happening: Only top level items are being rendered/picked up. When I dump site.Menus.main (using a <script> tag to view JSON rendering of site.Menus.main) I can see that top (top section) Menu objects that should have child items (site structure follows below), don’t.

Site/Content Structure

content/
├── _index.md
├── about.md
├── privacy-policy.md
├── terms-and-conditions.md
├── artwork
│   ├── _index.md
│   └── art-piece-00
│       ├── img
│       │   ├── primary
│       │   │   └── primary_768x1024_00.jpg
│       │   └── secondary
│       │   │   └── secondary_768x1024_00.jpg
│       │   │   └── secondary_768x1024_01.jpg
│       └── index.md
└── updates
    ├── _index.md
    ├── event1
    │   └── index.md
    ├── event2
    │   └── index.md
    └── event3
        ├── _index.md
        └── subevent1
            ├── event4.md
            └── event5.md

JSON Dump of site.Menus.main

  [
    {
      'Identifier': '',
      'Parent': '',
      'Name': 'About',
      'Pre': '',
      'Post': '',
      'URL': '',
      'PageRef': '',
      'Weight': 0,
      'Title': 'About',
      'Params': null,
      'Menu': '',
      'ConfiguredURL': '',
      'Page': {
        'Date': '2024-02-27T22:46:56-05:00',
        'Lastmod': '2024-02-27T22:46:56-05:00',
        'PublishDate': '2024-02-27T22:46:56-05:00',
        'ExpiryDate': '0001-01-01T00:00:00Z',
        'Aliases': null,
        'BundleType': '',
        'Description': '',
        'Draft': false,
        'IsHome': false,
        'Keywords': null,
        'Kind': 'page',
        'Layout': '',
        'LinkTitle': 'About',
        'IsNode': false,
        'IsPage': true,
        'Path': '/about',
        'Pathc': '/about',
        'Slug': '',
        'Lang': 'en',
        'IsSection': false,
        'Section': '',
        'Sitemap': {'ChangeFreq': '', 'Priority': -1, 'Filename': 'sitemap.xml'},
        'Type': 'page',
        'Weight': 0,
      },
      'Children': null,
    },
    {
      'Identifier': 'artwork',
      'Parent': '',
      'Name': 'Artwork',
      'Pre': '',
      'Post': '',
      'URL': '',
      'PageRef': '',
      'Weight': 0,
      'Title': 'Artwork',
      'Params': null,
      'Menu': '',
      'ConfiguredURL': '',
      'Page': {
        'Date': '2024-02-27T22:46:56-05:00',
        'Lastmod': '2024-02-27T22:46:56-05:00',
        'PublishDate': '2024-02-27T22:46:56-05:00',
        'ExpiryDate': '0001-01-01T00:00:00Z',
        'Aliases': null,
        'BundleType': 'branch',
        'Description': '',
        'Draft': false,
        'IsHome': false,
        'Keywords': null,
        'Kind': 'section',
        'Layout': '',
        'LinkTitle': 'Artwork',
        'IsNode': true,
        'IsPage': false,
        'Path': '/artwork',
        'Pathc': '/artwork',
        'Slug': '',
        'Lang': 'en',
        'IsSection': true,
        'Section': 'artwork',
        'Sitemap': {'ChangeFreq': '', 'Priority': -1, 'Filename': 'sitemap.xml'},
        'Type': 'artwork',
        'Weight': 0,
      },
      'Children': null,
    },
    {
      'Identifier': '',
      'Parent': '',
      'Name': 'Privacy Policy',
      'Pre': '',
      'Post': '',
      'URL': '',
      'PageRef': '',
      'Weight': 0,
      'Title': 'Privacy Policy',
      'Params': null,
      'Menu': '',
      'ConfiguredURL': '',
      'Page': {
        'Date': '2024-02-27T22:46:56-05:00',
        'Lastmod': '2024-02-27T22:46:56-05:00',
        'PublishDate': '2024-02-27T22:46:56-05:00',
        'ExpiryDate': '0001-01-01T00:00:00Z',
        'Aliases': null,
        'BundleType': '',
        'Description': '',
        'Draft': false,
        'IsHome': false,
        'Keywords': null,
        'Kind': 'page',
        'Layout': '',
        'LinkTitle': 'Privacy Policy',
        'IsNode': false,
        'IsPage': true,
        'Path': '/privacy-policy',
        'Pathc': '/privacy-policy',
        'Slug': '',
        'Lang': 'en',
        'IsSection': false,
        'Section': '',
        'Sitemap': {'ChangeFreq': '', 'Priority': -1, 'Filename': 'sitemap.xml'},
        'Type': 'page',
        'Weight': 0,
      },
      'Children': null,
    },
    {
      'Identifier': '',
      'Parent': '',
      'Name': 'Terms & Conditions',
      'Pre': '',
      'Post': '',
      'URL': '',
      'PageRef': '',
      'Weight': 0,
      'Title': 'Terms & Conditions',
      'Params': null,
      'Menu': '',
      'ConfiguredURL': '',
      'Page': {
        'Date': '2024-02-27T22:46:56-05:00',
        'Lastmod': '2024-02-27T22:46:56-05:00',
        'PublishDate': '2024-02-27T22:46:56-05:00',
        'ExpiryDate': '0001-01-01T00:00:00Z',
        'Aliases': null,
        'BundleType': '',
        'Description': '',
        'Draft': false,
        'IsHome': false,
        'Keywords': null,
        'Kind': 'page',
        'Layout': '',
        'LinkTitle': 'Terms & Conditions',
        'IsNode': false,
        'IsPage': true,
        'Path': '/terms-and-conditions',
        'Pathc': '/terms-and-conditions',
        'Slug': '',
        'Lang': 'en',
        'IsSection': false,
        'Section': '',
        'Sitemap': {'ChangeFreq': '', 'Priority': -1, 'Filename': 'sitemap.xml'},
        'Type': 'page',
        'Weight': 0,
      },
      'Children': null,
    },
    {
      'Identifier': 'updates',
      'Parent': '',
      'Name': 'Updates',
      'Pre': '',
      'Post': '',
      'URL': '',
      'PageRef': '',
      'Weight': 0,
      'Title': 'Updates',
      'Params': null,
      'Menu': '',
      'ConfiguredURL': '',
      'Page': {
        'Date': '2024-02-27T22:46:56-05:00',
        'Lastmod': '2024-02-27T22:46:56-05:00',
        'PublishDate': '2024-02-27T22:46:56-05:00',
        'ExpiryDate': '0001-01-01T00:00:00Z',
        'Aliases': null,
        'BundleType': 'branch',
        'Description': '',
        'Draft': false,
        'IsHome': false,
        'Keywords': null,
        'Kind': 'section',
        'Layout': '',
        'LinkTitle': 'Updates',
        'IsNode': true,
        'IsPage': false,
        'Path': '/updates',
        'Pathc': '/updates',
        'Slug': '',
        'Lang': 'en',
        'IsSection': true,
        'Section': 'updates',
        'Sitemap': {'ChangeFreq': '', 'Priority': -1, 'Filename': 'sitemap.xml'},
        'Type': 'updates',
        'Weight': 0,
      },
      'Children': null,
    }]

After searching around in the documentation, this forum and the net in general, I can’t find anyone saying that they’ve faced a similar issue. At first, I thought that perhaps page items were not being picked up by Hugo because they had draft: true in them; I then set draft: false in all *.md files; that didn’t work so I then deleted draft:... entirely from all MD files.

After reading a few posts (including this one), I tweaked the content folder dir structure a few times thinking that the issue might be because I didn’t understand the diff between Leaf and Branch bundles correctly. To clarify, the Updates section/Branch Bundle folder originally looked like this:

content/
└── updates
    ├── _index.md
    ├── event1.md
    ├── event2
    │   └── index.md
    └── event3
        ├── _index.md
        ├── event4.md
        └── event5.md

But changing from the above to what I have currently (illustrated in the “Site/Content Structure” code block) didn’t make any difference… and I get the feeling that both versions should work correctly.

So… all said, I’m starting to suspect that the auto-generated menu that Hugo provides as a value for sectionPagesMenu doesn’t run recursively… which is why I’m reaching out for help. If it doesn’t generate recursive menus, it feels like a bit of a wasted opportunity.

Conclusion

To explain… the site I’m working on will have A LOT of “artwork” and “updates” pages, each living as a Leaf Bundle like so:

content/
├── _index.md # <-- This is the home page and is a Section/List Page, right?
├── artwork # <-- Branch Bundle?
│   ├── _index.md # <-- Section/List Page?
│   └── art-piece-00 # <-- Leaf Bundle?
│       ├── img # <-- All files within this directory are "Page Resources" right?
│       │   ├── primary
│       │   │   └── primary_768x1024_00.jpg
│       │   └── secondary
│       │   │   └── secondary_768x1024_00.jpg
│       │   │   └── secondary_768x1024_01.jpg
│       └── index.md # <-- This is a Single Page?
└── updates # <-- Another Branch Bundle?
    ├── _index.md # <-- List Page for the "Updates" Branch Bundle?
    ├── event1 # <-- Leaf Bundle
    │   └── index.md # <-- Single Page
    ├── event2
    │   └── index.md
    └── event3 # <-- Branch/Section Bundle
        ├── _index.md # <-- Branch/Section List Page
        └── subevent1 # <-- Leaf Bundle
            ├── event4.md # <-- Single Page (will this be rendered?)
            └── event5.md # <-- Single Page (will this be rendered?)
  1. Could someone please confirm whether or not Hugo’s sectionPagesMenu / site.Menus.main value recurse through the site’s structure to generate a FULL and Children-Inclusive menu automatically?
  2. If not, then, is there an automated way WITHOUT adding “menu/parent” values in individual Page front matter to “get” a fully recursive menu?
  3. If there isn’t currently an “out-of-the-box” solution for a fully-recursive menu, then is there a way to use the existing Hugo API to generate this structure without having to define menu structures in YAML/TOML/JSON data (be it in Site Config or within Page Front Matter)? PS: I know I could update various archetype templates to automatically set the menu: parent... values but am really hoping this isn’t necessary as it “feels” messy and prone to potential error - esp. because I’m developing this site for use by non-technical/non-coder users.
  4. To whomever can help me out here - please let me know if I’ve understood the nature of each directory/file’s role in the code block above? Esp. with regards to event4.md and event5.md? From some of the discussion threads I’ve read here, it looks like Leaf Bundles EXPECT a index.md file and ONLY THAT will be rendered as a valid, auto-indexed page. If this is the case, does that mean that event[4|5].md will not be rendered at all? OR, would they be considered headless pages?

https://gohugo.io/content-management/menus/#define-automatically

To automatically define a menu entry for each top-level section of your site, enable the section pages menu in your site configuration.

If you have two top-level sections there will be two menu entries. There’s nothing recursive about this.


https://gohugo.io/templates/menu-templates/#example

This partial template recursively “walks” a menu structure, rendering a localized, accessible nested list.

But you need to create the menu structure before you can walk it.


You can also create a navigation panel by recursively walking your content structure.

Hi @jmooring, thank you very much for your assistance!

I think I understand your answers. I was hoping you could help out a bit more as follows:

(Part 1 of 3)

I asked:

You responded:

I’ve read that para several times and it never occurred to me that in it, “a menu entry” was literally referring to a single menu entry, as I believe you are indicating in your response. As mentioned in my OP, I’d thought that the sites.Menu.main “auto-generated” menu would be a fully recursively built menu and thus, in the para you quoted, where it says, “a menu entry for each top-level section”, I thought that, since Menu entities have a Children property in them, that the Children item would be a slice of next-depth Menu entries. I see now that the documentation is very literal in how it’s stated I think it may be helpful if the phrasing there was adjusted to clarify that section pages menu will not recurse and its automation is the creation of the “top-most level pages - i.e. only pages/sections in the root of the content folder”.

(Part 2 of 3)

I asked:

You responded:

Could you please point me in the right direction with regards to which part of the Hugo API and/or documentation would allow the recursive construction of a Menu object? I’ve come across the RegularPagesRecursive Method which seems likely to do the trick… I’ve also re-read the documentation on Page Kinds in the context of RegularPagesRecursive:

Two questions here:

  1. Is this the sort of way in which you’re suggesting I can “create a navigation panel…recursively”?
  2. It appears that List pages (section landing pages defined for e.g.: updates/_index.md) would not be part of the return value of RegularPagesRecursive nor .Site.GetPage (e.g. .Site.GetPage "section" "/updates"). Granted, I’ve yet to test this (I’m just starting my work day here) but I thought I’d ask this up front in hopes of hearing back from you over the course of the day today.

(Part 3 of 3)

I asked:

Given the site file/folder structure I originally provided (snippet follows for convenience):

content/
└── updates
    ├── _index.md
    ├── event7.md
    ├── event8.md
    ├── event1
    │   └── index.md
    ├── event2
    │   └── index.md
    └── event3
        ├── _index.md
        └── subevent1
            ├── event4.md
            └── event5.md

Could you (or someone else) please answer this question as well? I.e., in the structure above:

  1. Will the following pages be rendered?: [event7.md, event8.md]
  2. When generating a Recursive Section Menu using RegularPagesRecursive or .Site.GetPage, will the following List pages be in the returned data structure?: [updates/_index.md, event3/_index.md]?

As always, many thanks!

Anand :smile:

Here’s an example site that includes a side bar menu generated by walking the content.

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

Files of interest:

  • layouts/partials/render-section-menu.html
  • layouts/_default/baseof.html (lines 40-46; calls the partial)
1 Like

Hey @jmooring - am going through the code you provided - thank you!

Quick Q: in {{ partial "render-section-menu.html" (dict "currentPage" $ "nodes" .) }}, what does the $ between currentPage and nodes mean?

Full code block is:

{{ if eq .Section "documentation" }}
  {{ with (site.GetPage "/documentation").Pages }}
    {{ partial "render-section-menu.html" (dict "currentPage" $ "nodes" .) }}
  {{ end }}
{{ end }}

Am I correct in understanding that, within the {{with ...}} opening template tag, a variable is being set (site.GetPage ...).Pages and that, within the inner template tag, {{partial ... (dict "currentPage" $ "nodes" . ) }} the $ is referencing the dynamically set variable in the with tag?

So, in other words, {{with ...}} could have been:

  {{ $documentationPages := (site.GetPage "/documentation").Pages}}
  {{ with $documentationPages }}
      {{ partial "render-section-menu.html" (dict "currentPage" $documentationPages "nodes" .) }}

?

No. Please see docs.
https://gohugo.io/functions/go-template/with/#understanding-context

Use the $ to get the context passed into the template.

And please experiment with templating code yourself. It’s a great way to learn.

1 Like

Thanks for that! I’ve been struggling with understanding which parts of code I see in templates are part of the Hugo framework vs. parts that are coming from Golang. I didn’t realise that with (and perhaps other Functions/Methods) are acting as parts of a DSL with their own idiosyncracies… as in this case, I thought that the $ was a keyword/symbol similar to :=.

The only parts “coming from Golang” are the functions, operators, and statements provided by Go’s text/template package. Again, see docs:
https://gohugo.io/functions/go-template/

Thanks again, you rock! Am a visual learner so, having found the following URLs:

I’m taking the time to watch over the following YT vid (and others I may come across like it if need be - sharing here for posterity’s sake in case other newbies like me need a rapid and quick way to wrap their heads around this subject matter):

Again, for posterity’s sake…

In the video I’m watching, “A Crash Course on Go Templates”, the presenter has a very good explanation of the difference between the “.” (“dot”) and “$” (“dollar”) symbols as used within Hugo/Go Templates:

A Crash Course on Go Templates: time code = 13min/10sec.: the gist of it is:

BOTH . and $ (NOTE: just $, the dollar sign on its own and not $someVar) refer to the context variable that is passed into a given template. The difference is, the . can change what it’s pointing/referring to within a template based upon whether it is being used in EITHER a with or range “action”. For example, the value that . is pointing to can change within with and range template actions. On the other hand, the $ symbol ALWAYS refers to the ORIGINAL context that was passed into a template and it never changes.

Examples:

file: layouts/_default/baseof.html has calls a “partial” with the file name, “partials/some-partial.html” like so:

{{- /* file: layouts/_default/baseof.html */ -}}
{{ $myFullName := slice "Abraa" "Cadabra" "Dobrinda"  }}
{{ partial "some-partial.html" $myFullName }}

then, within partials/some-partial.html

{{- /* `.` refers to the slice, `$myFullName` that was passed into the partial template. */ -}}
{{ delimit . " " }}<br>{{- /* Outputs "Abraa Cadabra Dobrinda"  */ -}}

<!-- `$` ALSO refers to the slice, `$myFullName` that was passed into the partial template.  */ -}}
{{ delimit $ " " }}<br>{{- /* Outputs the same as `.` "Abraa Cadabra Dobrinda"  */ -}}

{{ range . }}
  {{- /*
    In here, the `.` is referring to each individual element that the slice $myFullName has in it.
    Thus, it will render "Abraa" then "Cadabra" then "Dobrinda" individually. NOTE: here, the `.` "dot"
    context IS NO LONGER A SLICE and thus, cannot be used with the "collections.Delimit" function.
  */ -}}
  {{ . }}<br>
  {{- /*
    Conversely, the `$` STILL refers to the original/"root level context" that was passed in, namely:
    $myFullName. Thus, it will render "Abraa Cadabra Dobrinda" as a full string for every iteration
    of the range's loop.
  */ -}}
  {{ delimit $ " " }}<br>
{{ end}}

So the total output of of layouts/some-partial.html should look something like this:

Abraa Cadabra Dobrinda<br>

Abraa Cadabra Dobrinda<br>

Abraa<br>
Abraa Cadabra Dobrinda<br>
Cadabra<br>
Abraa Cadabra Dobrinda<br>
Dobrinda<br>
Abraa Cadabra Dobrinda<br>

I hope this helps someone else… I think it would be helpful to move this reply into it’s own thread but am unsure of how or whether to do that.

Cheers!

Copy paste into a new tips and tricks post.

Thanks for that @Arif - DONE!: Hugo Templates, their nature and accessing the data (“Context”) injected into them: A detailed explanation of the “.” (dot) and “$” (dollar) symbols as used in Hugo Templates - i.e. their purpose and the difference between them

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.