Create a page layout
Use an existing site base template as your starting point. Put shared document structure in the base and page-specific markup in blocks.
{% extends "base.tpl" %}
{% block content %}
<main>
<h1>{_ Our garden _}</h1>
<p>{_ Find out what is growing this week. _}</p>
</main>
{% endblock %}
Check which blocks your chosen base provides before overriding one. Keep the standard head and body includes so active modules can contribute scripts, styles, metadata, and browser initialization. In a custom base, use tag#all_include with the standard include names instead of copying module-specific head fragments.
In a custom base, put {% all include "_html_head.tpl" %} inside <head>. Near the end of <body>, keep {% all include "_html_body.tpl" %}, the site's _js_include.tpl, and one final {% script %} in their established order. Wires collect browser code for that final script tag; without it a page can render correctly while its buttons do nothing.
Reuse markup with includes and categories
Extract repeated markup into a partial and pass its inputs explicitly.
{% include "_garden_card.tpl" id=id %}
Inside the partial, read the resource using m.rsc[id]. Keep the partial focused on one component so another page can reuse it without inheriting unrelated page behavior.
Use a category include when the representation depends on the resource's category:
{% catinclude "_garden_card.tpl" id %}
Provide a generic fallback and add category-specific variants when their markup differs. Use the template selection tools to check which variant wins for the resource. See tag#include, tag#catinclude, and Find the template used by a page.
For the example above, save the fallback as priv/templates/_garden_card.tpl. An event-specific version is _garden_card.event.tpl. A resource named garden_open_day can override both with _garden_card.name.garden_open_day.tpl. Test an event and an ordinary text resource: the latter should still use the fallback.
Display values safely
Resource properties read through m.rsc follow Zotonic's content handling. Query arguments and values returned by custom models do not automatically have the same guarantees.
Escape plain text from those sources:
<p>{{ q.term|escape }}</p>
Do not mark arbitrary user input as safe HTML. Decide at the model or content boundary whether a field contains text, sanitized HTML, a URL, or another type. Keep that decision consistent across templates and API responses.
When a value renders unexpectedly, inspect its type and translation behavior before applying more filters. See filter#escape, Expose data through a model, and Keep permission checks at the boundary.
Cache a template fragment
First measure the work performed by the fragment. Cache a fragment only when you know which inputs and data changes affect its output.
Check tag#cache for the supported duration, variation, and dependency arguments. A fragment containing a user's private information must not be shared between users through an incomplete cache key. Language and resource identity can also change the result.
Test a cache hit, then change the underlying content and check invalidation. During debugging, the Development page can disable template cache tags. Restore that setting before measuring normal page behavior. See Distinguish stale cache data from stale code and Choose checks for a change.
For a public resource card, a starting point is:
{% cache 60 garden_card vary=id vary=z_language if_anonymous %}
<a href="{{ id.page_url }}">{{ id.title }}</a>
{% endcache %}
The resource ID varies the entry and is a cache dependency; language separates translations. This example caches only anonymous requests. If the fragment also reads connected resources, add the corresponding dependencies. Rename a shared cache block when its output contract changes.