gohugoio/hugo · error
maximum template call stack size exceeded in
Error message
maximum template call stack size exceeded in %q
What it means
Hugo guards against infinite recursion in template execution by tracking the nesting level of partial/template calls. In ExecuteWithContextAndKey (templatestore.go:539-541) a hard threshold of 999 is enforced: each nested ExecuteWithContext increments the level via the CurrentTemplateInfo parent chain, and when it exceeds 999 the named template's filename is reported. This prevents stack overflows from mutually-recursive partials or templates that include themselves.
Solutions
- Identify the template named in the error and trace its include chain to find the cycle.
- Add a base-case guard in the recursive partial (e.g. {{ if .Pages }}...{{ partial "nav" . }}{{ end }} or pass a depth counter and stop recursing).
- Use Hugo's built-in constructs (range over .Pages) instead of manual recursion through partials.
- Temporarily enable template metrics/instrumentation to see which partial is invoked repeatedly.
Example fix
// before — recursive partial layouts/partials/nav.html
{{ partial "nav" . }}
// after — guard against nil/empty to terminate recursion
{{ with .Pages }}
{{ range . }}
{{ partial "nav" . }}
{{ end }}
{{ end }} Defensive patterns
Strategy: validation
Validate before calling
// In a recursive partial, always guard the recursion before calling partial
{{ with .Pages }}
{{ range . }}
{{ partial "nav" . }}
{{ end }}
{{ end }}
// Or pass a depth counter and stop at a safe limit
{{ if lt .Depth 50 }}
{{ partial "nav" (dict "node" .Node "depth" (add .Depth 1)) }}
{{ end }} Prevention
- Always include a base case (empty/nil check) before any recursive partial call.
- Prefer Hugo's range over nested data over manual recursion through partials.
- Lint templates for partials that reference themselves by name.
When it happens
Trigger: A template partial calls itself directly or transitively (e.g. partial A includes partial B which includes partial A), or a layout includes itself via {{ template "_default/list.html" . }}. Triggered during site build/render when ExecuteWithContextAndKey recurses past level 999.
Common situations: A recursive partial used to render a tree/menu structure (e.g. nav) without a base-case termination check; a baseof/layout that accidentally re-includes itself after a rename or refactor; upgrading Hugo versions where partial context passing changed and a guard condition now never hits nil.
Related errors
- failed to transform internal partial decorator template
- failed to add internal partial decorator template
- failed to create internal partial decorator template
- failed to find internal partial decorator template
- inner cannot be used inside a with block that wraps a…
AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09).
Data as JSON: /api/errors/59825a66bbef044d.
Report an issue: GitHub.
Appendix: source
Thrown at tpl/tplimpl/templatestore.go:541
templ := ti.Template
parent := tpl.Context.CurrentTemplate.Get(ctx)
var level int
if parent != nil {
level = parent.Level + 1
}
currentTi := &tpl.CurrentTemplateInfo{
Parent: parent,
Level: level,
Key: key,
CurrentTemplateInfoOps: ti,
}
ctx = tpl.Context.CurrentTemplate.Set(ctx, currentTi)
const levelThreshold = 999
if level > levelThreshold {
return fmt.Errorf("maximum template call stack size exceeded in %q", ti.Filename())
}
if t.opts.Metrics != nil {
defer t.opts.Metrics.MeasureSince(templ.Name(), time.Now())
}
execErr := t.storeSite.executer.ExecuteWithContext(ctx, ti, wr, data)
if execErr != nil {
return t.addFileContext(ti, "execute of template failed", execErr)
}
return nil
}
func (t *TemplateStore) GetFunc(name string) (reflect.Value, bool) {
v, found := t.storeSite.execHelper.funcs[name]
return v, found
}
View on GitHub (pinned to 52c9bd7908)