<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="https://cecil-945.pages.dev/xsl/atom.xsl" media="all"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <id>https://cecil-945.pages.dev/how-to/</id>
  <title>Cecil - How to?</title>
  <subtitle><![CDATA[Cecil is a command-line PHP application that merges Markdown pages, media and Twig templates to generate a static website.]]></subtitle>
  <link href="https://cecil-945.pages.dev/how-to/atom.xml" rel="self" type="application/atom+xml" />
  <link href="https://cecil-945.pages.dev/how-to/" rel="alternate" type="text/html" />
  <updated>2026-10-14T15:45:48+00:00</updated>
  <author>
    <name>Cecil</name>
    <uri>https://cecil-945.pages.dev/</uri>
  </author>
  <entry xml:lang="en">
    <id>https://cecil-945.pages.dev/how-to/markdown-toc-notes/</id>
    <title>Add a table of contents and notes</title>
    <published>2026-10-14T00:00:00+00:00</published>
    <link href="https://cecil-945.pages.dev/how-to/markdown-toc-notes/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>Cecil extends Markdown with a few handy features for long pages: an automatic table of contents, note blocks and attributes on elements.</p>
<h2 id="insert-a-table-of-contents">Insert a table of contents</h2>
<p>Add the <code translate="no">[toc]</code> tag where the table of contents should appear in the body of the page:</p>
<pre><code class="language-markdown hljs markdown" translate="no">[toc]

<span class="hljs-section">## Installation</span>
<span class="hljs-section">### Requirements</span>
<span class="hljs-section">## Usage</span></code></pre>
<p>By default, the table of contents is built from H2 and H3 headings. Change it in <code translate="no">cecil.yml</code>:</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-attr">pages:</span>
  <span class="hljs-attr">body:</span>
    <span class="hljs-attr">toc:</span> <span class="hljs-string">[h2,</span> <span class="hljs-string">h3,</span> <span class="hljs-string">h4]</span></code></pre>
<h2 id="display-the-table-of-contents-in-a-template">Display the table of contents in a template</h2>
<p>To place the table of contents outside the content (e.g. in a sidebar), use the <code translate="no">toc</code> filter on the page body:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">aside</span>&gt;</span>
  </span><span class="hljs-template-variable">{{ page.body|toc }}</span><span class="xml">
<span class="hljs-tag">&lt;/<span class="hljs-name">aside</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">article</span>&gt;</span>
  </span><span class="hljs-template-variable">{{ page.content }}</span><span class="xml">
<span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span></span></code></pre>
<p>The filter also accepts the headings to extract, e.g. <code translate="no">{{ page.body|toc(selectors=['h2']) }}</code>.</p>
<h2 id="add-notes">Add notes</h2>
<p>Highlight information with a note block:</p>
<pre><code class="language-markdown hljs markdown" translate="no">:::tip
<span class="hljs-strong">**Tip:**</span> This is advice.
:::</code></pre>
<p>Is converted to:</p>
<pre><code class="language-html hljs xml" translate="no"><span class="hljs-tag">&lt;<span class="hljs-name">aside</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"note note-tip"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>Tip:<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span> This is advice.
  <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">aside</span>&gt;</span></code></pre>
<p>Available types are <code translate="no">info</code>, <code translate="no">tip</code>, <code translate="no">important</code>, <code translate="no">warning</code> and <code translate="no">caution</code> (or no type). Style them with the <code translate="no">note</code> and <code translate="no">note-&lt;type&gt;</code> CSS classes.</p>
<h2 id="add-attributes">Add attributes</h2>
<p>Set an id, a class or any attribute on a heading, a fenced code block, a link or an image, inside curly brackets at the end of the line:</p>
<pre><code class="language-markdown hljs markdown" translate="no"><span class="hljs-section">## Installation {#install .highlighted}</span></code></pre>
<p>The <code translate="no">#install</code> id gives a stable anchor to link to: <code translate="no">[See installation](#install)</code>.</p>
<aside class="note note-warning"><p>For an inline element, like a link, add a line break after the closing brace.</p></aside>
<aside class="note note-info"><p>See the documentation of the <a href="/documentation/content/#table-of-contents">table of contents</a>, <a href="/documentation/content/#notes">notes</a>, <a href="/documentation/content/#attributes">attributes</a> and the <a href="/documentation/templates/#toc"><code translate="no">toc</code> filter</a>.</p></aside>]]>
    </content>
  </entry>
  <entry xml:lang="en">
    <id>https://cecil-945.pages.dev/how-to/content-redirect-alias/</id>
    <title>Redirect old URLs</title>
    <published>2026-10-13T00:00:00+00:00</published>
    <link href="https://cecil-945.pages.dev/how-to/content-redirect-alias/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>When a page moves or is renamed, its old URL should not lead to a 404 error. Cecil provides two front matter variables to handle this: <code translate="no">alias</code> and <code translate="no">redirect</code>.</p>
<h2 id="redirect-old-urls-to-a-page">Redirect old URLs to a page</h2>
<p>The <code translate="no">alias</code> variable lists the old paths of the current page. Each of them redirects to the page:</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-meta">---</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">About</span>
<span class="hljs-attr">alias:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">contact</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">about-us</span>
<span class="hljs-meta">---</span></code></pre>
<p>In this example, <code translate="no">/contact/</code> and <code translate="no">/about-us/</code> redirect to <code translate="no">/about/</code>.</p>
<h2 id="redirect-a-page-to-another-url">Redirect a page to another URL</h2>
<p>The <code translate="no">redirect</code> variable turns a page into a redirection to the given URL, internal or external:</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-meta">---</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">Old</span> <span class="hljs-string">documentation</span>
<span class="hljs-attr">redirect:</span> <span class="hljs-string">"https://cecil.app/documentation/"</span>
<span class="hljs-meta">---</span></code></pre>
<h2 id="without-a-markdown-file">Without a Markdown file</h2>
<p>If the old page has no content anymore, declare it as a <a href="/documentation/configuration/#pages-virtual">virtual page</a> in <code translate="no">cecil.yml</code>:</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-attr">pages:</span>
  <span class="hljs-attr">virtual:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">path:</span> <span class="hljs-string">code</span>
      <span class="hljs-attr">redirect:</span> <span class="hljs-string">https://github.com/Cecilapp/Cecil</span></code></pre>
<h2 id="generated-pages">Generated pages</h2>
<p>For each alias and each redirection, Cecil renders a page with the built-in <a href="https://github.com/Cecilapp/Cecil/blob/main/resources/layouts/_default/redirect.html.twig" target="_blank" rel="noopener noreferrer"><code translate="no">redirect.html.twig</code></a> template. It contains:</p>
<ul>
<li>a <code translate="no">&lt;link rel="canonical"&gt;</code> to the target URL</li>
<li>a <code translate="no">&lt;meta name="robots" content="noindex"&gt;</code> tag, so search engines don’t index it</li>
<li>a <code translate="no">&lt;meta http-equiv="refresh"&gt;</code> tag and a JavaScript redirection</li>
<li>a fallback link for visitors</li>
</ul>
<aside class="note note-tip"><p>To customize this page, create your own <code translate="no">layouts/redirect.html.twig</code> template: it takes precedence over the built-in one.</p></aside>
<aside class="note note-info"><p>See the documentation of the <a href="/documentation/content/#redirect"><code translate="no">redirect</code></a> and <a href="/documentation/content/#alias"><code translate="no">alias</code></a> variables.</p></aside>]]>
    </content>
  </entry>
  <entry xml:lang="en">
    <id>https://cecil-945.pages.dev/how-to/config-environments/</id>
    <title>Use different settings per environment</title>
    <published>2026-10-12T00:00:00+00:00</published>
    <link href="https://cecil-945.pages.dev/how-to/config-environments/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>Your local site and your production site rarely share the same settings: base URL, debug mode, output directory… Cecil lets you keep a single <code translate="no">cecil.yml</code> and override it per environment.</p>
<h2 id="keep-production-values-in-cecil-yml">Keep production values in <code translate="no">cecil.yml</code></h2>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-attr">title:</span> <span class="hljs-string">"My site"</span>
<span class="hljs-attr">baseurl:</span> <span class="hljs-string">https://example.com/</span>
<span class="hljs-attr">debug:</span> <span class="hljs-literal">false</span></code></pre>
<h2 id="override-with-extra-config-files">Override with extra config files</h2>
<p>Create a file with only the keys to change, for example <code translate="no">config/dev.yml</code>:</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-attr">baseurl:</span> <span class="hljs-string">http://localhost:8000/</span>
<span class="hljs-attr">debug:</span> <span class="hljs-literal">true</span></code></pre>
<p>Then load it with the <code translate="no">--config</code> option, available with the <code translate="no">build</code> and <code translate="no">serve</code> commands:</p>
<pre><code class="language-bash hljs bash" translate="no">php cecil.phar serve --config=config/dev.yml</code></pre>
<p>You can pass several files, separated by commas. They are merged in order after <code translate="no">cecil.yml</code>, so each file overrides the previous ones:</p>
<pre><code class="language-bash hljs bash" translate="no">php cecil.phar build --config=config/prod.yml,config/local.yml</code></pre>
<h2 id="override-with-environment-variables">Override with environment variables</h2>
<p>Any configuration key can be set with an environment variable prefixed with <code translate="no">CECIL_</code>, in uppercase. Nested keys are separated with an underscore (e.g. <code translate="no">CECIL_OUTPUT_DIR</code> sets <code translate="no">output.dir</code>):</p>
<pre><code class="language-bash hljs bash" translate="no"><span class="hljs-built_in">export</span> CECIL_BASEURL=<span class="hljs-string">"https://staging.example.com/"</span>
<span class="hljs-built_in">export</span> CECIL_DEBUG=<span class="hljs-literal">true</span>
php cecil.phar build</code></pre>
<p>Cecil also loads a <code translate="no">.env</code> file from the site root, if it exists (variables already defined by the system are preserved):</p>
<pre><code class="language-dotenv" translate="no">CECIL_BASEURL="http://localhost:8000/"
CECIL_DEBUG=true</code></pre>
<p>Environment variables are applied on top of the configuration files, which makes them handy in CI, for example:</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-comment"># .gitlab-ci.yml</span>
<span class="hljs-attr">pages:</span>
  <span class="hljs-attr">variables:</span>
    <span class="hljs-attr">CECIL_ENV:</span> <span class="hljs-string">production</span>
    <span class="hljs-attr">CECIL_OUTPUT_DIR:</span> <span class="hljs-string">public</span></code></pre>
<h2 id="read-the-environment-in-templates">Read the environment in templates</h2>
<p>Use the <code translate="no">getenv</code> function to adapt templates, for example to include analytics in production only:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> getenv('CECIL_ENV') == 'production' %}</span><span class="xml">
  </span><span class="hljs-template-variable">{{ <span class="hljs-name">include</span><span class="hljs-params">('partials/analytics.html.twig')</span> }}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span></code></pre>
<p>The <code translate="no">site.debug</code> variable tells you if debug mode is enabled:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> site.debug %}</span><span class="hljs-template-variable">{{ <span class="hljs-name">dump</span><span class="hljs-params">(page)</span> }}</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span></code></pre>
<aside class="note note-info"><p>See the documentation for <a href="/documentation/configuration/#override-configuration">overriding the configuration</a>, the <a href="/documentation/configuration/#debug"><code translate="no">debug</code> option</a> and the <a href="/documentation/templates/#getenv"><code translate="no">getenv</code> function</a>.</p></aside>]]>
    </content>
  </entry>
  <entry xml:lang="en">
    <id>https://cecil-945.pages.dev/how-to/templates-assets-scss/</id>
    <title>Compile, minify and fingerprint CSS and JavaScript</title>
    <published>2026-10-11T00:00:00+00:00</published>
    <link href="https://cecil-945.pages.dev/how-to/templates-assets-scss/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>Cecil can compile <a href="https://sass-lang.com" target="_blank" rel="noopener noreferrer">Sass</a> files, minify CSS and JavaScript, and add a content hash to file names (cache busting) directly from your templates, without any external build tool.</p>
<h2 id="configure-assets-processing">Configure assets processing</h2>
<p>Source files are stored in the <code translate="no">assets/</code> directory. Processing is enabled by default and can be tuned in <code translate="no">cecil.yml</code>:</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-attr">assets:</span>
  <span class="hljs-attr">compile:</span>
    <span class="hljs-attr">style:</span> <span class="hljs-string">compressed</span>    <span class="hljs-comment"># `expanded` or `compressed`</span>
    <span class="hljs-attr">import:</span> <span class="hljs-string">[sass,</span> <span class="hljs-string">scss]</span> <span class="hljs-comment"># imported paths</span>
  <span class="hljs-attr">minify:</span> <span class="hljs-literal">true</span>           <span class="hljs-comment"># minifies CSS and JS</span>
  <span class="hljs-attr">fingerprint:</span> <span class="hljs-literal">true</span>      <span class="hljs-comment"># adds content hash to file names</span></code></pre>
<h2 id="load-a-stylesheet">Load a stylesheet</h2>
<p>Create an <em>asset</em> from a Sass file, compile it with <code translate="no">to_css</code> and render the <code translate="no">&lt;link&gt;</code> element with the <code translate="no">html</code> function:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="hljs-template-variable">{{ html(asset('css/styles.scss')|to_css|minify) }}</span></code></pre>
<p>Filters can be chained to control each step explicitly, which is useful if a feature is disabled globally:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="hljs-template-variable">{{ asset('css/styles.scss')|to_css|minify|fingerprint }}</span></code></pre>
<h2 id="bundle-javascript-files">Bundle JavaScript files</h2>
<p>Pass an array of paths to create a single bundle, with a custom file name:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="hljs-template-variable">{{ html(asset(['js/menu.js', 'js/search.js'], {filename: 'scripts.js'})) }}</span></code></pre>
<p>Options can also be set per asset, for example to keep a remote file untouched:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="hljs-template-variable">{{ html(asset('https://cdnjs.cloudflare.com/ajax/libs/anchor-js/4.3.1/anchor.<span class="hljs-name">min</span>.js', {minify: false})) }}</span></code></pre>
<h2 id="add-subresource-integrity">Add Subresource Integrity</h2>
<p>Use the <code translate="no">integrity</code> function (or the <code translate="no">integrity</code> attribute of the asset) to generate a <code translate="no">sha384</code> hash:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">set</span></span> styles = asset('css/styles.scss')|to_css %}</span><span class="xml">
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ url(styles) }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span> <span class="hljs-attr">integrity</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ integrity(styles) }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span> <span class="hljs-attr">crossorigin</span>=<span class="hljs-string">"anonymous"</span>&gt;</span></span></code></pre>
<h2 id="inline-critical-css">Inline critical CSS</h2>
<p>Output the content of an asset with the <code translate="no">inline</code> filter:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span></span><span class="hljs-template-variable">{{ asset('css/critical.scss')|to_css|minify|inline }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></span></code></pre>
<p>For a few lines of styles written directly in the template, use the <code translate="no">scss_to_css</code> and <code translate="no">minify_css</code> filters:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span>
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">apply</span></span> scss_to_css|minify_css %}</span><span class="xml">
  $color: #fcfcfc;
  body {
    background-color: $color;
  }
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endapply</span></span> %}</span><span class="xml">
<span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></span></code></pre>
<aside class="note note-info"><p>See the documentation of the <a href="/documentation/templates/#asset"><code translate="no">asset</code> function</a>, the <a href="/documentation/templates/#filters">filters</a> and the <a href="/documentation/configuration/#assets">assets configuration</a>.</p></aside>]]>
    </content>
  </entry>
  <entry xml:lang="en">
    <id>https://cecil-945.pages.dev/how-to/content-taxonomies/</id>
    <title>Organize pages with tags and categories</title>
    <published>2026-10-10T00:00:00+00:00</published>
    <link href="https://cecil-945.pages.dev/how-to/content-taxonomies/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>Taxonomies let you classify pages with terms (e.g. <code translate="no">PHP</code>) grouped in vocabularies (e.g. <code translate="no">tags</code>). Cecil then generates a page per vocabulary and a page per term.</p>
<h2 id="declare-vocabularies">Declare vocabularies</h2>
<p>Vocabularies are declared in <code translate="no">cecil.yml</code>, paired by plural and singular names:</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-attr">taxonomies:</span>
  <span class="hljs-attr">categories:</span> <span class="hljs-string">category</span>
  <span class="hljs-attr">tags:</span> <span class="hljs-string">tag</span></code></pre>
<aside class="note note-warning"><p>There are no default vocabularies: you must declare them to use them.</p></aside>
<h2 id="classify-pages">Classify pages</h2>
<p>Add terms in the front matter of your pages, using the plural name of the vocabulary:</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-meta">---</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">My</span> <span class="hljs-string">first</span> <span class="hljs-string">post</span>
<span class="hljs-attr">categories:</span> <span class="hljs-string">["Development"]</span>
<span class="hljs-attr">tags:</span> <span class="hljs-string">["PHP",</span> <span class="hljs-string">"Static site"</span><span class="hljs-string">]</span>
<span class="hljs-meta">---</span></code></pre>
<p>Cecil generates:</p>
<ul>
<li><code translate="no">/tags/</code>: the list of the terms of the vocabulary</li>
<li><code translate="no">/tags/php/</code> and <code translate="no">/tags/static-site/</code>: the list of the pages of each term</li>
</ul>
<h2 id="customize-the-list-of-terms">Customize the list of terms</h2>
<p>The <em>vocabulary</em> template is named after the <strong>plural</strong>, e.g. <code translate="no">layouts/taxonomy/tags.html.twig</code>:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">extends</span></span> 'page.html.twig' %}</span><span class="xml">

</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">block</span></span> content %}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span></span><span class="hljs-template-variable">{{ page.title }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">for</span></span> term in page.terms %}</span><span class="xml">
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ url(term.id) }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ term.name }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span> (</span><span class="hljs-template-variable">{{ term|<span class="hljs-keyword">length</span> }}</span><span class="xml">)<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> %}</span><span class="xml">
  <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endblock</span></span> %}</span></code></pre>
<h2 id="customize-the-pages-of-a-term">Customize the pages of a term</h2>
<p>The <em>term</em> template is named after the <strong>singular</strong>, e.g. <code translate="no">layouts/taxonomy/tag.html.twig</code>:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">extends</span></span> 'page.html.twig' %}</span><span class="xml">

</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">block</span></span> content %}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span></span><span class="hljs-template-variable">{{ page.title }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">for</span></span> p in page.paginator.pages ?? page.pages %}</span><span class="xml">
    <span class="hljs-tag">&lt;<span class="hljs-name">article</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ url(p) }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ p.title }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> %}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ url(page.plural) }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span>All </span><span class="hljs-template-variable">{{ page.plural }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endblock</span></span> %}</span></code></pre>
<h2 id="display-the-terms-of-a-page">Display the terms of a page</h2>
<p>In a page template, link each term to its page:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">for</span></span> tag in page.tags ?? [] %}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ url('tags/' ~ tag) }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ tag }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> %}</span></code></pre>
<p>Or use the built-in partial: <code translate="no">{{ include('partials/terms-list.html.twig', {vocabulary: 'tags'}) }}</code>.</p>
<aside class="note note-info"><p>See the documentation of <a href="/documentation/configuration/#taxonomies">taxonomies configuration</a>, <a href="/documentation/templates/#type-vocabulary">templates lookup rules</a> and <a href="/documentation/templates/#page">taxonomy variables</a>.</p></aside>]]>
    </content>
  </entry>
  <entry xml:lang="en">
    <id>https://cecil-945.pages.dev/how-to/templates-menus/</id>
    <title>Build a navigation menu</title>
    <published>2026-10-09T00:00:00+00:00</published>
    <link href="https://cecil-945.pages.dev/how-to/templates-menus/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>Cecil manages navigation menus as collections of entries (name, URL, weight) that you can declare in the configuration or directly in pages, then render anywhere in your templates.</p>
<h2 id="declare-entries-in-configuration">Declare entries in configuration</h2>
<p>Add a <code translate="no">menus</code> section in <code translate="no">cecil.yml</code>. Each menu (e.g.: <code translate="no">main</code>, <code translate="no">footer</code>) is a list of entries with a unique <code translate="no">id</code>:</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-attr">menus:</span>
  <span class="hljs-attr">main:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">about</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">"About"</span>
      <span class="hljs-attr">url:</span> <span class="hljs-string">/about/</span>
      <span class="hljs-attr">weight:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">footer:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">github</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">"GitHub"</span>
      <span class="hljs-attr">url:</span> <span class="hljs-string">https://github.com/Cecilapp/Cecil</span>
      <span class="hljs-attr">weight:</span> <span class="hljs-number">99</span></code></pre>
<aside class="note note-info"><p>A <code translate="no">main</code> menu is automatically created with the home page entry and all sections entries.</p></aside>
<h2 id="add-a-page-from-its-front-matter">Add a page from its front matter</h2>
<p>A page can add itself to one or more menus with the <code translate="no">menu</code> variable. The entry name is the page <code translate="no">title</code> and the URL is the page path:</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-meta">---</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">Our</span> <span class="hljs-string">Expertise</span>
<span class="hljs-attr">menu:</span>
  <span class="hljs-attr">main:</span>
    <span class="hljs-attr">weight:</span> <span class="hljs-number">15</span>
  <span class="hljs-attr">footer:</span>
    <span class="hljs-attr">weight:</span> <span class="hljs-number">15</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">"Expertise"</span> <span class="hljs-comment"># override the entry name in this menu</span>
<span class="hljs-meta">---</span></code></pre>
<h2 id="override-or-disable-an-entry">Override or disable an entry</h2>
<p>Use the page ID as <code translate="no">id</code> to override an existing entry, or <code translate="no">enabled: false</code> to remove it:</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-attr">menus:</span>
  <span class="hljs-attr">main:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">index</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">"Home"</span>
      <span class="hljs-attr">weight:</span> <span class="hljs-number">1</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">about</span>
      <span class="hljs-attr">enabled:</span> <span class="hljs-literal">false</span></code></pre>
<h2 id="render-the-menu">Render the menu</h2>
<p>Loop over <code translate="no">site.menus.&lt;menu&gt;</code>, sorted by weight, and compare each entry ID with the current page ID to highlight the active item:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">nav</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">for</span></span> entry in site.menus.main|sort_by_weight %}</span><span class="xml">
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ url(entry.url) }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span></span></span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> entry.id == page.id %}</span><span class="xml"><span class="hljs-tag"> <span class="hljs-attr">aria-current</span>=<span class="hljs-string">"page"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"active"</span></span></span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span><span class="xml"><span class="hljs-tag">&gt;</span></span><span class="hljs-template-variable">{{ entry.name }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> %}</span><span class="xml">
  <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span></span></code></pre>
<p>A single entry can also be reached by its ID:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> site.menus.main.about is defined %}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ url(site.menus.main.about.url) }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ site.menus.main.about.name }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span></code></pre>
<aside class="note note-tip"><p>Cecil also provides a built-in partial: <code translate="no">{{ include('partials/navigation.html.twig', {menu: 'main'}) }}</code>.</p></aside>
<aside class="note note-info"><p>See the documentation of the <a href="/documentation/configuration/#menus">menus configuration</a>, the <a href="/documentation/content/#menu"><code translate="no">menu</code> front matter variable</a> and the <a href="/documentation/templates/#site"><code translate="no">site.menus</code> variable</a>.</p></aside>]]>
    </content>
  </entry>
  <entry xml:lang="en">
    <id>https://cecil-945.pages.dev/how-to/output-search-index/</id>
    <title>Add client-side search</title>
    <published>2026-10-08T00:00:00+00:00</published>
    <link href="https://cecil-945.pages.dev/how-to/output-search-index/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>A static site has no server-side search, but Cecil can generate a <strong>JSON index of your pages</strong> at build time. A few lines of JavaScript are then enough to search it in the browser.</p>
<h2 id="create-the-index-page">Create the index page</h2>
<p>Create a page whose only <a href="/documentation/content/#output">output format</a> is <code translate="no">json</code>, with a dedicated layout:</p>
<p><em>pages/search.md</em></p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-meta">---</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">Search</span> <span class="hljs-string">index</span>
<span class="hljs-attr">layout:</span> <span class="hljs-string">search</span>
<span class="hljs-attr">output:</span> <span class="hljs-string">json</span>
<span class="hljs-attr">excluded:</span> <span class="hljs-literal">true</span>
<span class="hljs-meta">---</span></code></pre>
<p><a href="/documentation/content/#excluded"><code translate="no">excluded: true</code></a> keeps this technical page out of list pages and the sitemap.</p>
<h2 id="write-the-json-template">Write the JSON template</h2>
<p>Following the <a href="/documentation/templates/#lookup-rules">lookup rules</a>, Cecil renders this page with <code translate="no">layouts/search.json.twig</code>. Build an array of the <a href="/documentation/templates/#site">showable</a> pages, then encode it:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="hljs-template-tag">{%- <span class="hljs-name"><span class="hljs-keyword">set</span></span> index = [] -%}</span><span class="xml">
</span><span class="hljs-template-tag">{%- <span class="hljs-name"><span class="hljs-keyword">for</span></span> p in site.pages.showable|<span class="hljs-keyword">filter</span>(p =&gt; p.type == 'page') -%}</span><span class="xml">
  </span><span class="hljs-template-tag">{%- <span class="hljs-name"><span class="hljs-keyword">set</span></span> index = index|<span class="hljs-keyword">merge</span>([{
    title: p.title,
    url: url(p),
    description: p.description|<span class="hljs-keyword">default</span>(''),
    content: p.content|<span class="hljs-keyword">striptags</span>|excerpt(300)
  }]) -%}</span><span class="xml">
</span><span class="hljs-template-tag">{%- <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> -%}</span><span class="xml">
</span><span class="hljs-template-variable">{{ index|<span class="hljs-keyword">json_encode</span>(<span class="hljs-name">constant</span><span class="hljs-params">('JSON_UNESCAPED_UNICODE')</span> b-or <span class="hljs-name">constant</span><span class="hljs-params">('JSON_UNESCAPED_SLASHES')</span>)|<span class="hljs-keyword">raw</span> }}</span></code></pre>
<p>After <code translate="no">php cecil.phar build</code>, the index is available at <code translate="no">/search.json</code>.</p>
<aside class="note note-info"><p>See the <a href="/documentation/content/#output"><code translate="no">output</code> variable</a>, the <a href="/documentation/configuration/#output-formats">output formats</a> and the <a href="/documentation/templates/#url"><code translate="no">url</code> function</a> documentation.</p></aside>
<h2 id="search-with-fuse-js">Search with Fuse.js</h2>
<p>Add a search field to a template and load <a href="https://www.fusejs.io" target="_blank" rel="noopener noreferrer">Fuse.js</a> from a CDN to query the index:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"search"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"search"</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Search…"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"results"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">import</span> Fuse <span class="hljs-keyword">from</span> <span class="hljs-string">'https://cdn.jsdelivr.net/npm/fuse.js@7/dist/fuse.mjs'</span>;

  <span class="hljs-keyword">const</span> index = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'</span></span></span><span class="hljs-template-variable">{{ url('search', {format: 'json'}) }}</span><span class="xml">').then(r =&gt; r.json());
  const fuse = new Fuse(index, { keys: ['title', 'description', 'content'], threshold: 0.3 });
  const results = document.getElementById('results');

  document.getElementById('search').addEventListener('input', (e) =&gt; {
    results.replaceChildren(...fuse.search(e.target.value, { limit: 10 }).map(({ item }) =&gt; {
      const li = document.createElement('li');
      const a = document.createElement('a');
      a.href = item.url;
      a.textContent = item.title;
      li.append(a);
      return li;
    }));
  });
<span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span></code></pre>
<h2 id="alternative-pagefind">Alternative: Pagefind</h2>
<p><a href="https://pagefind.app" target="_blank" rel="noopener noreferrer">Pagefind</a> indexes the generated HTML files instead: no JSON template is needed. Run it after the build, on the output directory (<code translate="no">_site</code> by default):</p>
<pre><code class="language-bash hljs bash" translate="no">php cecil.phar build
npx pagefind --site _site</code></pre>
<p>Then load its UI in a template:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/pagefind/pagefind-ui.css"</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/pagefind/pagefind-ui.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"search"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'DOMContentLoaded'</span>, () =&gt; <span class="hljs-keyword">new</span> PagefindUI({ <span class="hljs-attr">element</span>: <span class="hljs-string">'#search'</span> }));
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span></code></pre>
<aside class="note note-tip"><p>The index is a regular page: to regenerate only JSON outputs, declare a <a href="/documentation/configuration/#pages-subsets">subset</a> and run <code translate="no">php cecil.phar build --render-subset=&lt;name&gt;</code>.</p></aside>]]>
    </content>
  </entry>
  <entry xml:lang="en">
    <id>https://cecil-945.pages.dev/how-to/deploy-github-pages/</id>
    <title>Deploy to GitHub Pages with GitHub Actions</title>
    <published>2026-10-07T00:00:00+00:00</published>
    <link href="https://cecil-945.pages.dev/how-to/deploy-github-pages/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>With the <a href="https://github.com/Cecilapp/Cecil-Action" target="_blank" rel="noopener noreferrer">Cecil Action</a>, every push to your repository builds the site and publishes it to <strong>GitHub Pages</strong>, with no server to manage.</p>
<h2 id="enable-github-pages">Enable GitHub Pages</h2>
<p>In your repository, go to <strong>Settings</strong> → <strong>Pages</strong> and, under <strong>Build and deployment</strong>, set <strong>Source</strong> to <strong>GitHub Actions</strong>.</p>
<h2 id="add-the-workflow">Add the workflow</h2>
<p>Create the file <code translate="no">.github/workflows/build-and-deploy.yml</code>:</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">and</span> <span class="hljs-string">deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">GitHub</span> <span class="hljs-string">Pages</span>
<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span> <span class="hljs-string">[master,</span> <span class="hljs-string">main]</span>
  <span class="hljs-attr">workflow_dispatch:</span>
<span class="hljs-attr">concurrency:</span>
  <span class="hljs-attr">group:</span> <span class="hljs-string">pages</span>
  <span class="hljs-attr">cancel-in-progress:</span> <span class="hljs-literal">true</span>
<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">source</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v6</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">site</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">Cecilapp/Cecil-Action@v4</span>
  <span class="hljs-attr">deploy:</span>
    <span class="hljs-attr">needs:</span> <span class="hljs-string">build</span>
    <span class="hljs-attr">permissions:</span>
      <span class="hljs-attr">pages:</span> <span class="hljs-string">write</span>
      <span class="hljs-attr">id-token:</span> <span class="hljs-string">write</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">github-pages</span>
      <span class="hljs-attr">url:</span> <span class="hljs-string">${{</span> <span class="hljs-string">steps.deployment.outputs.page_url</span> <span class="hljs-string">}}</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">GitHub</span> <span class="hljs-string">Pages</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">deployment</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/deploy-pages@v5</span></code></pre>
<p>The <code translate="no">build</code> job downloads Cecil, installs themes (if a <code translate="no">composer.json</code> file exists), builds the site and uploads the output directory as a Pages artifact. The <code translate="no">deploy</code> job then publishes it.</p>
<h2 id="base-url">Base URL</h2>
<p>You don’t need to change <code translate="no">baseurl</code> in <code translate="no">cecil.yml</code>: the action builds the site with the URL provided by GitHub Pages (e.g. <code translate="no">https://&lt;user&gt;.github.io/&lt;repository&gt;/</code>), using the <code translate="no">--baseurl</code> option.</p>
<h2 id="customize-the-build">Customize the build</h2>
<p>The action accepts the following optional inputs:</p>
<pre><code class="language-yaml hljs yaml" translate="no">      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">site</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">Cecilapp/Cecil-Action@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">version:</span> <span class="hljs-string">'9.6.2'</span>       <span class="hljs-comment"># Cecil version (latest by default)</span>
          <span class="hljs-attr">install_themes:</span> <span class="hljs-string">'no'</span>   <span class="hljs-comment"># skip themes installation (`yes` by default)</span>
          <span class="hljs-attr">options:</span> <span class="hljs-string">'-v --drafts'</span> <span class="hljs-comment"># build command options (`-v` by default)</span></code></pre>
<aside class="note note-tip"><p>To speed up builds, you can also restore and save the <code translate="no">.cache</code> directory between runs: see the full workflow in the <a href="/documentation/deploy/#github-pages">GitHub Pages deployment documentation</a>, and the list of <a href="/documentation/commands/#build">build options</a>.</p></aside>]]>
    </content>
  </entry>
  <entry xml:lang="en">
    <id>https://cecil-945.pages.dev/how-to/output-rss-json-feed/</id>
    <title>Publish an RSS or JSON feed</title>
    <published>2026-10-06T00:00:00+00:00</published>
    <link href="https://cecil-945.pages.dev/how-to/output-rss-json-feed/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>Cecil renders feeds with <strong>output formats</strong>: by default, the home page, sections and taxonomy terms are already published in HTML <strong>and Atom</strong> (e.g. <code translate="no">/atom.xml</code>, <code translate="no">/blog/atom.xml</code>). Add the <code translate="no">rss</code> or <code translate="no">jsonfeed</code> formats to publish RSS 2.0 or <a href="https://www.jsonfeed.org" target="_blank" rel="noopener noreferrer">JSON Feed</a> files too.</p>
<h2 id="enable-feeds-for-all-list-pages">Enable feeds for all list pages</h2>
<p>Set the formats applied to each page type with <code translate="no">output.pagetypeformats</code>:</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-attr">output:</span>
  <span class="hljs-attr">pagetypeformats:</span>
    <span class="hljs-attr">homepage:</span> <span class="hljs-string">[html,</span> <span class="hljs-string">atom,</span> <span class="hljs-string">rss,</span> <span class="hljs-string">jsonfeed]</span>
    <span class="hljs-attr">section:</span> <span class="hljs-string">[html,</span> <span class="hljs-string">atom,</span> <span class="hljs-string">rss,</span> <span class="hljs-string">jsonfeed]</span></code></pre>
<p>Cecil now generates <code translate="no">rss.xml</code> and <code translate="no">feed.json</code> next to each <code translate="no">index.html</code> of the home page and sections (e.g. <code translate="no">/blog/rss.xml</code>, <code translate="no">/blog/feed.json</code>).</p>
<aside class="note note-info"><p>Formats are replaced, not merged: keep <code translate="no">html</code> in the list. See <a href="/documentation/configuration/#output-pagetypeformats"><code translate="no">output.pagetypeformats</code></a> and the list of <a href="/documentation/configuration/#output-formats">default formats</a>.</p></aside>
<h2 id="enable-a-feed-for-a-single-section">Enable a feed for a single section</h2>
<p>To publish a feed only for one section, use the <a href="/documentation/content/#output"><code translate="no">output</code></a> variable in the front matter of the section index page:</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-meta">---</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">Blog</span>
<span class="hljs-attr">output:</span> <span class="hljs-string">[html,</span> <span class="hljs-string">rss]</span>
<span class="hljs-meta">---</span></code></pre>
<h2 id="advertise-the-feed">Advertise the feed</h2>
<p>If your templates include the <a href="/documentation/configuration/#metatags">metatags partial</a>, <code translate="no">&lt;link rel="alternate"&gt;</code> tags pointing to the feeds of the current page are added automatically in the <code translate="no">&lt;head&gt;</code>.</p>
<p>Otherwise, add the link yourself with the <code translate="no">url()</code> function and its <code translate="no">format</code> option:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"alternate"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"application/rss+xml"</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ site.title }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ url(page, {canonical: true, format: 'rss'}) }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span></code></pre>
<h2 id="customize-the-feed-template">Customize the feed template</h2>
<p>Feeds are rendered by the <a href="/documentation/templates/#built-in-templates">built-in templates</a> <code translate="no">_default/list.rss.twig</code>, <code translate="no">_default/list.atom.twig</code> and <code translate="no">_default/list.jsonfeed.twig</code>. Following the <a href="/documentation/templates/#lookup-rules">lookup rules</a>, create <code translate="no">layouts/list.rss.twig</code> (all list pages) or <code translate="no">layouts/blog/list.rss.twig</code> (<code translate="no">blog</code> section only) to override them.</p>
<p>You can extend the built-in template and redefine only the <code translate="no">item</code> block, for example to publish an excerpt instead of the full content:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">extends</span></span> '_default/list.rss.twig' %}</span><span class="xml">

</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">block</span></span> item %}</span><span class="xml">
      <span class="hljs-tag">&lt;<span class="hljs-name">guid</span>&gt;</span></span><span class="hljs-template-variable">{{ url(item, {canonical: true}) }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">guid</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span></span><span class="hljs-template-variable">{{ item.title|e }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">pubDate</span>&gt;</span></span><span class="hljs-template-variable">{{ item.<span class="hljs-name">date</span>|<span class="hljs-keyword">date</span>('r') }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">pubDate</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">link</span>&gt;</span></span><span class="hljs-template-variable">{{ url(item, {canonical: true}) }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">link</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">description</span>&gt;</span>&lt;![CDATA[</span><span class="hljs-template-variable">{{ item.content|excerpt_html }}</span><span class="xml">]]&gt;<span class="hljs-tag">&lt;/<span class="hljs-name">description</span>&gt;</span>
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endblock</span></span> %}</span></code></pre>
<aside class="note note-tip"><p>Set <a href="/documentation/configuration/#baseurl"><code translate="no">baseurl</code></a> in <code translate="no">cecil.yml</code>: feeds use absolute URLs.</p>
<p>The RSS feed can also be styled in browsers by enabling the <code translate="no">xsl/rss</code> <a href="/documentation/configuration/#pages-default">default page</a>:</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-attr">pages:</span>
  <span class="hljs-attr">default:</span>
    <span class="hljs-attr">xsl/rss:</span>
      <span class="hljs-attr">published:</span> <span class="hljs-literal">true</span></code></pre></aside>]]>
    </content>
  </entry>
  <entry xml:lang="en">
    <id>https://cecil-945.pages.dev/how-to/templates-pagination/</id>
    <title>Paginate a list of pages</title>
    <published>2026-10-05T00:00:00+00:00</published>
    <link href="https://cecil-945.pages.dev/how-to/templates-pagination/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>When a section contains many pages, Cecil can split its list into several pages (e.g.: <code translate="no">/blog/</code>, <code translate="no">/blog/page/2/</code>, <code translate="no">/blog/page/3/</code>, etc.) and give you a <em>paginator</em> to build the navigation links.</p>
<h2 id="configure-pagination">Configure pagination</h2>
<p>Pagination is enabled by default for list pages (<em>homepage</em>, <em>section</em> and <em>term</em>), with 5 entries per page. Change it in <code translate="no">cecil.yml</code>:</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-attr">pages:</span>
  <span class="hljs-attr">pagination:</span>
    <span class="hljs-attr">max:</span> <span class="hljs-number">10</span>    <span class="hljs-comment"># maximum number of entries per page</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">page</span> <span class="hljs-comment"># path to the paginated page</span></code></pre>
<h2 id="override-it-for-a-section">Override it for a section</h2>
<p>Set the <code translate="no">pagination</code> variable in the front matter of the section index file (e.g.: <code translate="no">pages/blog/index.md</code>):</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-meta">---</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">Blog</span>
<span class="hljs-attr">pagination:</span>
  <span class="hljs-attr">max:</span> <span class="hljs-number">20</span>
<span class="hljs-meta">---</span></code></pre>
<p>Or disable it for this section only:</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-meta">---</span>
<span class="hljs-attr">pagination:</span> <span class="hljs-literal">false</span>
<span class="hljs-meta">---</span></code></pre>
<h2 id="display-the-paginated-pages">Display the paginated pages</h2>
<p>In the list template (e.g.: <code translate="no">layouts/blog/list.html.twig</code>), loop over <code translate="no">page.paginator.pages</code> and fall back to <code translate="no">page.pages</code> when pagination is disabled:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">for</span></span> p in page.paginator.pages ?? page.pages %}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">article</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ url(p) }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ p.title }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">time</span> <span class="hljs-attr">datetime</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ p.<span class="hljs-name">date</span>|<span class="hljs-keyword">date</span>('c') }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ p.<span class="hljs-name">date</span>|<span class="hljs-keyword">date</span>('j M Y') }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">time</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> %}</span></code></pre>
<h2 id="add-navigation-links">Add navigation links</h2>
<p>Paginator links are page IDs, so use the <code translate="no">url()</code> function to create working links:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> page.paginator %}</span><span class="xml">
<span class="hljs-tag">&lt;<span class="hljs-name">nav</span>&gt;</span>
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> page.paginator.links.prev is defined %}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ url(page.paginator.links.prev) }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"prev"</span>&gt;</span>Previous<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>Page </span><span class="hljs-template-variable">{{ page.paginator.current }}</span><span class="xml"> of </span><span class="hljs-template-variable">{{ page.paginator.count }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> page.paginator.links.next is defined %}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ url(page.paginator.links.next) }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"next"</span>&gt;</span>Next<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span><span class="xml">
<span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span>
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span></code></pre>
<p>To list every page number, iterate from <code translate="no">1</code> to <code translate="no">page.paginator.count</code>:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> page.paginator %}</span><span class="xml">
<span class="hljs-tag">&lt;<span class="hljs-name">nav</span>&gt;</span>
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">for</span></span> i in 1..page.paginator.count %}</span><span class="xml">
    </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> i == page.paginator.current %}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">aria-current</span>=<span class="hljs-string">"page"</span>&gt;</span></span><span class="hljs-template-variable">{{ i }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
    </span><span class="hljs-template-tag">{% <span class="hljs-name">elseif</span> i == 1 %}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ url(page.paginator.links.first) }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ i }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
    </span><span class="hljs-template-tag">{% <span class="hljs-name">else</span> %}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ url(page.paginator.links.path ~ '/' ~ i) }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ i }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
    </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> %}</span><span class="xml">
<span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span>
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span></code></pre>
<aside class="note note-info"><p>See the documentation of the <a href="/documentation/templates/#page"><code translate="no">page.paginator</code> variable</a>, the <a href="/documentation/configuration/#pages-pagination">pagination configuration</a> and the <a href="/documentation/content/#section">section <code translate="no">pagination</code> variable</a>.</p></aside>]]>
    </content>
  </entry>
  <entry xml:lang="en">
    <id>https://cecil-945.pages.dev/how-to/content-multilingual/</id>
    <title>Translate a site into multiple languages</title>
    <published>2026-10-04T00:00:00+00:00</published>
    <link href="https://cecil-945.pages.dev/how-to/content-multilingual/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>Cecil handles multilingual sites natively: declare the languages, add translated pages, then link them together and translate your templates.</p>
<h2 id="declare-languages">Declare languages</h2>
<p>Define the main language and the list of available languages in <code translate="no">cecil.yml</code>:</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-attr">language:</span> <span class="hljs-string">en</span>
<span class="hljs-attr">languages:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">code:</span> <span class="hljs-string">en</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">English</span>
    <span class="hljs-attr">locale:</span> <span class="hljs-string">en_US</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">code:</span> <span class="hljs-string">fr</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Français</span>
    <span class="hljs-attr">locale:</span> <span class="hljs-string">fr_FR</span>
    <span class="hljs-attr">config:</span>
      <span class="hljs-attr">title:</span> <span class="hljs-string">"Mon site en français"</span></code></pre>
<p>Options stored under the <code translate="no">config</code> key of a language override the global ones (here the site <code translate="no">title</code>).</p>
<h2 id="translate-a-page">Translate a page</h2>
<p>Duplicate the reference page and suffix its file name with the language code:</p>
<pre><code class="language-plaintext hljs plaintext" translate="no">pages/
├─ about.md    # the reference page
└─ about.fr.md # the french version</code></pre>
<p>Use the <code translate="no">slug</code> variable to translate the URL of the page:</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-meta">---</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">À</span> <span class="hljs-string">propos</span>
<span class="hljs-attr">slug:</span> <span class="hljs-string">a-propos</span>
<span class="hljs-meta">---</span></code></pre>
<p><code translate="no">about.md</code> is published to <code translate="no">/about/</code> and <code translate="no">about.fr.md</code> to <code translate="no">/fr/a-propos/</code>.</p>
<aside class="note note-tip"><p>To create a page that only exists in another language (not a translation), set <code translate="no">language: fr</code> in its front matter.</p></aside>
<h2 id="link-translated-pages">Link translated pages</h2>
<p>Each page exposes its translations with <code translate="no">page.translations</code>. Add a language switcher to your template:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">for</span></span> p in page.translations %}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ url(p) }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span> <span class="hljs-attr">hreflang</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ p.language }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ site.language.name(p.language) }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> %}</span></code></pre>
<p>You can also include the built-in partial: <code translate="no">{{ include('partials/languages.html.twig') }}</code>.</p>
<h2 id="translate-template-strings">Translate template strings</h2>
<p>Wrap texts with the <code translate="no">trans</code> tag or filter:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="hljs-template-tag">{% <span class="hljs-name">trans</span> %}</span><span class="xml">Read more</span><span class="hljs-template-tag">{% <span class="hljs-name">endtrans</span> %}</span><span class="xml">
</span><span class="hljs-template-variable">{{ 'Read more'|trans }}</span></code></pre>
<p>Then add a translation file named after the language locale in the <code translate="no">translations</code> directory:</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-comment"># translations/messages.fr_FR.yaml</span>
<span class="hljs-attr">Read more:</span> <span class="hljs-string">Lire</span> <span class="hljs-string">la</span> <span class="hljs-string">suite</span></code></pre>
<p>Extract the strings of your templates with:</p>
<pre><code class="language-bash hljs bash" translate="no">php cecil.phar util:translations:extract --locale=fr_FR --save</code></pre>
<aside class="note note-info"><p>See the documentation of <a href="/documentation/content/#multilingual">multilingual content</a>, <a href="/documentation/configuration/#languages">languages configuration</a> and <a href="/documentation/templates/#localization">templates localization</a>.</p></aside>]]>
    </content>
  </entry>
  <entry xml:lang="en">
    <id>https://cecil-945.pages.dev/how-to/templates-metatags/</id>
    <title>Apply SEO features in templates</title>
    <published>2026-06-09T00:00:00+00:00</published>
    <link href="https://cecil-945.pages.dev/how-to/templates-metatags/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>Cecil exposes its SEO feature through the <code translate="no">partials/metatags.html.twig</code> template. Include it in the <code translate="no">&lt;head&gt;</code> of your base template so every page automatically gets <strong>meta tags, canonical links, social cards, and structured data</strong>.</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ site.language }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span>&gt;</span>
    </span><span class="hljs-template-variable">{{ <span class="hljs-name">include</span><span class="hljs-params">('partials/metatags.html.twig')</span> }}</span><span class="xml">
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    ...
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span></code></pre>
<p>The partial reads page front matter first, then falls back to site configuration when needed. See the <a href="/documentation/configuration/#metatags">metatags configuration documentation</a> for all available options.</p>
<p>If you need to override the default title or image, pass values directly to the partial:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="hljs-template-variable">{{ <span class="hljs-name">include</span><span class="hljs-params">('partials/metatags.html.twig', {title: 'Custom title', image: og_image})</span> }}</span></code></pre>]]>
    </content>
  </entry>
  <entry xml:lang="en">
    <id>https://cecil-945.pages.dev/how-to/markdown-performance-images/</id>
    <title>Optimize images in Markdown</title>
    <published>2025-06-01T00:00:00+00:00</published>
    <updated>2025-06-02T00:00:00+00:00</updated>
    <link href="https://cecil-945.pages.dev/how-to/markdown-performance-images/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>One of the most effective ways to improve the performance of your website is to optimize images.</p>
<p>Cecil can manage the following optimizations for images in Markdown, automatically:</p>
<ol>
<li><strong>Dimensions</strong>: Images dimensions are set to ensure proper layout and prevent layout shifts</li>
<li><strong>Compression</strong>: The image is compressed to reduce file size without significant loss of quality</li>
<li><strong>Image formats</strong>: Cecil generates AVIF and WebP formats for the image</li>
<li><strong>Responsive images</strong>: Cecil generates two different sizes of the image (based on the configuration) to serve the appropriate size for different devices</li>
<li><strong>Lazy loading</strong>: The image is set to load lazily, meaning it will only load when it comes into the viewport</li>
<li><strong>Decoding</strong>: The image is set to decode asynchronously, improving the initial page load time</li>
<li><strong>Placeholder</strong>: A color placeholder is used while the image is loading</li>
</ol>
<aside class="note note-info"><p>See documentation for more details on the <a href="/documentation/configuration/#assets-images">global assets configuration</a> and the <a href="/documentation/configuration/#pages-body">pages configuration</a>.</p></aside>
<h2 id="example">Example</h2>
<p>Bellow an example with a PNG image 1920x1276 pixels.</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="xml">![A star forming region in the sky](../../assets/arnaud-girault-IjEtFjxXweE-unsplash.jpg "Photo by Arnaud Girault"){placeholder=color}</span></code></pre>
<figure>
<picture title="Photo by Arnaud Girault">
<source type="image/avif" srcset="/thumbnails/480x/images/examples/arnaud-girault-IjEtFjxXweE-unsplash.9f9b5448197aded5325854d4f3c79652.avif 480w, /thumbnails/768x/images/examples/arnaud-girault-IjEtFjxXweE-unsplash.9f9b5448197aded5325854d4f3c79652.avif 768w, /thumbnails/1024x/images/examples/arnaud-girault-IjEtFjxXweE-unsplash.9f9b5448197aded5325854d4f3c79652.avif 1024w" width="1024" height="681" sizes="100vw">
<source type="image/webp" srcset="/thumbnails/480x/images/examples/arnaud-girault-IjEtFjxXweE-unsplash.9f9b5448197aded5325854d4f3c79652.webp 480w, /thumbnails/768x/images/examples/arnaud-girault-IjEtFjxXweE-unsplash.9f9b5448197aded5325854d4f3c79652.webp 768w, /thumbnails/1024x/images/examples/arnaud-girault-IjEtFjxXweE-unsplash.9f9b5448197aded5325854d4f3c79652.webp 1024w" width="1024" height="681" sizes="100vw">
<img src="/thumbnails/1024x/images/examples/arnaud-girault-IjEtFjxXweE-unsplash.9f9b5448197aded5325854d4f3c79652.jpg" alt="A star forming region in the sky" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="681" style=";max-width:100%;height:auto;background-color:rgb(20 28 29);" srcset="/thumbnails/480x/images/examples/arnaud-girault-IjEtFjxXweE-unsplash.9f9b5448197aded5325854d4f3c79652.jpg 480w, /thumbnails/768x/images/examples/arnaud-girault-IjEtFjxXweE-unsplash.9f9b5448197aded5325854d4f3c79652.jpg 768w, /thumbnails/1024x/images/examples/arnaud-girault-IjEtFjxXweE-unsplash.9f9b5448197aded5325854d4f3c79652.jpg 1024w" sizes="100vw">
</picture>
<figcaption>Photo by <a href="https://unsplash.com/photos/a-star-forming-region-in-the-sky-IjEtFjxXweE" target="_blank" rel="noopener noreferrer">Arnaud Girault</a></figcaption>
</figure>
<h3 id="configuration">Configuration</h3>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-comment"># assets global configuration</span>
<span class="hljs-attr">assets:</span>
  <span class="hljs-attr">images:</span>
    <span class="hljs-attr">optimize:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">responsive:</span>
      <span class="hljs-attr">widths:</span> <span class="hljs-string">[768,</span> <span class="hljs-number">1024</span><span class="hljs-string">]</span>
<span class="hljs-comment"># configuration of images in Markdown</span>
<span class="hljs-attr">pages:</span>
  <span class="hljs-attr">body:</span>
    <span class="hljs-attr">images:</span>
      <span class="hljs-attr">formats:</span> <span class="hljs-string">[avif,</span> <span class="hljs-string">webp]</span>
      <span class="hljs-attr">responsive:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">lazy:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">decoding:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">placeholder:</span> <span class="hljs-string">color</span></code></pre>
<h3 id="generated-html">Generated HTML</h3>
<pre><code class="language-html hljs xml" translate="no"><span class="hljs-tag">&lt;<span class="hljs-name">picture</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">source</span>
    <span class="hljs-attr">type</span>=<span class="hljs-string">"image/avif"</span>
    <span class="hljs-attr">srcset</span>=<span class="hljs-string">"/thumbnails/1024/arnaud-girault-IjEtFjxXweE-unsplash.c0bdd31264ac3d0d364d02bced31038f.avif 1024w,
            /thumbnails/768/arnaud-girault-IjEtFjxXweE-unsplash.c0bdd31264ac3d0d364d02bced31038f.avif 768w"</span>
    <span class="hljs-attr">sizes</span>=<span class="hljs-string">"100vw"</span>
    <span class="hljs-attr">width</span>=<span class="hljs-string">"1024"</span>
    <span class="hljs-attr">height</span>=<span class="hljs-string">"681"</span>
  &gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">source</span>
    <span class="hljs-attr">type</span>=<span class="hljs-string">"image/webp"</span>
    <span class="hljs-attr">srcset</span>=<span class="hljs-string">"/thumbnails/1024/arnaud-girault-IjEtFjxXweE-unsplash.c0bdd31264ac3d0d364d02bced31038f.webp 1024w,
            /thumbnails/768/arnaud-girault-IjEtFjxXweE-unsplash.c0bdd31264ac3d0d364d02bced31038f.webp 768w"</span>
    <span class="hljs-attr">sizes</span>=<span class="hljs-string">"100vw"</span>
    <span class="hljs-attr">width</span>=<span class="hljs-string">"1024"</span>
    <span class="hljs-attr">height</span>=<span class="hljs-string">"681"</span>
  &gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/thumbnails/1024/arnaud-girault-IjEtFjxXweE-unsplash.c0bdd31264ac3d0d364d02bced31038f.jpg"</span>
    <span class="hljs-attr">alt</span>=<span class="hljs-string">"Photo by Arnaud Girault"</span>
    <span class="hljs-attr">loading</span>=<span class="hljs-string">"lazy"</span>
    <span class="hljs-attr">decoding</span>=<span class="hljs-string">"async"</span>
    <span class="hljs-attr">width</span>=<span class="hljs-string">"1024"</span>
    <span class="hljs-attr">height</span>=<span class="hljs-string">"681"</span>
    <span class="hljs-attr">style</span>=<span class="hljs-string">";max-width:100%;height:auto;background-color:rgb(58, 56, 44);"</span>
    <span class="hljs-attr">srcset</span>=<span class="hljs-string">"/thumbnails/1024/arnaud-girault-IjEtFjxXweE-unsplash.c0bdd31264ac3d0d364d02bced31038f.jpg 1024w,
            /thumbnails/768/arnaud-girault-IjEtFjxXweE-unsplash.c0bdd31264ac3d0d364d02bced31038f.jpg 768w"</span>
    <span class="hljs-attr">sizes</span>=<span class="hljs-string">"100vw"</span>
  &gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">picture</span>&gt;</span></code></pre>]]>
    </content>
  </entry>
  <entry xml:lang="en">
    <id>https://cecil-945.pages.dev/how-to/templates-image-formats/</id>
    <title>Generate multiple formats of an image in templates</title>
    <published>2025-06-01T00:00:00+00:00</published>
    <link href="https://cecil-945.pages.dev/how-to/templates-image-formats/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>For performance and bandwidth optimization, Cecil can generate multiple formats of an image, such as AVIF and WebP. This allows the browser to select the best format based on its capabilities:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="hljs-template-variable">{{ html(asset('image.jpg'), attributes={alt: 'Alternative description'}, options={formats: ['avif', 'webp']}) }}</span></code></pre>
<blockquote>
<p>AVIF and WebP are image formats that have superior compression and quality characteristics compared to their older JPEG and PNG counterparts. Encoding your images in these formats rather than JPEG or PNG means that they will load faster and consume less cellular data.</p>
<p>AVIF is supported in Chrome, Firefox, and Opera and offers smaller file sizes compared to other formats with the same quality settings.</p>
<p>WebP is supported in the latest versions of Chrome, Firefox, Safari, Edge, and Opera and provides better lossy and lossless compression for images on the web.</p>
</blockquote>
<p>– <a href="https://developer.chrome.com/docs/lighthouse/performance/uses-webp-images" target="_blank" rel="noopener noreferrer">Chrome Lighthouse docs</a></p>
<h2 id="example">Example</h2>
<p>Below an example of how to generate AVIF and WebP formats of an image:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="hljs-template-variable">{{ html(asset('cecil-logo.png'), attributes={alt: 'Cecil logo'}, options={formats: ['avif', 'webp']}) }}</span></code></pre>
<h3 id="generated-html">Generated HTML</h3>
<pre><code class="language-html hljs xml" translate="no"><span class="hljs-tag">&lt;<span class="hljs-name">picture</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">source</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image/avif"</span> <span class="hljs-attr">srcset</span>=<span class="hljs-string">"/cecil-logo.c1af8a129a0cde81f9b94ffbf452e10b.avif"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">source</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image/webp"</span> <span class="hljs-attr">srcset</span>=<span class="hljs-string">"/cecil-logo.c1af8a129a0cde81f9b94ffbf452e10b.webp"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/cecil-logo.c1af8a129a0cde81f9b94ffbf452e10b.png"</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"250"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"250"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"Cecil logo"</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">picture</span>&gt;</span></code></pre>]]>
    </content>
  </entry>
  <entry xml:lang="en">
    <id>https://cecil-945.pages.dev/how-to/templates-responsive-images/</id>
    <title>Render responsive images in templates</title>
    <published>2024-01-18T00:00:00+00:00</published>
    <updated>2025-05-12T00:00:00+00:00</updated>
    <link href="https://cecil-945.pages.dev/how-to/templates-responsive-images/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>The easiest way to render <a href="https://developer.mozilla.org/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images" target="_blank" rel="noopener noreferrer">responsive images</a> in templates is with the <a href="/documentation/templates/#html">html function</a>:</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="hljs-template-variable">{{ html(asset('image.jpg'), attributes={alt: 'Alternative description'}, options={responsive: true}) }}</span></code></pre>
<aside class="note note-important"><p>The default width values of the generated images are 480, 640, 768, 1024, 1366, 1600 and 1920. They can be modified in the <em>assets</em> section of the <a href="/documentation/configuration/#assets-images">configuration</a>.</p></aside>
<h2 id="example">Example</h2>
<p>Below an example with a PNG image 1000x1000 pixels.</p>
<pre><code class="language-twig hljs twig" translate="no"><span class="hljs-template-variable">{{ html(asset('cecil-logo-1000.png'), attributes={alt: 'Cecil logo'}, options={responsive: true}) }}</span></code></pre>
<h3 id="configuration">Configuration</h3>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-attr">assets:</span>
  <span class="hljs-attr">images:</span>
    <span class="hljs-attr">responsive:</span>
      <span class="hljs-attr">widths:</span> <span class="hljs-string">[768,</span> <span class="hljs-number">1024</span><span class="hljs-string">]</span></code></pre>
<h3 id="generated-html">Generated HTML</h3>
<pre><code class="language-html hljs xml" translate="no"><span class="hljs-tag">&lt;<span class="hljs-name">img</span>
  <span class="hljs-attr">alt</span>=<span class="hljs-string">"Cecil logo"</span>
  <span class="hljs-attr">width</span>=<span class="hljs-string">"1000"</span>
  <span class="hljs-attr">height</span>=<span class="hljs-string">"1000"</span>
  <span class="hljs-attr">src</span>=<span class="hljs-string">"/cecil-logo-1000.fbacb922cddbcdb7ca9a03a3ca3cf2ca.png"</span>
  <span class="hljs-attr">srcset</span>=<span class="hljs-string">"/thumbnails/768/cecil-logo-1000.fbacb922cddbcdb7ca9a03a3ca3cf2ca.png 768w,
          /cecil-logo-1000.fbacb922cddbcdb7ca9a03a3ca3cf2ca.png 1000w"</span>
  <span class="hljs-attr">sizes</span>=<span class="hljs-string">"100vw"</span>
&gt;</span></code></pre>]]>
    </content>
  </entry>
</feed>
