Render headings differently on home page

Hello,

I would like to render markdown posts with different heading levels depending on whether a post is seen from its own single page or from the home page.

I have tried to do this by setting a variable in a scratchpad that tracks a heading level offset, which is set to 0 on single pages and set to 1 on the home page.

The heading level is then modified accordingly in the file render-heading.html.

Unfortunately, this produces inconsistent random results on each build.

Minimum working example

Templates
  • File layouts/_default/baseof.html:
    <!DOCTYPE html>
    <html lang="{{ site.Language.LanguageCode }}">
      <head>
        <meta charset="utf-8">
      </head>
      {{- block "main" . }}{{- end }}
    </html>
    
  • File layouts/_default/single.html:
    {{- define "main" }}
      {{- .Scratch.Set "offset" 0 }}
      {{- .Content }}
      <p>(single.html) .Scratch.Get "offset" = {{ .Scratch.Get "offset" }} (should be 0)
    {{- end }}
    
  • File layouts/_default/home.html:
    {{- define "main" }}
    {{- range .Paginator.Pages }}
      {{- .Scratch.Set "offset" 1 }}
      {{- .Content }}
      <p>(home.html) .Scratch.Get "offset" = {{ .Scratch.Get "offset" }} (should be 1)
    {{- end }}
    {{- end }}
    
  • File layouts/_default/_markup/render-heading.html:
    {{- $offset := .Page.Scratch.Get "offset" }}
    {{- $level := add .Level $offset }}
      <h{{ $level }}>
        <a class="Heading-link u-clickable" href="{{ .Page.RelPermalink }}">{{ safeHTML .Text }} (H{{ $level }})</a>
      </h{{ $level }}>
      <p>(render-heading.html) .Scratch.Get "offset" = {{ $offset }} (should be 0 on single page and 1 on home page)
    
Website built by Hugo v0.132.0
  • File index.html:

    <!DOCTYPE html>
    <html lang="en">
      <head>
    	<meta name="generator" content="Hugo 0.132.0">
        <meta charset="utf-8">
      </head>
      <h2>
        <a class="Heading-link u-clickable" href="/hugo-conditional-render-heading/post/1/">Heading (H2)</a>
      </h2>
      <p>(render-heading.html) .Scratch.Get "offset" = 1 (should be 0 on single page and 1 on home page)
      <p>(home.html) .Scratch.Get "offset" = 0 (should be 1)
      <h1>
        <a class="Heading-link u-clickable" href="/hugo-conditional-render-heading/post/2/">Heading (H1)</a>
      </h1>
      <p>(render-heading.html) .Scratch.Get "offset" = 0 (should be 0 on single page and 1 on home page)
      <p>(home.html) .Scratch.Get "offset" = 0 (should be 1)
    </html>
    
  • File post/1/index.html:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8">
      </head>
      <h2>
        <a class="Heading-link u-clickable" href="/hugo-conditional-render-heading/post/1/">Heading (H2)</a>
      </h2>
      <p>(render-heading.html) .Scratch.Get "offset" = 1 (should be 0 on single page and 1 on home page)
      <p>(single.html) .Scratch.Get "offset" = 0 (should be 0)
    </html>
    
  • File post/2/index.html:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8">
      </head>
      <h1>
        <a class="Heading-link u-clickable" href="/hugo-conditional-render-heading/post/2/">Heading (H1)</a>
      </h1>
      <p>(render-heading.html) .Scratch.Get "offset" = 0 (should be 0 on single page and 1 on home page)
      <p>(single.html) .Scratch.Get "offset" = 0 (should be 0)
    </html>
    

Is there a way to do this using render hooks? If not, could this be possible by manipulating the home page content with regular expressions?

What about:

{{- $offset := cond .Page.IsHome 1 0 }}

Thank you for your answer @bep. With this change, the offset seems to always be 0, even on the home page:

File render-heading.html with your change
{{- $offset := cond .Page.IsHome 1 0 }}
{{- $level := add .Level $offset }}
  <h{{ $level }}>
    <a class="Heading-link u-clickable" href="{{ .Page.RelPermalink }}">{{ safeHTML .Text }} (H{{ $level }})</a>
  </h{{ $level }}>
  <p>(render-heading.html) .Scratch.Get "offset" = {{ $offset }} (should be 0 on single page and 1 on home page)
Resulting index.html
<!DOCTYPE html>
<html lang="en">
  <head>
	<meta name="generator" content="Hugo 0.132.0">
    <meta charset="utf-8">
  </head>
  <h1>
    <a class="Heading-link u-clickable" href="/hugo-conditional-render-heading/post/1/">Heading (H1)</a>
  </h1>
  <p>(render-heading.html) .Scratch.Get "offset" = 0 (should be 0 on single page and 1 on home page)
  <p>(home.html) .Scratch.Get "offset" = 0 (should be 1)
  <h1>
    <a class="Heading-link u-clickable" href="/hugo-conditional-render-heading/post/2/">Heading (H1)</a>
  </h1>
  <p>(render-heading.html) .Scratch.Get "offset" = 0 (should be 0 on single page and 1 on home page)
  <p>(home.html) .Scratch.Get "offset" = 0 (should be 1)
</html>
Resulting post/1/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
  </head>
  <h1>
    <a class="Heading-link u-clickable" href="/hugo-conditional-render-heading/post/1/">Heading (H1)</a>
  </h1>
  <p>(render-heading.html) .Scratch.Get "offset" = 0 (should be 0 on single page and 1 on home page)
  <p>(single.html) .Scratch.Get "offset" = 0 (should be 0)
</html>
Resulting post/2/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
  </head>
  <h1>
    <a class="Heading-link u-clickable" href="/hugo-conditional-render-heading/post/2/">Heading (H1)</a>
  </h1>
  <p>(render-heading.html) .Scratch.Get "offset" = 0 (should be 0 on single page and 1 on home page)
  <p>(single.html) .Scratch.Get "offset" = 0 (should be 0)
</html>

Any idea how to get the offset to be 0 on single pages and 1 on the home page?

OK, but note that I didn’t read your original code that carefully, so I’m not sure what offset you want to have on home page. The above means: Home => 1, others => 0.

But OK, reading your original code I see what happens.

The .Content gets rendered once and cached, so there’s no simple way (that I can think of now) to have different headings for a given regular page when rendered on the home page vs on its own.

Thanks for taking the time to read my code.

The reason I would like to do this is because having several h1 elements on the same page used to be valid HTML5, but it was removed from the specification a couple of years ago (source, follow up). Now, the specification says that each page should have a single h1 element.

For example, a spec-compliant home page may have this outline:

  • (h1) Site title
    • (h2) Post 1
      • (h3) Heading 1
      • (h3) Heading 2
    • (h2) Post 2
      • (h3) Heading 1
      • (h3) Heading 2

And a spec-compliant post, when viewed on its own, may have this outline:

  • (h1) Post 1
    • (h2) Heading 1
    • (h2) Heading 2

In my opinion, it would be quite nice if Hugo would allow to easily create such an outline. Should I create an issue for this feature that links to this post?

I agree that it would be useful, for several reasons.

I had a quick scan, and this issue should solve this:

My plan was to add a (optional) scope key to the .Contents method, so you could do:

(.Contents "home").Render

Or something like that … It should not be too hard to add …

1 Like

A little early, but you may be interested in this PR … Add Page.Contents with scope support by bep · Pull Request #12759 · gohugoio/hugo · GitHub

1 Like

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