skip to content

In production a template edit keeps rendering the old output while development shows it immediately — what explains the difference?

level: seniorimportance: should knowfreq 42%

answer

  1. compile once, execute per request
  2. keyed by resolved name and variant
  3. development re-checks the source
  4. packaged artifact is not the edited file
  5. first request per template pays compilation

basics

~20 s

Engines parse a template into an executable form once and cache it by resolved name for the process lifetime. Development configurations re-check the source or disable the cache; production keeps the compiled form until the process restarts.

solid answer

~40 s

Rendering has two phases: **compile** the template source into an executable form, and **execute** it against the model. Compilation is expensive relative to execution, so engines cache the compiled form keyed by the resolved template name — usually plus locale or variant. Development profiles either switch the cache off or stamp each entry with the source's modification time and re-check it per render, which is why edits appear instantly. Production keeps the entry for the process lifetime, and in many deployments the template lives inside a packaged artifact the running process never re-reads anyway, so a redeploy or restart is the intended refresh path. The same cache explains the latency spike on the first request per template after a deploy, and it is why template names must never be built from request input.

go deeper

for a junior

Recall that a template is compiled once and kept in memory, and that development setups reload it on change while deployed ones keep the compiled copy until restart.

for a middle

Explain the two phases and the cache key — resolved name plus locale or variant — and why per-render source checking is a development convenience rather than a default.

for a senior

Diagnose it end to end: separate a stale output cache from a stale compiled template, confirm which artifact the process actually reads, and account for the post-deploy cold-start spike.

for a principal

Weigh precompilation at build time against startup warming against hot-reloadable templates, and decide whether templates are deployed code or managed content with their own release path.

Template rendering is two phases with very different costs, and the cache between them explains most of the surprising behaviour teams hit in production. ## What is actually cached The engine turns template **source** into an **executable form** — a parse tree, a generated function, a compiled unit, depending on the engine — and keeps that form in memory under a key. The key is normally the resolved template name plus whatever dimensions took part in resolution: locale, theme, variant. What is *not* cached, in the ordinary configuration, is the rendered output: each request still executes the template against its own model. This matters for reasoning about failures. A stale **page** and a stale **template** are different problems with different fixes — an output cache in front of the application, versus a compiled-template entry inside it. ## Why development and production diverge | Setting | Development | Production | |---|---|---| | Compiled-template cache | off, or validated against the source each render | on for the process lifetime | | Source of templates | files on disk, edited in place | usually files inside the deployed artifact | | First-request cost | paid constantly, and unnoticed | paid once per template, visible after deploy | | Refresh path | save the file | redeploy or restart the process | So two mechanisms, not one, produce "my edit did nothing": the cache holds an already-compiled entry, and in a packaged deployment the file you edited may not even be the file the process reads. Both are intentional. A per-render source check costs a metadata lookup on every render of every template, which is exactly the kind of small constant you do not want in a hot path serving many requests. ## The production effects that follow - **Cold-start latency.** The first request to touch each template pays compilation. After a deploy, a scale-out, or a process restart, a burst of requests hits an empty cache at once, so the tail latency spike is concentrated rather than spread. Precompiling templates at build time, or warming the important ones at startup before the instance accepts traffic, removes it. - **Rolling deploys serve two versions.** The cache is per process. During a rollout some instances hold the old compiled form and some the new, so a user can see both within seconds. This is normally fine, and is worth remembering when a template change ships alongside a model change that the old template cannot render. - **Unbounded growth from dynamic names.** A cache keyed by a name derived from request input grows with the input space. Beyond the memory problem, a name built from untrusted input lets a caller aim resolution at templates you never intended to expose. Map request values onto a fixed allow-list of names. - **Negative lookups.** Whether a *failed* resolution is remembered varies. If misses are not cached, every request for a name that does not resolve walks the whole resolver chain and touches the filesystem, which turns a broken link into a measurable load source. - **Variant multiplication.** Keys that include locale and theme multiply entries by the number of supported combinations. It is rarely a problem, but it is the reason a "small" template set can hold thousands of compiled entries. ## Diagnosing it in the field 1. **Establish which layer is stale.** Request the page with caching intermediaries bypassed. If the old content survives that, the staleness is inside the application; if it disappears, an output cache or a reverse proxy owns it. 2. **Confirm what the process is reading.** Check whether the deployed artifact contains the edited template at all. Editing an extracted copy beside a running process is a common false lead. 3. **Check the profile.** Confirm whether the cache is enabled and whether source re-checking is configured, and confirm which profile the instance actually booted with rather than which one the repository defaults to. 4. **Restart deliberately.** A restart that fixes it confirms the compiled-template cache; a restart that does not sends you back to step one. ## The judgment call The cache is not a setting to "fix" — disabling it in production trades a per-render metadata check and repeated compilation for an editing convenience nobody has in a packaged deployment. The two legitimate moves are to **precompile at build time**, which converts the cost into build time and removes the cold-start spike entirely, and to **warm on startup**, which keeps runtime compilation but moves it before the instance takes traffic. Hot-reloading templates in production is a third option, occasionally chosen so content can change without a deploy; it means accepting a source check or an explicit invalidation path, and treating templates as deployable content with their own review trail.

  • Why does latency spike right after a deploy even though the code path is unchanged?
    Every instance starts with an empty compiled-template cache, so the first request touching each template pays parsing and compilation, and a rollout concentrates those first requests into a short window. Precompiling at build time, or rendering the important templates against a dummy model during startup before the instance is marked ready, moves that cost out of the traffic path.
  • Why must a template name never be built from request input?
    Two reasons that compound. The cache is keyed by resolved name, so an attacker-controlled name space grows the cache without bound. And resolution itself becomes steerable: the caller chooses which template renders, which can reach files that were never meant to be served. Map input onto a fixed set of known names and reject anything outside it.
  • How would you tell a stale compiled template from a stale cached page?
    Request the page with intermediaries bypassed and with cache-defeating request headers. If the stale content persists, it is being produced by the application, which points at the compiled-template entry; if fresh content appears, an output cache or a reverse proxy is serving it. A process restart that clears it confirms the in-process cache.

A compiled template is a printing plate cast once from the artwork. Editing the artwork on the shelf changes nothing that comes off the press until a new plate is cast.

saying these in an interview costs you the question

  • Assumes editing a template on a running server updates the rendered page
  • Believes the engine caches rendered pages rather than the compiled template
  • Wants to disable the template cache in production to make deploys simpler
  • Derives a template name from a request parameter and caches the result
  • Expects a rolling deploy to switch every instance to the new template at once