Force content rendering or use templates.Defer to read Page.Store in head?

A render hook writes a value to the page store:

{{ .Page.Store.Set "hasFeature" true }}

The page head reads that value to conditionally include CSS.

I have found two working approaches.

Approach 1: force content rendering before reading the store:

{{ $noop := .Content }}
{{ if .Store.Get "hasFeature" }}
  <link rel="stylesheet" href="...">
{{ end }}

This is similar to the pattern currently shown in the Hugo documentation,
where .WordCount is evaluated before Store.Get.

Approach 2: defer the consumer:

{{ $data := dict "page" . }}
{{ with templates.Defer (dict "data" $data) }}
  {{ if page.Store.Get "hasFeature" }}
    <link rel="stylesheet" href="...">
  {{ end }}
{{ end }}

Both approaches appear to work.

The first keeps the dependency local and seems easier to reason about.
Rendered content is cached, so a later .Content access should not perform
the full conversion again.

The second avoids explicitly rendering content from the head, but introduces
a deferred placeholder and final output rewriting. The documentation describes
templates.Defer as intended for rare use cases.

For a single page and output format, where render hooks populate Page.Store
and the head consumes it:

  1. Is either approach considered preferable?
  2. Are there correctness, concurrency, caching, server rebuild, or output-format
    differences that should influence the choice?
  3. Is templates.Defer primarily intended for values that cannot be complete
    until later in the site build, rather than this page-local dependency?

Approach 1. templates.Defer comes with a cost that you should avoid if you can.

By evaluating WordCount instead of Content, we reduce the amount of data allocated to the noop variable. Both methods force content rendering. See FAQ.

I suspect this is a noise-level optimization, but am not sure.

Thanks. I investigated this further because I was curious whether assigning
the smaller WordCount result to $noop reduces the overall cost.

In Hugo v0.164.0, WordCount calls contentPlain, which first calls
contentRendered and therefore populates the same rendered-content cache as
Content. It then additionally strips HTML, splits the plain text into words,
calculates word counts and reading time, and stores the result in the
plain-content cache.

My site does not otherwise use Len, WordCount, FuzzyWordCount,
Plain, PlainWords, or ReadingTime. Only the rendered Content is
consumed later by the page and comment templates.

I compared these variants:

{{ $noop := .Content }}
{{ $noop := .Len }}
{{ $noop := .WordCount }}

All three produced byte-identical public output.

With 60 balanced builds per variant on the current site:

Method Mean build time
Content 311.63 ms
Len 312.69 ms
WordCount 315.31 ms

The paired difference between Len and Content was not distinguishable from
noise: +1.07 ms, 95% CI −1.08 to +3.22 ms. WordCount was 3.69 ms slower than
Content, with a 95% CI of +1.86 to +5.52 ms.

To amplify the mechanism, I added 500 independent pages based on a 55.9 KiB
Chinese article. With 30 balanced builds per variant, Content averaged
596.82 ms, Len 598.38 ms, and WordCount 641.43 ms. Content and Len
remained indistinguishable, while WordCount was 44.62 ms slower.

This supports your noise-level assessment for the real site. Although Len
has the narrowest implementation path, it showed no measurable benefit over
Content. Because Content also communicates the intent directly and its
cached result is consumed later, I plan to keep it here.

For transparency, the source investigation and benchmark were designed and
executed by GPT-5.6 Sol in my local workspace. I reviewed the reported
methodology and conclusions, and have included the relevant setup and results
above for reproducibility.