/ โ† HTML Checker Fixes / Static Site Generators
In this guide
  1. Where static site errors come from
  2. Validating the build output
  3. Layout and partial errors
  4. Markdown and shortcode quirks
  5. The fix, step by step
  6. A build check you can adapt
  7. Edge cases
  8. Questions and answers

Where static site errors come from

A static site page is assembled from three layers, and each produces its own kind of error:

  1. Layouts and partials, written in Go templates for Hugo, Liquid for Jekyll, Nunjucks, Liquid or others for Eleventy, and .astro components for Astro. Errors here repeat on every page that uses the layout.
  2. Markdown conversion, done by Goldmark in Hugo, kramdown in Jekyll by default, markdown-it in Eleventy and remark in Astro. Each follows its own rules for raw HTML, blank lines and indentation, and those rules decide whether your HTML survives intact.
  3. Shortcodes, includes and components dropped into content. They are written by developers but placed by authors, often in positions the developer did not expect, such as the middle of a paragraph.

Because the output is generated, the fix is never to edit the HTML file in the output folder. It will be overwritten on the next build. Every fix goes into a layout, a partial, a content file or the generator's configuration.

GeneratorOutput folderMarkdown converterRaw HTML in Markdown
Hugopublic/GoldmarkOmitted by default and replaced with an HTML comment; enabled with the unsafe renderer setting
Jekyll_site/kramdownPassed through; Markdown inside HTML blocks is not processed unless the block has markdown="1"
Eleventy_site/markdown-itPassed through by default; Markdown files are also run through a template language first
Astrodist/remark and rehypePassed through in .md; .mdx treats HTML as JSX and is stricter

Validating the build output

Run the HTML Checker against a few deployed pages of each type to see the errors as visitors get them. To check every page at once, validate the output folder locally with a command-line validator.

# html-validate (Node)
npm install --save-dev html-validate
npx html-validate "public/**/*.html"

# The Nu HTML Checker, the engine behind the W3C validator (Java)
java -jar vnu.jar --skip-non-html --errors-only _site/

html-validate reads its rules from a .htmlvalidate.json file. Start with its recommended preset and turn off rules that do not reflect real problems on your site, rather than ignoring the whole report. The Nu checker gives the same messages as the W3C online validator, which makes it easier to compare with the HTML Checker results.

Turn off minification while you debug

Minified output puts a whole page on one line, so every error is reported at line 1 and a column number in the tens of thousands. Build without minification when validating: in Hugo, leave off the --minify flag; in Eleventy and Astro, disable the minifying transform or integration for the debug build. Minifiers also legitimately remove optional end tags and attribute quotes, which is valid HTML but can confuse stricter lint rules.

๐Ÿ’ก Validate a handful of representative pages first: the home page, a list page, a long article with images and embeds, and a page using each shortcode or component. That surfaces nearly every pattern before you run the full site.

Layout and partial errors

Content wrapped in a paragraph

The most common layout error is a template that wraps the rendered content in an element that cannot hold it. Rendered Markdown contains paragraphs, lists, headings and tables, so it needs a block container.

{{/* Hugo: invalid, because .Content contains its own paragraphs */}}
<p class="lead">{{ .Content }}</p>

{{/* Valid */}}
<div class="prose">{{ .Content }}</div>

The same applies to Jekyll's {{ content }}, Eleventy's {{ content | safe }} and an Astro <slot /> placed inside a <p> or <span>. Front matter descriptions rendered through Markdown have the same problem, because the converter adds its own paragraph; render them as plain text or use an inline Markdown function such as Hugo's markdownify or .RenderString, which avoid the wrapper for short text.

Partials that open what another closes

A header partial that opens <main> and a footer partial that closes it will break the day a layout uses one without the other. Keep each partial self-contained, and let the base layout own the page skeleton: doctype, html, head, body and main.

Layouts nested twice

In Jekyll and Eleventy, a layout can itself use another layout. If both include the full skeleton, the output contains two html and body elements and a stray doctype. Only the outermost layout should contain the skeleton; inner layouts output a fragment.

Escaped output

If the content shows literal tags such as &lt;p&gt; on the page, the template language is escaping HTML it should trust. In Nunjucks use the safe filter on rendered content; in Hugo, use safeHTML only for HTML you control; in Astro use the set:html directive. Escaped output is not a validation error, but the fix is often made wrongly by pasting HTML into a place that then nests badly.

Markdown and shortcode quirks

Shortcodes and components inside paragraphs

Authors often place a shortcode on the same line as text, or directly under a line of text with no blank line in between. The converter then treats it as part of the paragraph, and a shortcode that outputs a figure, div or iframe ends up inside a <p>. Put block shortcodes on their own line with a blank line above and below. In Hugo, note the difference between the two call styles: output from the {{< >}} form is inserted as it is, while output from the {{% %}} form is passed through the Markdown converter, which can add paragraph tags around it.

Indented HTML becomes a code block

In Markdown, a line indented by four spaces is code. Raw HTML in a Markdown file that is neatly indented for readability can be turned into an escaped code block halfway through, leaving an opening <div> without its closing tag. Keep raw HTML blocks flush with the left margin, or move complex HTML into a shortcode or component.

Blank lines end HTML blocks

Under CommonMark rules, which Goldmark, markdown-it and remark follow, a raw HTML block ends at the first blank line. Text after the blank line is treated as Markdown again. That can be useful, but it also means a blank line inside a <table> or <details> written by hand can split the HTML so that part of it is wrapped in paragraphs.

Hugo's raw HTML omitted comment

In Hugo, raw HTML in Markdown is removed by default and replaced with the comment <!-- raw HTML omitted -->. If only the opening tag of an element was on its own line, the result can be a closing tag with nothing to close. Either enable raw HTML deliberately with markup.goldmark.renderer.unsafe = true in the site configuration, or replace raw HTML in content with shortcodes.

Jekyll and markdown inside HTML

kramdown does not convert Markdown inside an HTML block unless the block carries markdown="1". Authors who expect Markdown to work inside a div sometimes add HTML tags by hand to compensate and leave some unclosed. The markdown="1" attribute is removed from the output by kramdown, so it does not become a validation error itself.

The fix, step by step

Step 1

Validate an unminified build of the site

Build the site locally with minification turned off, so the output has readable line numbers. Then run html-validate or the Nu checker over the output folder, public/, _site/ or dist/ depending on the generator. Compare a few results with the HTML Checker on the live site to confirm you are seeing the same errors, and export the report so you can sort messages by message type and by file.

Step 2

Map each error to a layout, partial or content file

If the same error appears on every page using one layout, the cause is in that layout or a partial it includes. If it appears on a single page, the cause is in that page's Markdown or a shortcode used there. Search the source for a class name or text near the error to find the file. Never fix the generated HTML in the output folder, because the next build overwrites it and the error returns.

Step 3

Fix element wrappers in layouts and partials

Change any p or span that wraps rendered content, a slot or a Markdown field into a div or a suitable sectioning element. Make each partial open and close its own elements, and make sure only the outermost layout contains the doctype, html, head and body. Check layouts that inherit from other layouts, because doubling up the page skeleton produces stray doctype and duplicate body errors on every page.

Step 4

Stop block output landing inside paragraphs

Find shortcodes, includes and components that output block elements such as figure, div, iframe or table, and check where authors have placed them. Move each call onto its own line with a blank line above and below. In Hugo, use the angle-bracket call style for shortcodes that output finished HTML. Add a note to the shortcode documentation explaining that it must stand alone, so new content follows the same rule.

Step 5

Set how the Markdown renderer treats raw HTML

Decide whether authors may write raw HTML in Markdown. If yes, enable it explicitly, for example with the Goldmark unsafe setting in Hugo, and keep raw HTML flush left without blank lines inside it. If not, replace existing raw HTML with shortcodes or components and remove the leftover tags. In Jekyll, add markdown="1" to HTML blocks whose contents should be converted instead of hand-writing tags around them.

Step 6

Repair truncated summaries and excerpts

Check list pages, feeds and cards that show a shortened version of each post. Filters that cut rendered HTML by character or word count, such as a truncate filter applied to post content, can stop in the middle of an element and leave it open. Strip the HTML before truncating, use the generator's built-in summary or excerpt feature, or give each post a plain-text description in front matter and use that instead.

Step 7

Fail the build when new errors appear

Add the validator to your continuous integration pipeline so it runs after every build and fails the job on errors. Commit the validator configuration to the repository so everyone uses the same rules. Once the existing errors are cleared, keep the rule set strict, and rerun the HTML Checker on the deployed site from time to time to catch problems introduced by scripts or third-party embeds that the build folder does not contain.

A build check you can adapt

This GitHub Actions workflow builds a Hugo site and validates the output. The same pattern works for Jekyll, Eleventy or Astro by changing the build command and folder.

name: Validate HTML
on: [pull_request]
jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: peaceiris/actions-hugo@v3
        with:
          hugo-version: 'latest'
          extended: true
      - run: hugo --environment production
      - run: npx --yes html-validate "public/**/*.html"

If the site is large, validate a sample of pages on every pull request and the whole site on a schedule. Pin the validator version in package.json so an upgrade with new rules does not unexpectedly fail an unrelated change.

โš ๏ธ The build folder does not include anything added by JavaScript at runtime, such as comment widgets, consent banners or analytics tags. Errors from those only show up when you check the live pages with the HTML Checker.

Edge cases

Questions and answers

1. Why is my Hugo page showing a raw HTML omitted comment?

Since Hugo switched to the Goldmark Markdown renderer, raw HTML in Markdown files is removed by default for safety and replaced with that comment. If you trust your content authors, set markup.goldmark.renderer.unsafe to true in the site configuration and raw HTML will pass through. Otherwise, replace the HTML with a shortcode that outputs it. Check for leftover closing tags afterwards, because removing only part of an element's markup can leave a stray end tag.

2. Why does my shortcode output end up inside a p tag?

Because the Markdown converter sees the shortcode as part of a paragraph. That happens when the call sits on the same line as text or directly under a line of text without a blank line between them. Put block-level shortcodes on their own line, with a blank line above and below. In Hugo, also check the call style: the percent form sends the shortcode's output through the Markdown renderer, which can add paragraph tags around it.

3. Should I fix errors in the public or _site folder?

No. Those folders are rebuilt from your source files every time the generator runs, so any change you make there is lost on the next build. Use the generated files to find errors, then trace each one back to the layout, partial, shortcode or Markdown file that produced it and fix it there. It is worth adding the output folder to your editor's excluded files so nobody edits it by mistake.

4. Which validator should I use for a static site?

Either works well. html-validate runs on Node, is easy to add to projects that already use npm, and has configurable rules. The Nu HTML Checker, available as vnu.jar, is the engine behind the W3C online validator and gives the same messages, which makes its results easy to compare with the HTML Checker. Many teams run html-validate on every pull request and use the HTML Checker on the deployed site for the full picture.

5. Why is indented HTML in my Markdown showing as code?

In Markdown, any line indented by four or more spaces starts a code block. If you indent raw HTML to make it readable, part of it can be turned into escaped code, and the rest remains real HTML, leaving an element opened but never closed. Keep raw HTML blocks aligned with the left margin in Markdown files, or move complex markup into a shortcode, include or component where indentation does not matter.

6. Does minified HTML cause validation errors?

Usually not. Good minifiers only remove things HTML allows you to omit, such as optional end tags, some attribute quotes and whitespace. The result is valid, though some strict lint rules may complain about the style. The practical problem is that minified pages are one long line, so error locations become impossible to read. Validate an unminified build while you are fixing errors, and minify only the production build that you deploy.

7. Why do excerpts on my blog list page break the layout?

The excerpt was probably made by cutting the rendered HTML of each post to a fixed length. If the cut falls inside a link, list or emphasis element, the opening tag is kept but its closing tag is lost, and everything after it on the list page is swallowed into that element. Strip HTML before truncating, use the generator's own summary or excerpt feature, or write a short plain-text description in each post's front matter.

Check your generated pages

The HTML Checker validates your markup against W3C standards and reports errors and warnings covering the doctype, encoding, tag nesting, duplicate IDs, ARIA, forms and the language attribute, so you can fix each layout and partial at its source.

Open HTML Checker Fixes โ†’
๐Ÿ’ฌ Got a problem?
Related Guides: All HTML Checker Fixes  ·  Fixing Unclosed and Mismatched Tags  ·  HTML Errors in React and Next.js  ·  HTML Checker Guide  ·  Learning Hub

About aiwebpageseo

aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical SEO tools. Rather than relying on AI-theorised assumptions, the platform analyses live URL performance, delivering objective diagnostics, page speed metrics, CLS debugging, and site crawl data alongside actionable technical tutorials.