gohugoio/hugo · error
text is already rendered, repeating it may cause infinite re
Error message
text is already rendered, repeating it may cause infinite recursion
What it means
Raised in RenderString (page__content.go) when the value passed in is already of type hstring.HTML — i.e. output that Hugo's own render pipeline produced (such as the result of a render hook's .Text or a previous RenderString). Re-rendering already-rendered HTML is at best a no-op and at worst causes infinite recursion (a render hook calling RenderString on its own output), so Hugo refuses.
Source
Thrown at hugolib/page__content.go:912
m, ok := args[0].(map[string]any)
if !ok {
return "", errors.New("first argument must be a map")
}
if err := mapstructure.WeakDecode(m, &opts); err != nil {
return "", fmt.Errorf("failed to decode options: %w", err)
}
if opts.Markup != "" {
opts.Markup = markup.ResolveMarkup(opts.Markup)
}
}
contentToRenderv := args[sidx]
if _, ok := contentToRenderv.(hstring.HTML); ok {
// This content is already rendered, this is potentially
// a infinite recursion.
return "", errors.New("text is already rendered, repeating it may cause infinite recursion")
}
var err error
contentToRender, err = cast.ToStringE(contentToRenderv)
if err != nil {
return "", err
}
if err = pco.initRenderHooks(); err != nil {
return "", err
}
conv := pco.po.p.getContentConverter()
if opts.Markup != "" && opts.Markup != pco.po.p.m.pageConfigSource.ContentMediaType.SubType {
var err error
conv, err = pco.po.p.m.newContentConverter(pco.po.p, opts.Markup)
if err != nil {View on GitHub ↗ (pinned to 8a468df065)
Solutions
- Don't re-render: use the already-rendered value directly, e.g. `{{ .Text }}` in the render hook.
- If you genuinely need to re-process, work from the raw source instead — e.g. `.PlainText` in a render hook, or the original front-matter string — and pass that to RenderString.
- Convert explicitly only when you understand the recursion risk: `{{ .Page.RenderString (string $v) }}` forces plain-string handling.
Example fix
{{/* layouts/_markup/render-link.html before */}}
<a href="{{ .Destination }}">{{ .Page.RenderString .Text }}</a>
{{/* after */}}
<a href="{{ .Destination }}">{{ .Text }}</a> Defensive patterns
Strategy: validation
Validate before calling
{{/* never feed already-rendered content back into RenderString */}}
{{ $raw := .RawContent }}
{{ $out := .RenderString $raw }} {{/* OK: raw, not .Content */}} Prevention
- Pass raw/plain strings to RenderString, never .Content or another RenderString result
- In shortcodes, use .Inner or .InnerDeindent, not the page's rendered content
- Treat this error as a recursion guard: find where rendered output re-enters the pipeline
When it happens
Trigger: A markdown render hook (e.g. layouts/_markup/render-link.html) passing `.Text` (already-rendered HTML) into `.Page.RenderString`; templates piping the result of one RenderString/markdownify call into another RenderString.
Common situations: Render-hook authors trying to post-process link/heading text with RenderString; recursive shortcode/hook chains introduced when porting themes to newer Hugo versions where .Text became typed HTML; double-processing front-matter fields once through markdownify and again through RenderString.
Related errors
- circular call stack detected in partial %q
- can't apply myself (no turtles allowed)
- maximum template call stack size exceeded in %q
- unrecognized render hook template
- want 1 or 2 arguments
AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31).
Data as JSON: /data/errors/cf3e609e957a5b5e.json.
Report an issue: GitHub ↗.