The curious case of the two empty paragraphs tags - Hugo generates empty paragraphs at the start and end of each page

Hey All,

I ran into this strange instance where for each page of a website, Hugo generates an empty paragraph element at the start and end of the content output with {{ .Content }}. Below is an example of what one of the Markdown files look like.

---
title: Page-specific title goes here.
keywords: Page keywords go here.
description: Page meta description goes here.
---

{{< pricing >}}
{{< plans >}}
{{< testimonials >}}
{{< faq >}}
{{< cta-card >}}

However, if I change the above to the following, the problem no longer occurs.

---
title: Page-specific title goes here.
keywords: Page keywords go here.
description: Page meta description goes here.
---

{{< pricing >}}
<!-- this line is intentional to stop Hugo from starting and ending the content with empty paragraph tags -->
{{< plans >}}
{{< testimonials >}}
{{< faq >}}
<!-- this line is intentional to stop Hugo from starting and ending the content with empty paragraph tags -->
{{< cta-card >}}

I would love to understand why this is the case. I assumed it was because there is an empty line after the frontmatter and at the end of the file, but removing those did not solve the problem.

NOTE: The comments are not required. They are merely added to highlight the difference.

Are you viewing the rendered page using your browser’s dev tools, or are you viewing source using Ctrl+U?

Thank you for asking, @jmooring. Using the devtools, but as paragraph elements have a 1rem top and bottom margin, I can also see a white space after the header component and before the footer component. From that alone, I know that those are in the DOM. These go away when I change the Markdown file inside the content directory, as shown in my original post.

browsers and it’s devtools will trying to correct malformed HTML markup in many different ways. To accurately check the source of page generated by Hugo is by using View Source (Ctrl+U) to see the raw text source.

2 Likes

@pamubay is correct.

The rendered content does not have empty paragraphs. Instead, it is wrapped within opening and closing p tags, which in this case is not desirable. So, as you have learned, you need to either:

  1. Insert a blank line between shortcode calls, or
  2. Call the shortcodes using the {{% %}} notation

Why is the content wrapped within opening and closing p tags? It’s complicated. Take this example…

layouts/shortcodes/a.html

<div>AAA</div>

layouts/shortcodes/b.html

<div>BBB</div>

markdown

{{< a >}}
{{< b >}}

When you call a shortcode using the {{< >}} notation, we replace the shortcode call with a unique placeholder (a short string without whitespace) before sending the content to the markdown renderer (Goldmark). When we get the rendered content back from Goldmark, we replace the placeholder with the rendered shortcode.

So we send this markdown to Goldmark:

HAHAHUGOSHORTCODE-s0-HBHB
HAHAHUGOSHORTCODE-s1-HBHB

And Goldmark correctly returns this HTML:

<p>
HAHAHUGOSHORTCODE-s0-HBHB
HAHAHUGOSHORTCODE-s1-HBHB
</p>

The we do the replacement to produce this (invalid) HTML:

<p>
<div>AAA</div>
<div>BBB</div>
</p>

If you place a place a blank line between the shortcode calls, we send this markdown to Goldmark:

HAHAHUGOSHORTCODE-s0-HBHB

HAHAHUGOSHORTCODE-s1-HBHB

And Goldmark correctly returns this HTML:

<p>HAHAHUGOSHORTCODE-s0-HBHB</p>
<p>HAHAHUGOSHORTCODE-s1-HBHB</p>

The we do the replacement to produce this (valid) HTML:

<div>AAA</div>
<div>BBB</div>

But wait, where did the p tags go? When we do the replacement, if the placeholder is wrapped within opening and closing p tags, we remove the opening and closing p tags:

This approach is necessary, but imperfect.

3 Likes

Thank you for taking the time to provide feedback, @pamubay, and @jmooring. I looked at the source and saw that the entire page’s content is wrapped within a paragraph tag. It makes 100% sense why I am then getting those two empty paragraphs once the browser has done its thing and fixed the invalid HTML.

@jmooring, thank you for the info on the different ways to call a shortcode. I was unsure what the exact difference was as I could only find the following in the documentation:

will be called with either {{< myshortcode />}} or {{% myshortcode /%}} depending on the type of parameters you choose.

I did find this on the page about markdownify (transform.Markdownify | Hugo)
“If the resulting HTML is a single paragraph, Hugo removes the wrapping p tags to produce inline HTML as required per the example above.”

I did find that a little odd, as you can also use a mix of positional and names parameters with a shortcode.

Allowing both types of parameters (i.e., a “flexible” shortcode) is useful for complex layouts where you want to set default values that can be easily overridden by users.

I appreciate the details you went into concerning what happens when you call a shortcode with {{< use-cases >}}. So, the shortcode is replaced with HAHAHUGOSHORTCODE-s0-HBHB, but what happens when you call the shortcode with {{% use-cases %}}?

I am not expecting another detailed response :grin:, just the gist of what happens differently. Thanks again.

Interestingly when a call a shortcode using{{% features %}}, it outputs the HTML wrapped in a number of pre and code tags. I also noticed an HTML comment that states, <!-- raw HTML omitted -->.

I tried calling {{ .Content | safeHTML }}, but that interestingly did not change anything. I also tried piping it to .Page.RenderString, but in this instance, nothing is rendered for any of the shortcodes in the browser (all that is shown in the page source is a whole lot of <!-- raw HTML omitted -->).

It looks like the option for this instance is to add those empty lines.

We do not use a placeholder. The shortcode is fully rendered.

I suspect your content/code is indented four or more spaces, which is a markdown indented code block.

By default, the markdown renderer (Goldmark) considers HTML within markdown as unsafe, and comments it out. View source in your browser and you will see <!-- raw HTML omitted -->.

You can change Goldmark’s behavior with this in your site configuration:

[markup.goldmark.renderer]
unsafe = true

It’s not unsafe if you control the content.


Finally, I do not understand how the behavior of markdownify and “flexible” shortcodes are relevant to this conversation. It’s probably better to open a new topic for that discussion.

Thanks again for your always detailed responses. For the markdownify reference, I highlighted this as it speaks to your explanation of how Hugo unwraps those paragraph tags that cause the invalid HTML, which in turn can lead to the situation I ran into.

Concerning the “flexible” mention. I highlighted this as the page on creating your own shortcodes states which shortcode syntax you choose is based on the type of parameters you choose to use with the custom shortcode. Later on, it indicates that you could also combine different types of parameters with a single shortcode. I suspect the shortcode syntax you choose would then not be based on the parameter type but instead would be based on the difference in how Hugo processes the different types of shortcodes, i.e., the great explanation you provided here.

It would be great if that were also added to the docs. I am happy to open a pull request if you and others agree that it would be helpful. With that said, I am happy to add support if you (@jmooring) would rather open a pull request yourself.

By default, the markdown renderer (Goldmark) considers HTML within markdown as unsafe

Yup, I figured, which is why I tried {{ .Content | safeHTML }} but I guess it is too late at this stage of the pipeline. :+1:

I suspect your content/code is indented four or more spaces, which is a markdown indented code block.

Aha! Prettier takes care of most of that, and I assume the default is four spaces. If I look at the content of one of the shortcodes, some of the code is indeed indented with four spaces.

<div
  class="hero-main hero-main-{{ $hero_data.background_image_style }}"
>
  <div class="girdle hero-main-wrapper">
    <div class="hero-main-content-container">
      <h1 class="hero-heading {{ $hero_data.title_decorator_class }}">

I find, in general that Prettier does not play well with Hugo templates :slight_smile: Neither does the axe-linter VSCode plugin, for that matter, especially if you build an unordered list like this:

<ul class="reset-list team-gallery">
  {{ range . }}
  <li class="team-member">
    ...
  </li>
  {{ end }}
</ul>
{{ end }}

I am getting off the topic now, though :slight_smile: Thanks again for the help and detailed explanations @jmooring, I really do appreciate it.

Thanks for pointing that out; the documentation is wrong. I have deleted that reference.

1 Like