Skip to content

fix(pagination): avoid inline partial name collision with Hugo - #2119

Merged
markdumay merged 2 commits into
mainfrom
fix/pagination-inline-partial-collision
Aug 4, 2026
Merged

fix(pagination): avoid inline partial name collision with Hugo#2119
markdumay merged 2 commits into
mainfrom
fix/pagination-inline-partial-collision

Conversation

@markdumay

Copy link
Copy Markdown
Collaborator

Pagination nondeterministically renders Hugo's default markup instead of the theme's, which also strips the pagination icon rules out of the purged CSS. Three-line rename.

The bug

layouts/_partials/assets/pagination.html defines its markup as inline partials named _partials/inline/pagination/{default,terse}. Hugo ships an embedded template named _partials/pagination.html — the names collide, and partial "inline/pagination/default" resolves to Hugo's embedded pagination template rather than ours on roughly 58% of builds.

When that happens the pagination renders as:

<a aria-disabled="true" aria-label="First" class="page-link" role="button" tabindex="-1">
  <span aria-hidden="true">&laquo;&laquo;</span></a>

instead of:

<a aria-disabled="true" aria-label="First page" class="page-link" role="button" tabindex="-1">
  <span aria-hidden="true"><svg class="svg-inline--fa bi bi-chevron-double-left" ...></span></a>

So the build silently loses both the themed icon and the translated aria-label (T "paginationFirst" → "First page" becomes a hardcoded "First").

Knock-on effect on purged CSS

Because the icon classes never appear in the rendered HTML, they never reach hugo_stats.json, so PurgeCSS correctly strips the matching rules:

.bi-chevron-double-left:before{content:"\f27f"}
.bi-chevron-double-right:before{content:"\f280"}

Net effect on a production site: the pagination first/last buttons ship with no glyph at all on a random ~58% of builds, and main.min.css changes hash between otherwise identical builds. That is how this was found — chasing a CSS hash that would not reproduce on gethinode.com.

The fix

Rename the namespace to _partials/inline/paginator/*, which has no embedded counterpart. sidebar/* and bundlev3/* are the only other namespaced inline partials in the theme, and neither collides with Hugo's embedded set (pagination, opengraph, twitter_cards, schema, disqus, google_analytics).

Verification

exampleSite, HUGO_PAGINATION_PAGERSIZE=2, checking /nl/blog/ for Hugo's &laquo; markup:

degraded builds
before 3 / 8
after 0 / 12

Independently on gethinode.com, which hits this on /docs/configuration/:

degraded builds
before 21 / 36
after 0 / 16

With the fix, main.min.css is byte-identical across 6 consecutive builds there; before it alternated between two hashes.

Also run: pnpm test (eslint + stylelint + markdownlint + template tests) → 0 issues; pnpm build:example → 149 pages EN/FR/NL, exit 0.

Ruled out

Recorded so nobody re-tests them: the dynamic printf partial name is not the cause (static if/else dispatch still degraded 7/10); there is no duplicate inline-partial definition; and GOMAXPROCS=1 still degrades (2/6), so it is not simply a parallelism race. Shadowing the embedded template with a site-level layouts/_partials/pagination.html also suppresses it (14/14) but is a workaround, not a fix.

Upstream

Hugo silently resolving a namespaced partial to an unrelated embedded template — nondeterministically, with no warning — looks like a Hugo defect in its own right, and is probably worth reporting separately. This PR is the theme-side fix.

🤖 Generated with Claude Code

The pagination markup is defined as inline partials named
`_partials/inline/pagination/{default,terse}`. Hugo ships an embedded
template named `_partials/pagination.html`, and that name collides:
`partial "inline/pagination/default"` resolves to Hugo's embedded
pagination template instead of ours on roughly 58% of builds.

When it does, pagination renders Hugo's default markup — bare `&laquo;`
glyphs and a hardcoded `aria-label="First"` — instead of the themed
icons and the translated label. The icon classes are then absent from
the rendered HTML, so they never reach `hugo_stats.json`, and PurgeCSS
correctly strips the matching rules. The result is a production build
that nondeterministically ships pagination buttons with no glyph, and a
main.min.css whose hash changes between otherwise identical builds.

Rename the namespace to `_partials/inline/paginator/*`, which has no
embedded counterpart. `sidebar/*` and `bundlev3/*` are the only other
namespaced inline partials and neither collides.

Measured on this exampleSite with `HUGO_PAGINATION_PAGERSIZE=2`:
3/8 builds degraded before, 0/12 after. On gethinode.com, which hits
this on /docs/configuration/: 21/36 degraded before, 0/16 after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@netlify

netlify Bot commented Aug 4, 2026

Copy link
Copy Markdown

Deploy Preview for gethinode-demo ready!

Name Link
🔨 Latest commit fb4b1aa
🔍 Latest deploy log https://app.netlify.com/projects/gethinode-demo/deploys/6a71bb40dcc4400008c53d13
😎 Deploy Preview https://deploy-preview-2119--gethinode-demo.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@markdumay
markdumay enabled auto-merge August 4, 2026 10:15
@markdumay
markdumay merged commit 2782a53 into main Aug 4, 2026
17 checks passed
@markdumay

Copy link
Copy Markdown
Collaborator Author

🎉 This PR is included in version 3.19.1 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@markdumay
markdumay deleted the fix/pagination-inline-partial-collision branch August 4, 2026 10:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant