<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <language>en</language>
    <title>Webstoemp - Blogposts Feed</title>
    <description>Webstoemp is the portfolio and blog of Jérôme Coupé, a designer and front-end developer from Brussels, Belgium.</description>
    <link>https://www.webstoemp.com</link>
    <managingEditor>jerome.coupe@webstoemp.com (Jerôme Coupé)</managingEditor>
    <webMaster>jerome.coupe@webstoemp.com (Jerôme Coupé)</webMaster>
    <pubDate>Mon, 27 Oct 2025 13:14:24 +0000</pubDate>
    <lastBuildDate>Mon, 27 Oct 2025 13:14:24 +0000</lastBuildDate>
    <generator>Eleventy</generator>
    
      <item>
        <title>External Asset Pipeline with Eleventy</title>
        <link>https://www.webstoemp.com/blog/eleventy-dev-server-external-asset-pipeline/</link>
        <pubDate>Mon, 23 Jan 2023 00:00:00 +0000</pubDate>
        <guid>https://www.webstoemp.com/blog/eleventy-dev-server-external-asset-pipeline/</guid>
        <description>
        <![CDATA[
          <p>I like to use NPM scripts for my asset pipeline instead of using Eleventy to generate assets and orchestrate everything. The latest release of Eleventy Dev Server makes that approach both easy to implement and quite performant.</p>
          <h2>Two approaches to asset pipelines</h2>
<p>As outlined by Max Bock in <a href="https://mxb.dev/blog/eleventy-asset-pipeline/">his infamous blogpost about asset pipelines</a>, there are two main ways to transform your assets source files (SCSS and JS mainly) into production-ready output files.</p>
<ol>
<li>Use external tools working in parallel with Eleventy</li>
<li>Use Eleventy to manage everything</li>
</ol>
<p>Here is another <a href="https://chriskirknielsen.com/blog/eleventy-asset-pipeline-precompiled-assets/">nice blogpost by Vadim Makeev</a> if you would rather like Eleventy to be in charge. As for me, I like the flexibility of an external build pipeline, mainly because I can easily use the same approach in non-Eleventy contexts.</p>
<p>My default solution is to use NPM scripts to compile assets directly to my output folder. Meanwhile, using a package like <code>npm-run-all</code>, I can have Eleventy do its thing. Running those processes in parallel is quite efficient and the separation of concerns appeals to me.</p>
<p>Previously, you could essentially go down three different routes with this external approach:</p>
<ol>
<li>Use <code>addWatchTarget</code> in your configuration file to watch source files and trigger an Eleventy build when any file changes. Eleventy builds are fast, but triggering a whole build to reload my browser feels a bit wasteful. It also introduces a potential race condition between your external asset pipeline and Eleventy.</li>
<li>Generate compiled assets in a folder added to <code>.gitignore</code> as well as to Eleventy ignore rules and use <code>addPassthroughCopy</code> to copy those generated assets to the output folder. No Eleventy build is triggered but your assets are now written twice to disc, which also seems a bit awkward.</li>
<li>Use an external package like Browsersync as a local server to do the watching. Easy to manage via the CLI and the <code>--files</code> option allows you to watch your entire output directory to trigger browser reloads.</li>
</ol>
<h2>A native performant solution</h2>
<p>With the 1.0 version of <a href="https://www.11ty.dev/docs/dev-server/">Eleventy Dev Server</a>, I can now use a native and elegant solution.</p>
<p>I can keep building my assets with NPM scripts and run <code>npx @11ty/eleventy --serve --quiet</code> in parallel. The trick here is to use the new <code>watch</code> option in your config file to target the output directories used by your build process.</p>
<p>Any change to files in these directories will then trigger a server reload, even if those changes are the result of an external asset pipeline.</p>
<p>Here is a bare bones example of the scripts I use in development. You could use <code>--watch</code> flags with <code>sass</code> and <code>esbuild</code> instead of the <code>onchange</code> package but, again, I like the separation of concerns (and I also find it easier to read and understand).</p>
<pre class="language-json"><code class="language-json"><span class="token property">"scripts"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
  <span class="token property">"server"</span><span class="token operator">:</span> <span class="token string">"npx @11ty/eleventy --serve --quiet"</span><span class="token punctuation">,</span>
  <span class="token property">"styles:dev"</span><span class="token operator">:</span> <span class="token string">"sass --embed-source-map --source-map-urls=\"absolute\" \"./src/assets/scss/main.scss\" \"./dist/assets/css/main.css\""</span><span class="token punctuation">,</span>
  <span class="token property">"scripts:dev"</span><span class="token operator">:</span> <span class="token string">"esbuild \"./src/assets/js/main.js\" --target=es2020 --bundle --outfile=\"./dist/assets/js/main.bundle.js\""</span><span class="token punctuation">,</span>
  <span class="token property">"watch:scripts"</span><span class="token operator">:</span> <span class="token string">"onchange \"./src/assets/js/**/*\" -- npm run scripts:dev"</span><span class="token punctuation">,</span>
  <span class="token property">"watch:styles"</span><span class="token operator">:</span> <span class="token string">"onchange \"./src/assets/scss/**/*\" -- npm run styles:dev"</span><span class="token punctuation">,</span>
  <span class="token property">"dev"</span><span class="token operator">:</span> <span class="token string">"npm-run-all --parallel server watch:*"</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span></code></pre>
<p>And here is the relevant portion of an Eleventy config file.</p>
<pre class="language-js"><code class="language-js">module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">eleventyConfig</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// ---------- other config options ----------</span>

  <span class="token comment">// ignore source assets (processing and watching)</span>
  eleventyConfig<span class="token punctuation">.</span>ignores<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"./src/assets/**/*"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  eleventyConfig<span class="token punctuation">.</span>watchIgnores<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"./src/assets/**/*"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// pass through (copy fonts and images to output directory)</span>
  eleventyConfig<span class="token punctuation">.</span><span class="token function">addPassthroughCopy</span><span class="token punctuation">(</span><span class="token string">"./src/assets/img"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  eleventyConfig<span class="token punctuation">.</span><span class="token function">addPassthroughCopy</span><span class="token punctuation">(</span><span class="token string">"./src/assets/fonts"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// server config (watch generated assets in dist folder)</span>
  eleventyConfig<span class="token punctuation">.</span><span class="token function">setServerOptions</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    <span class="token literal-property property">watch</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"./dist/assets/css/**/*.css"</span><span class="token punctuation">,</span> <span class="token string">"./dist/assets/js/**/*.js"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token literal-property property">port</span><span class="token operator">:</span> <span class="token number">3000</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>That's all there is to it, really. <a href="https://github.com/jeromecoupe/webstoemp">This very website</a> uses the approach outlined above. So far, I am very happy with it and plan to use it for all my other projects once Eleventy 2.0 comes out of beta.</p>

        ]]>
        </description>
      </item>
    
      <item>
        <title>Modular code with Nunjucks and Eleventy</title>
        <link>https://www.webstoemp.com/blog/modular-code-nunjucks-eleventy/</link>
        <pubDate>Fri, 06 Aug 2021 00:00:00 +0000</pubDate>
        <guid>https://www.webstoemp.com/blog/modular-code-nunjucks-eleventy/</guid>
        <description>
        <![CDATA[
          <p>These days, web development and design are all about modularization and components. Template languages have a lot to offer and Eleventy itself has a few tricks up its sleeve.</p>
          <h2>Team Nunjucks</h2>
<p>I personally use <a href="https://mozilla.github.io/nunjucks/">Nunjucks</a> as my templating language with Eleventy. On top of being powerful and simple to learn, its syntax is similar to Twig, which I have been using for a while in <a href="https://craftcms.com/">Craft</a> projects. That being said, <a href="https://liquidjs.com/">LiquidJS</a> looks more fully-featured than the version of Liquid I used back in the days with Jekyll ... but I digress.</p>
<p>Back to my rule of thumb: when modularizing code, use all the tools offered by templating languages first. Only reach for custom filters and shortcodes when your use case requires it.</p>
<h2>Layouts</h2>
<p>Among other things, I am quite fond of Nunjucks' <a href="https://mozilla.github.io/nunjucks/templating.html#template-inheritance">template inheritance</a> concept. The combination of <code>extends</code> and <code>block</code> allows authors to chain multiple templates and to define blocks that extending templates can override or not. Nunjucks templates share the same scope or context, which means defined variables are available to other templates in that chain and can also be overridden.</p>
<p>As a rule of thumb, I use Nunjucks template inheritance for layouts and only use <a href="https://www.11ty.dev/docs/layout-chaining/#addendum-about-existing-templating-features">Eleventy's layout system</a> when I need to specify a layout in a markdown file front matter. In that case, the called Nunjucks template has a <code>{{ content | safe }}</code> tag where I want the content of my Markdown file to appear and Nunjucks takes over from then on.</p>
<h2>Filters</h2>
<p>Most templating languages offer a bunch of <a href="https://mozilla.github.io/nunjucks/templating.html#builtin-filters">built-in filters</a>. When they are not quite up to snuff, Eleventy allows you to create <a href="https://www.11ty.dev/docs/filters/">custom filters</a> using all the power of JavaScript and Node, as well as the ecosystem of packages, modules and libraries coming along with it.</p>
<p>Nunjucks has no filter to format dates? We can harness the power of <a href="https://moment.github.io/luxon/">Luxon</a> and create one. How about localisation while we're at it?</p>
<pre class="language-js"><code class="language-js"><span class="token comment">/**
 * Format a date with Luxon
 *
 * @param {String} date - string Date
 * @param {String} format - date format (Luxon)
 * @param {String} locale - locale
 * @returns {String} formatted date
 */</span>

<span class="token keyword">const</span> <span class="token punctuation">{</span> DateTime <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"luxon"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span>date<span class="token punctuation">,</span> format<span class="token punctuation">,</span> locale <span class="token operator">=</span> <span class="token string">"en"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  date <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>date<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">return</span> DateTime<span class="token punctuation">.</span><span class="token function">fromJSDate</span><span class="token punctuation">(</span>date<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setLocale</span><span class="token punctuation">(</span>locale<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toFormat</span><span class="token punctuation">(</span>format<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>We make our filter available in Nunjucks, Liquid, Handlebars and JavaScript by adding it to <code>.eleventy.js</code>. Luxon is included in Eleventy but let's add it to our <code>package.json</code> for good measure: <code>npm i -D luxon</code>.</p>
<pre class="language-js"><code class="language-js">module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">eleventyConfig</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// ... more config ...</span>
  eleventyConfig<span class="token punctuation">.</span><span class="token function">addFilter</span><span class="token punctuation">(</span>
    <span class="token string">"formatDate"</span><span class="token punctuation">,</span>
    <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"./src/_11ty/filters/formatDate.js"</span><span class="token punctuation">)</span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token comment">// ... more config ...</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Usage:</p>
<pre class="language-jinja2"><code class="language-jinja2"><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{{</span> <span class="token string">"2021-08-06"</span> <span class="token operator">|</span> <span class="token function">formatDate</span><span class="token punctuation">(</span><span class="token string">"DDD"</span><span class="token punctuation">)</span> <span class="token delimiter punctuation">}}</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>time</span> <span class="token attr-name">datetime</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{{</span> <span class="token variable">post</span><span class="token punctuation">.</span><span class="token variable">date</span> <span class="token operator">|</span> <span class="token function">formatDate</span><span class="token punctuation">(</span><span class="token string">'yyyy-MM-dd'</span><span class="token punctuation">)</span> <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{{</span> <span class="token variable">post</span><span class="token punctuation">.</span><span class="token variable">date</span> <span class="token operator">|</span> <span class="token function">formatDate</span><span class="token punctuation">(</span><span class="token string">"DDD"</span><span class="token punctuation">,</span> <span class="token string">"fr"</span><span class="token punctuation">)</span> <span class="token delimiter punctuation">}}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>time</span><span class="token punctuation">></span></span>
</code></pre>
<p>Another type of filters I see myself creating regularly are the ones allowing me to filter collections. Let's say we need to display only posts belonging to certain categories in our templates.</p>
<pre class="language-js"><code class="language-js"><span class="token comment">/**
 * Filter collection by categories
 *
 * @param {Array} collection - collection (assuming front-matter have a 'categories' key)
 * @param {Array} categories - array of categories (if string supplied, turn into array)
 * @returns {Array} collection items from specified categories
 */</span>

module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">collection<span class="token punctuation">,</span> categories</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">let</span> results <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Set</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> categories <span class="token operator">===</span> <span class="token string">"string"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    categories <span class="token operator">=</span> Array<span class="token punctuation">.</span><span class="token function">of</span><span class="token punctuation">(</span>categories<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  categories<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">cat</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">let</span> matches <span class="token operator">=</span> collection<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token operator">=></span>
      item<span class="token punctuation">.</span>data<span class="token punctuation">[</span><span class="token string">"categories"</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>cat<span class="token punctuation">)</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
    matches<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token operator">=></span> results<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  results <span class="token operator">=</span> Array<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span>results<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b</span><span class="token punctuation">)</span> <span class="token operator">=></span> a<span class="token punctuation">.</span>date <span class="token operator">-</span> b<span class="token punctuation">.</span>date<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> results<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Let's add the filter to our Eleventy configuration.</p>
<pre class="language-js"><code class="language-js">module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">eleventyConfig</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// ... more config ...</span>
  eleventyConfig<span class="token punctuation">.</span><span class="token function">addFilter</span><span class="token punctuation">(</span>
    <span class="token string">"getByCategories"</span><span class="token punctuation">,</span>
    <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"./src/_11ty/filters/getByCategories.js"</span><span class="token punctuation">)</span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token comment">// ... more config ...</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>We can then use it like this in our templates:</p>
<pre class="language-jinja2"><code class="language-jinja2"><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">set</span> <span class="token variable">yummyPosts</span> <span class="token operator">=</span> <span class="token variable">collections</span><span class="token punctuation">.</span><span class="token variable">posts</span> <span class="token operator">|</span> <span class="token function">getByCategories</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">"meals"</span><span class="token punctuation">,</span> <span class="token string">"desserts"</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token delimiter punctuation">%}</span></span>
</code></pre>
<h2>Includes</h2>
<p><a href="https://mozilla.github.io/nunjucks/templating.html#include">Nunjucks includes</a> do not have a separate scope and do not take parameters. However, they can be nested and have access to variables defined in the template context.</p>
<p>My use cases for includes are static partials or the ones only needing access to variables available in the Nunjucks template context: header, footer, html <code>&lt;head&gt;</code>, navigation, pagination, etc.</p>
<p>Here is an include for a navigation interface that is itself included in a header.</p>
<pre class="language-jinja2"><code class="language-jinja2"><span class="token jinja2 language-jinja2"><span class="token comment">{##
 # - get nav items from data file (./src/_data/mainnav.js)
 # - activeSection is set by Nunjucks and available in the template context
 # - if activeSection is equal to item.navTrigger, display active class and aria-current
 # - display navUrl, navLabel for each item
#}</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>nav</span> <span class="token attr-name">aria-label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>main navigation<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
  <span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">for</span> <span class="token variable">item</span> <span class="token keyword">in</span> <span class="token variable">mainnav</span> <span class="token delimiter punctuation">%}</span></span>

    <span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">set</span> <span class="token variable">activeClass</span> <span class="token operator">=</span> <span class="token string">""</span> <span class="token delimiter punctuation">%}</span></span>
    <span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">set</span> <span class="token variable">activeAria</span> <span class="token operator">=</span> <span class="token string">""</span> <span class="token delimiter punctuation">%}</span></span>
    <span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">if</span> <span class="token variable">item</span><span class="token punctuation">.</span><span class="token variable">navTrigger</span> <span class="token operator">==</span> <span class="token variable">activeSection</span> <span class="token delimiter punctuation">%}</span></span>
      <span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">set</span> <span class="token variable">activeClass</span> <span class="token operator">=</span> <span class="token string">"is-active"</span> <span class="token delimiter punctuation">%}</span></span>
      <span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">set</span> <span class="token variable">activeAria</span> <span class="token operator">=</span> <span class="token string">'aria-current="page"'</span> <span class="token operator">|</span> <span class="token variable">safe</span> <span class="token delimiter punctuation">%}</span></span>
    <span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>

    <span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">if</span> <span class="token keyword">loop</span><span class="token punctuation">.</span><span class="token variable">first</span> <span class="token delimiter punctuation">%}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>c-mainnav<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>c-mainnav__item<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>c-mainnav__link  <span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{{</span> <span class="token variable">activeClass</span> <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{{</span> <span class="token variable">item</span><span class="token punctuation">.</span><span class="token variable">navUrl</span> <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{{</span> <span class="token variable">activeAria</span> <span class="token delimiter punctuation">}}</span></span></span><span class="token punctuation">></span></span><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{{</span> <span class="token variable">item</span><span class="token punctuation">.</span><span class="token variable">navLabel</span> <span class="token delimiter punctuation">}}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span>
    <span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">if</span> <span class="token keyword">loop</span><span class="token punctuation">.</span><span class="token variable">last</span> <span class="token delimiter punctuation">%}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>

  <span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">endfor</span> <span class="token delimiter punctuation">%}</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>nav</span><span class="token punctuation">></span></span>
</code></pre>
<h2>Nunjucks macros</h2>
<p><a href="https://mozilla.github.io/nunjucks/templating.html#macro">Macros</a> are a bit more powerful and a bit more involved than includes. They have their own scope by default and can be passed positional or named parameters. They must also be imported using <code>{% from %}</code> or <code>{% import %}</code> before being used.</p>
<p>I use macros as soon as I need components with parameters and a small amount of logic. Their main advantage is their legibility when you need to output a non-trivial amount of HTML.</p>
<pre class="language-jinja2"><code class="language-jinja2"><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">macro</span> <span class="token function">itemPost</span><span class="token punctuation">(</span><span class="token variable">title</span><span class="token punctuation">,</span> <span class="token variable">date</span><span class="token punctuation">,</span> <span class="token variable">url</span><span class="token punctuation">,</span> <span class="token variable">featured</span><span class="token punctuation">,</span> <span class="token variable">locale</span><span class="token operator">=</span><span class="token string">"en"</span><span class="token punctuation">)</span> <span class="token delimiter punctuation">-%}</span></span>

  <span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">set</span> <span class="token variable">featuredClass</span> <span class="token operator">=</span> <span class="token string">"c-blogteaser--featured"</span> <span class="token keyword">if</span> <span class="token variable">featured</span> <span class="token delimiter punctuation">%}</span></span>

  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>article</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>c-blogteaser <span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{{</span> <span class="token variable">featuredClass</span> <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>c-blogteaser__date<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>time</span> <span class="token attr-name">datetime</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{{</span> <span class="token variable">date</span> <span class="token operator">|</span> <span class="token function">formatDate</span><span class="token punctuation">(</span><span class="token string">'yyyy-MM-dd'</span><span class="token punctuation">)</span> <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{{</span> <span class="token variable">date</span> <span class="token operator">|</span> <span class="token function">formatDate</span><span class="token punctuation">(</span><span class="token string">"DDD"</span><span class="token punctuation">,</span> <span class="token variable">locale</span><span class="token punctuation">)</span> <span class="token delimiter punctuation">}}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>time</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>c-blogteaser__title<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>c-blogteaser__link<span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{{</span> <span class="token variable">url</span> <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{{</span> <span class="token variable">title</span> <span class="token delimiter punctuation">}}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">></span></span>

<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">endmacro</span> <span class="token delimiter punctuation">%}</span></span>
</code></pre>
<p>Now we can import it and use it.</p>
<pre class="language-jinja2"><code class="language-jinja2"><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">from</span> <span class="token string">"macros/itemPost.njk"</span> <span class="token keyword">import</span> <span class="token variable">itemPost</span> <span class="token delimiter punctuation">%}</span></span>

<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">set</span> <span class="token variable">posts</span> <span class="token operator">=</span> <span class="token variable">collections</span><span class="token punctuation">.</span><span class="token variable">posts</span> <span class="token delimiter punctuation">%}</span></span>
<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">for</span> <span class="token variable">item</span> <span class="token keyword">in</span> <span class="token variable">posts</span> <span class="token delimiter punctuation">%}</span></span>
  <span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">if</span> <span class="token keyword">loop</span><span class="token punctuation">.</span><span class="token variable">first</span> <span class="token delimiter punctuation">%}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">></span></span><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>
      <span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{{</span> <span class="token function">itemPost</span><span class="token punctuation">(</span>
        <span class="token variable">title</span> <span class="token operator">=</span> <span class="token variable">item</span><span class="token punctuation">.</span><span class="token variable">data</span><span class="token punctuation">.</span><span class="token variable">title</span><span class="token punctuation">,</span>
        <span class="token variable">date</span> <span class="token operator">=</span> <span class="token variable">item</span><span class="token punctuation">.</span><span class="token variable">date</span><span class="token punctuation">,</span>
        <span class="token variable">url</span> <span class="token operator">=</span> <span class="token variable">item</span><span class="token punctuation">.</span><span class="token variable">url</span><span class="token punctuation">,</span>
        <span class="token variable">featured</span> <span class="token operator">=</span> <span class="token variable">item</span><span class="token punctuation">.</span><span class="token variable">data</span><span class="token punctuation">.</span><span class="token variable">featured</span>
      <span class="token punctuation">)</span> <span class="token delimiter punctuation">}}</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span>
  <span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">if</span> <span class="token keyword">loop</span><span class="token punctuation">.</span><span class="token variable">last</span> <span class="token delimiter punctuation">%}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>
<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">else</span> <span class="token delimiter punctuation">%}</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>No post found<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span>
<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">endfor</span> <span class="token delimiter punctuation">%}</span></span>
</code></pre>
<p>A minor drawback of Nunjucks macros is that they need to be explicitly imported. In that regard, the <a href="https://liquidjs.com/tags/render.html">LiquidJS <code>render</code> tag</a> appears to be a bit more flexible. Then again, we are explicitly importing components in &quot;React and friends&quot; all day long.</p>
<h2>Eleventy shortcodes</h2>
<p>Eleventy shortcodes are essentially a way to create custom tags in various template languages, which makes them the most powerful option at our disposal to create components.</p>
<p>They can pretty much do everything Nunjucks macros can do and then some.</p>
<p>I reach for shortcodes when I need advanced logic, fine grained control over errors, NPM modules or async components fetching data or reading files. Paired shortcodes also help when there is a big block of content to process.</p>
<p>Like many, I will often reach for shortcodes to provide content authors with the ability to include snippets of HTML into Markdown files: styled Youtube embeds or figures with captions are common use cases.</p>
<p>Eleventy being its usual flexible self, most shortcodes can be made universal, which makes them available in all template languages that supports them (Nunjucks, Liquid, JavaScript, Handlebars).</p>
<p>In order to use them in Markdown files (you can use includes or macros that way too), set <code>markdownTemplateEngine</code> in your <code>.eleventy.js</code> config file to <code>njk</code>. That tells Eleventy to process markdown files with Nunjucks before rendering them to HTML.</p>
<p>Speaking of Nunjucks, here is a shortcode that will render the content of any markdown file in a template.</p>
<pre class="language-js"><code class="language-js"><span class="token comment">/**
 * Returns rendered markdown from a file
 *
 * @param {String} path - Path of the file to render
 */</span>

<span class="token keyword">const</span> fs <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"fs"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> path <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"path"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> md <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"markdown-it"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">filePath</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  filePath <span class="token operator">=</span> path<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span>filePath<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">if</span> <span class="token punctuation">(</span>path<span class="token punctuation">.</span><span class="token function">extname</span><span class="token punctuation">(</span>filePath<span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token string">".md"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">RenderMdFile expects Markdown files.</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">const</span> fileContent <span class="token operator">=</span> fs<span class="token punctuation">.</span><span class="token function">readFileSync</span><span class="token punctuation">(</span>filePath<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">return</span> md<span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span>fileContent<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Again, we have to declare the shortcode in our Eleventy configuration (you know the drill by now), before we can use it in our Nunjucks templates.</p>
<pre class="language-jinja2"><code class="language-jinja2"><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">renderMdFile</span> <span class="token string">"src/content/markdown/test.md"</span> <span class="token delimiter punctuation">%}</span></span>
</code></pre>
<h2>Famous last words</h2>
<p>I am personally really fond of the HTML first approach taken by Nunjucks and Eleventy for server-side rendered components.</p>
<p>However, it seems (server-side rendered first) JavaScript components are banging hard on the door of the SSG world lately. Projects like <a href="https://www.netlify.com/blog/2020/09/18/eleventy-and-vue-a-match-made-to-power-netlify.com/">eleventy-plugin-vue</a>, <a href="https://slinkity.dev/">Slinkity</a> and <a href="https://astro.build/">Astro</a> are all moving in this direction.</p>
<p>I have a feeling we might soon all write all-encompassing server-side rendered first, hydrated on demand components handling data, markup, styling and interactions.</p>

        ]]>
        </description>
      </item>
    
      <item>
        <title>Structuring Eleventy projects</title>
        <link>https://www.webstoemp.com/blog/eleventy-projects-structure/</link>
        <pubDate>Thu, 29 Jul 2021 00:00:00 +0000</pubDate>
        <guid>https://www.webstoemp.com/blog/eleventy-projects-structure/</guid>
        <description>
        <![CDATA[
          <p>One of the great things about Eleventy is its flexibility and its lack of assumptions about how your projects should be organized. However, in order to preserve my own sanity, I needed to come up with a default files and folders architecture that made sense to me.</p>
          <p>Coupled with my lack of personal conventions regarding projects architecture, the unopinionated nature of Eleventy meant that all my projects were organized slightly differently. As a result, I lost some time figuring out how things were configured and organized when jumping from one project to another. Reusing code from project to project was also made more difficult.</p>
<p>In short, it was time for a minimal amount of structure and conventions. Here is what I came up with:</p>
<h2>Files and folders</h2>
<pre class="language-txt"><code class="language-txt">+-- build_tasks/
+-- src/
  +-- _11ty/
    +-- collections/
    +-- filters/
    +-- shortcodes/
    +-- utils/
  +-- _data/
  +-- _includes/
    +-- layouts/
    +-- macros/
    +-- partials/
    +-- svg/
  +-- assets/
    +-- fonts/
    +-- img/
    +-- js/
    +-- scss/
  +-- content/
    +-- en/
    +-- fr/
+-- .eleventy.js
+-- .gitignore
+-- package-lock.json
+-- package.json
+-- README.md</code></pre>
<ul>
<li><code>build_tasks</code>: I generally use NPM scripts as my <a href="https://mxb.dev/blog/eleventy-asset-pipeline/">asset pipeline</a>. Occasionally, I need something more than an NPM package and a one liner. Those slightly more involved build scripts live in this directory.</li>
<li><code>src/_11ty</code>: contains everything required by the <code>.eleventy.js</code> config file: collections, filters, shortcodes and utilities all go in their own directories. Reusing these in another project amounts to copying a file, installing dependencies if needed and requiring the file in <code>.eleventy.js</code>.</li>
<li><code>src/_data</code>: data directory containing static or dynamic local data files. Pretty much standard in Eleventy projects.</li>
<li><code>src/_includes</code>: I use Nunjucks extensively, along with <a href="https://mozilla.github.io/nunjucks/templating.html#extends">template inheritance</a> and <code>extends</code>, which means layouts, includes and macros must live in this directory.</li>
<li><code>src/_includes/layouts</code>: layouts used by the project. I use Nunjucks template inheritance whenever possible. <a href="https://mozilla.github.io/nunjucks/templating.html#block">Blocks</a> makes it a superior layout system for me compared to the (simpler) Eleventy layout system.</li>
<li><code>src/_includes/partials</code>: components that do not require any parameters other than those available in the global template context. Used for things like header, footer, etc.</li>
<li><code>src/_includes/macros</code>: Nunjucks macros are locally scoped and accept parameters / arguments. Used for standard components (cards, etc) and utilities (format dates, etc.).</li>
<li><code>src/_includes/svg</code>: SVG code to be included inline in pages. SVG are optimized and <a href="https://css-tricks.com/accessible-svgs/">accessible</a>.</li>
<li><code>src/assets</code>: static and processed assets. Typically contains <code>scss</code>, <code>img</code>, <code>fonts</code> and <code>js</code> directories. Most of these are handled by a build process, while others, like <code>fonts</code> are copied by 11ty.</li>
<li><code>src/content</code>: the content of the site, be it Nunjucks or Markdown files. I generally use directories and <code>getFilteredByGlob(Glob)</code> to define collections, rather than using tags. For <a href="/blog/multilingual-sites-eleventy/">multilingual projects</a>, I create subdirectories containing <a href="https://www.11ty.dev/docs/data-template-dir/">directory data files</a> to define locales as direct children of the <code>content</code> directory.</li>
</ul>
<p>Head over to Youtube if you'd rather see <a href="https://www.youtube.com/watch?v=boZiLtx8p3Q">a video where I go through this</a> while frantically moving my mouse cursor around.</p>
<p>I am sure these conventions I have set for myself will still evolve (escaping entropy is an illusion) but, so far, this way of loosely structuring projects makes sense to me and allows me to be more productive.</p>

        ]]>
        </description>
      </item>
    
      <item>
        <title>Minimalist video player</title>
        <link>https://www.webstoemp.com/blog/minimalist-video-player/</link>
        <pubDate>Thu, 22 Jul 2021 00:00:00 +0000</pubDate>
        <guid>https://www.webstoemp.com/blog/minimalist-video-player/</guid>
        <description>
        <![CDATA[
          <p>For a recent project, I needed a minimalist video player for services like Youtube and Vimeo. The objectives were to wait for user interaction to load videos, have a custom cover image and build a straightforward and maintainable solution.</p>
          <h2>Objectives</h2>
<p>The website I was working on could potentially have up to 12 video players on a single page. For performance reasons, I didn't want to load all these <code>iframe</code> and their content on first load. The client also wanted their videos to have a custom cover image and was using Vimeo and Youtube as video services.</p>
<p>On my side, I wanted to come up with something simple to maintain and that didn't depend on the ever-changing APIs of these services. I also wanted a fallback in the form of a link to these video services if the JavaScript didn't work or was not loaded.</p>
<p>Since I was going for a progressively enhanced solution, I settled on JavaScript modules as my cut the mustard test and checked for <code>template</code> tag support on top of that.</p>
<h2>HTML foundations</h2>
<p>I started with some (hopefully) semantic HTML code:</p>
<ul>
<li>a working link to the video on the relevant service</li>
<li>a responsive cover image</li>
<li>data attributes to store the ID of the video and the video service (used to dynamically create the <code>iframe</code> relevant <code>src</code> value with JavaScript)</li>
</ul>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>c-videoplayer  js-video-player<span class="token punctuation">"</span></span> <span class="token attr-name">data-video-service</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>vimeo<span class="token punctuation">"</span></span> <span class="token attr-name">data-video-id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>174919644<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>c-videoplayer__link  js-video-link<span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://vimeo.com/174919644<span class="token punctuation">"</span></span> <span class="token attr-name">aria-label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>play video: video title<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/239/600/338<span class="token punctuation">"</span></span>
         <span class="token attr-name">srcset</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/239/600/338 600w,
                 https://picsum.photos/id/239/800/450 800w<span class="token punctuation">"</span></span>
         <span class="token attr-name">sizes</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>(min-width: 1440px) 720px,
               (min-width: 750px) 50vw,
               100vw<span class="token punctuation">"</span></span>
         <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>c-videoplayer__cover<span class="token punctuation">"</span></span>
         <span class="token attr-name">loading</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>lazy<span class="token punctuation">"</span></span>
         <span class="token attr-name">decoding</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>async<span class="token punctuation">"</span></span>
         <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>js-video-template<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token punctuation">"</span></span> <span class="token attr-name">allow</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>autoplay; fullscreen<span class="token punctuation">"</span></span> <span class="token attr-name">allowfullscreen</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>I decided to have a <code>template</code> tag wrapped around the <code>iframe</code> to prevent it from rendering on page load. While it is certainly possible to generate the <code>iframe</code> entirely in JavaScript, it made more sense to me to have as much of the markup as possible in the HTML.</p>
<p>I also included that template inside every video player to make it more of a self-contained &quot;component&quot;.</p>
<h2>Add some CSS</h2>
<p>In terms of styles, the player needed to always have an aspect ratio of 16 by 9. The link or the <code>iframe</code> could then be absolutely positioned relative to the player to fill all the available space. A custom SVG &quot;play&quot; icon can be thrown in using generated content.</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/* --------------------------------
videoplayer
-------------------------------- */</span>

<span class="token selector">.c-videoplayer</span> <span class="token punctuation">{</span>
  <span class="token property">position</span><span class="token punctuation">:</span> relative<span class="token punctuation">;</span>
  <span class="token property">background-color</span><span class="token punctuation">:</span> black<span class="token punctuation">;</span>
  <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 16 / 9<span class="token punctuation">;</span>
  <span class="token property">background</span><span class="token punctuation">:</span> #000000<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token atrule"><span class="token rule">@supports</span> <span class="token keyword">not</span> <span class="token punctuation">(</span><span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 16 / 9<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  <span class="token selector">.c-videoplayer</span> <span class="token punctuation">{</span>
    <span class="token property">padding-top</span><span class="token punctuation">:</span> 56.25%<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token selector">.c-videoplayer__link,
.c-videoplayer > iframe</span> <span class="token punctuation">{</span>
  <span class="token property">border</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
  <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span>
  <span class="token property">top</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
  <span class="token property">left</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
  <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>
  <span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector">.c-videoplayer__link::after</span> <span class="token punctuation">{</span>
  <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span>
  <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span>
  <span class="token property">top</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span>
  <span class="token property">left</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span>
  <span class="token property">width</span><span class="token punctuation">:</span> 60px<span class="token punctuation">;</span>
  <span class="token property">height</span><span class="token punctuation">:</span> 60px<span class="token punctuation">;</span>
  <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translate3d</span><span class="token punctuation">(</span>-50%<span class="token punctuation">,</span> -50%<span class="token punctuation">,</span> 0<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span>../img/icon_play.svg<span class="token punctuation">)</span></span><span class="token punctuation">;</span>
  <span class="token property">background-position</span><span class="token punctuation">:</span> 50% 50%<span class="token punctuation">;</span>
  <span class="token property">background-repeat</span><span class="token punctuation">:</span> no-repeat<span class="token punctuation">;</span>
  <span class="token property">background-size</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span>

  <span class="token property">transition</span><span class="token punctuation">:</span> transform 0.2s ease-out<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token atrule"><span class="token rule">@media</span> all <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 500px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  <span class="token selector">.c-videoplayer__link::after</span> <span class="token punctuation">{</span>
    <span class="token property">width</span><span class="token punctuation">:</span> 72px<span class="token punctuation">;</span>
    <span class="token property">height</span><span class="token punctuation">:</span> 72px<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token selector">.c-videoplayer__link:hover::after,
.c-videoplayer__link:focus::after</span> <span class="token punctuation">{</span>
  <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translate3d</span><span class="token punctuation">(</span>-50%<span class="token punctuation">,</span> -50%<span class="token punctuation">,</span> 0<span class="token punctuation">)</span> <span class="token function">scale</span><span class="token punctuation">(</span>1.1<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector">.c-videoplayer__cover</span> <span class="token punctuation">{</span>
  <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span>
  <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>
  <span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>
  <span class="token property">object-fit</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span>
  <span class="token property">transition</span><span class="token punctuation">:</span> opacity 0.2s ease-out<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector">.c-videoplayer__link:hover > .c-videoplayer__cover,
.c-videoplayer__link:focus > .c-videoplayer__cover</span> <span class="token punctuation">{</span>
  <span class="token property">opacity</span><span class="token punctuation">:</span> 0.72<span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>We now have a working baseline in the form of an humble anchor tag wrapped around a responsive cover image and linking to the video on the relevant video service.</p>
<p>We can now add a layer of JavaScript to replace that link with a fully functional <code>iframe</code> when the link is clicked.</p>
<h2>A sprinkle of JS</h2>
<p>If we break down what our JavaScript code needs to accomplish, here is what we come up with:</p>
<ul>
<li>test if the browser supports the <code>template</code> element (and bail out if it does not)</li>
<li>use the values of the <code>data-video-service</code> and <code>data-video-id</code> attributes to create the relevant <code>src</code> value for the <code>iframe</code></li>
<li>grab the <code>template</code> and import its content</li>
<li>generate and add the relevant <code>src</code> value to the <code>iframe</code></li>
<li>replace the link with a functional iframe when the link is clicked</li>
</ul>
<pre class="language-js"><code class="language-js"><span class="token comment">/**
 * CSS selectors
 */</span>
<span class="token keyword">const</span> <span class="token constant">SELECTORS</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">player</span><span class="token operator">:</span> <span class="token string">".js-video-player"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">link</span><span class="token operator">:</span> <span class="token string">".js-video-link"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">template</span><span class="token operator">:</span> <span class="token string">".js-video-template"</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token comment">/**
 * Check if HTML templates are supprted
 * @returns {Boolean} is template tag supported
 */</span>
<span class="token keyword">function</span> <span class="token function">supportsTemplate</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token string">"content"</span> <span class="token keyword">in</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">"template"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">/**
 * Build iframe src value
 * @param {String} videoService
 * @param {String} videoId
 * @returns {String} src url for iframe
 */</span>
<span class="token keyword">function</span> <span class="token function">getIframeSrc</span><span class="token punctuation">(</span><span class="token parameter">videoService<span class="token punctuation">,</span> videoId</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">let</span> iframeSrc <span class="token operator">=</span> <span class="token string">""</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>videoService <span class="token operator">===</span> <span class="token string">"youtube"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    iframeSrc <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">https://www.youtube.com/embed/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>videoId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">?autoplay=1</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>videoService <span class="token operator">===</span> <span class="token string">"vimeo"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    iframeSrc <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">https://player.vimeo.com/video/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>videoId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">?color=e76c34&amp;amp;title=0&amp;amp;byline=0&amp;amp;portrait=0&amp;amp;autoplay=1</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">return</span> iframeSrc<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">/**
 * Get all players in the page
 * Swap the placeholder image and link for a video iframe
 */</span>
<span class="token keyword">function</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// cut the mustard test</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">supportsTemplate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"Your browser does not support the template tag"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token comment">// get all players</span>
  <span class="token keyword">const</span> players <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token constant">SELECTORS</span><span class="token punctuation">.</span>player<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// loop through players</span>
  players<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">player</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token comment">// get values</span>
    <span class="token keyword">const</span> service <span class="token operator">=</span> player<span class="token punctuation">.</span>dataset<span class="token punctuation">.</span>videoService<span class="token punctuation">;</span>
    <span class="token keyword">const</span> id <span class="token operator">=</span> player<span class="token punctuation">.</span>dataset<span class="token punctuation">.</span>videoId<span class="token punctuation">;</span>

    <span class="token comment">// checks</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>service <span class="token operator">!==</span> <span class="token string">"youtube"</span> <span class="token operator">&amp;&amp;</span> service <span class="token operator">!==</span> <span class="token string">"vimeo"</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>id<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>

    <span class="token comment">// prepare iframe src and check</span>
    <span class="token keyword">const</span> iframeSrc <span class="token operator">=</span> <span class="token function">getIframeSrc</span><span class="token punctuation">(</span>service<span class="token punctuation">,</span> id<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>iframeSrc <span class="token operator">===</span> <span class="token string">""</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>

    <span class="token comment">// get and import template</span>
    <span class="token keyword">const</span> template <span class="token operator">=</span> player<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token constant">SELECTORS</span><span class="token punctuation">.</span>template<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> templateContent <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">importNode</span><span class="token punctuation">(</span>template<span class="token punctuation">.</span>content<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// get iframe</span>
    <span class="token keyword">const</span> iframe <span class="token operator">=</span> templateContent<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"iframe"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// add src to iframe</span>
    iframe<span class="token punctuation">.</span>src <span class="token operator">=</span> iframeSrc<span class="token punctuation">;</span>

    <span class="token comment">// get link</span>
    <span class="token keyword">const</span> link <span class="token operator">=</span> player<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token constant">SELECTORS</span><span class="token punctuation">.</span>link<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// when link is clicked,</span>
    <span class="token comment">// replace link (and image) with the template content</span>
    link<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span>
      <span class="token string">"click"</span><span class="token punctuation">,</span>
      <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        event<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        link<span class="token punctuation">.</span><span class="token function">replaceWith</span><span class="token punctuation">(</span>templateContent<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token boolean">false</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> init<span class="token punctuation">;</span></code></pre>
<p>On the CMS or SSG side of things, we need the following pieces of data:</p>
<ul>
<li>a string to identify the service used to host the video</li>
<li>the ID of the video</li>
<li>the title of the video</li>
<li>a full URL to the video on Youtube or Vimeo (can be built from the ID and service)</li>
</ul>
<p>We now have a minimalist customizable and maintainable video player falling back to a link wrapped around a responsive image. We can also have a page with many of those players without loading several <code>iframe</code> and their content when that page is initially loaded.</p>
<p>The main downside of using this method is that, using mobile browsers, videos will not autoplay, which means users will need to make two clicks to play them. To me, that's a very reasonable tradeoff to make.</p>

        ]]>
        </description>
      </item>
    
      <item>
        <title>Modular element queries with Craft CMS</title>
        <link>https://www.webstoemp.com/blog/modular-element-queries-craft-cms/</link>
        <pubDate>Mon, 09 Nov 2020 00:00:00 +0000</pubDate>
        <guid>https://www.webstoemp.com/blog/modular-element-queries-craft-cms/</guid>
        <description>
        <![CDATA[
          <p>In Craft 3, elements queries parameters use dot syntax exclusively, which changed how to implement search and filters with modular element queries. Here is a quick rundown of how I approach it nowadays.</p>
          <h2>Then and now</h2>
<p>With Craft 2, <a href="/blog/manipulating-craft-elementcriteriamodel-with-twig/">my go-to approach</a> to build <a href="/blog/combined-searches-and-filters-craft-cms/">modular element queries</a> was to manipulate objects with Twig before passing them to element queries as parameters. Here is a basic example using categories and relations in Craft 2.</p>
<pre class="language-twig"><code class="language-twig"><span class="token twig language-twig"><span class="token comment">{# get category ID from URL parameter #}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> selectedCatId <span class="token operator">=</span> craft<span class="token punctuation">.</span>request<span class="token punctuation">.</span>getParam<span class="token punctuation">(</span><span class="token string"><span class="token punctuation">'</span>category<span class="token punctuation">'</span></span><span class="token punctuation">)</span> <span class="token operator">?</span><span class="token operator">?</span> <span class="token boolean">null</span> <span class="token delimiter punctuation">%}</span></span>

<span class="token twig language-twig"><span class="token comment">{# initial query parameters #}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> queryParams <span class="token operator">=</span> <span class="token punctuation">{</span>
  section<span class="token punctuation">:</span> <span class="token string"><span class="token punctuation">'</span>blog<span class="token punctuation">'</span></span><span class="token punctuation">,</span>
  order<span class="token punctuation">:</span> <span class="token string"><span class="token punctuation">'</span>postDate desc<span class="token punctuation">'</span></span><span class="token punctuation">,</span>
  limit<span class="token punctuation">:</span> <span class="token number">10</span>
<span class="token punctuation">}</span> <span class="token delimiter punctuation">%}</span></span>

<span class="token twig language-twig"><span class="token comment">{# add relatedTo if needed #}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> selectedCatId <span class="token delimiter punctuation">%}</span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> queryParams <span class="token operator">=</span> queryParams<span class="token operator">|</span>merge<span class="token punctuation">(</span><span class="token punctuation">{</span>
    relatedTo<span class="token punctuation">:</span> selectedCatId
  <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token delimiter punctuation">%}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>

<span class="token twig language-twig"><span class="token comment">{# execute query #}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> blogposts <span class="token operator">=</span> craft<span class="token punctuation">.</span>entries<span class="token punctuation">(</span>queryParams<span class="token punctuation">)</span><span class="token punctuation">.</span>find<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token delimiter punctuation">%}</span></span>
</code></pre>
<p>Since version 3, Craft favors the use of dot syntax and chained functions, which means we have to change our approach slightly. However, we can essentially accomplish the same thing using Twig's <code>{% do %}</code> tag.</p>
<p>The <a href="https://twig.symfony.com/doc/3.x/tags/do.html">Twig documentation</a> is quite succinct and could probably be fleshed but a little bit. Essentially, <code>do</code> executes whatever is passed to it but does not print or return any value, which is exactly what we need.</p>
<pre class="language-twig"><code class="language-twig"><span class="token twig language-twig"><span class="token comment">{# get category ID from URL parameter #}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> selectedCatId <span class="token operator">=</span> craft<span class="token punctuation">.</span>app<span class="token punctuation">.</span>request<span class="token punctuation">.</span>getParam<span class="token punctuation">(</span><span class="token string"><span class="token punctuation">"</span>category<span class="token punctuation">"</span></span><span class="token punctuation">)</span> <span class="token operator">?</span><span class="token operator">?</span> <span class="token boolean">null</span> <span class="token delimiter punctuation">%}</span></span>

<span class="token twig language-twig"><span class="token comment">{# initial query #}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> query <span class="token operator">=</span> craft<span class="token punctuation">.</span>entries<span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">.</span>section<span class="token punctuation">(</span><span class="token string"><span class="token punctuation">"</span>blog<span class="token punctuation">"</span></span><span class="token punctuation">)</span>
  <span class="token punctuation">.</span>orderBy<span class="token punctuation">(</span><span class="token string"><span class="token punctuation">"</span>postDate desc<span class="token punctuation">"</span></span><span class="token punctuation">)</span>
  <span class="token punctuation">.</span>limit<span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token delimiter punctuation">%}</span></span>

<span class="token twig language-twig"><span class="token comment">{# add relatedTo if needed #}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> selectedCatId <span class="token delimiter punctuation">%}</span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">do</span> query<span class="token punctuation">.</span>relatedTo<span class="token punctuation">(</span>selectedCatId<span class="token punctuation">)</span> <span class="token delimiter punctuation">%}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>

<span class="token twig language-twig"><span class="token comment">{# execute query #}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> blogposts <span class="token operator">=</span> query<span class="token punctuation">.</span>all<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token delimiter punctuation">%}</span></span>
</code></pre>
<h2>Cumulative filters and search</h2>
<p>Here is a slightly more advanced example involving categories, publication years and search.</p>
<p>We want a paginated list of blogposts and the ability to filter them by publication year, by categories and also be able to search on titles. The search and filters should be cumulative. For example, we want to be able to search all blogposts published in 2016, belonging to the &quot;Gardening&quot; category and that have &quot;Brad&quot; in their title.</p>
<p>Here is the plan:</p>
<ol>
<li>create a form using <code>post</code> or <code>get</code> to set parameters and values (use <code>get</code> if you want the parameters and values to be visible in the URL)</li>
<li>retrieve values using <code>craft.app.request.getParam()</code></li>
<li>create a base query</li>
<li>add parameters to base query as needed using retrieved values</li>
</ol>
<pre class="language-twig"><code class="language-twig"><span class="token twig language-twig"><span class="token comment">{# -------------------------------------------------
layout
------------------------------------------------- #}</span></span>

<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">extends</span> <span class="token string"><span class="token punctuation">"</span>_layouts/base.twig<span class="token punctuation">"</span></span> <span class="token delimiter punctuation">%}</span></span>


<span class="token twig language-twig"><span class="token comment">{# -------------------------------------------------
variables
------------------------------------------------- #}</span></span>

<span class="token twig language-twig"><span class="token comment">{# get parameters from request and set default values #}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> searchTerm <span class="token operator">=</span> craft<span class="token punctuation">.</span>app<span class="token punctuation">.</span>request<span class="token punctuation">.</span>getParam<span class="token punctuation">(</span><span class="token string"><span class="token punctuation">"</span>search<span class="token punctuation">"</span></span><span class="token punctuation">)</span> <span class="token operator">?</span><span class="token operator">?</span> <span class="token boolean">null</span> <span class="token delimiter punctuation">%}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> selectedCatId <span class="token operator">=</span> craft<span class="token punctuation">.</span>app<span class="token punctuation">.</span>request<span class="token punctuation">.</span>getParam<span class="token punctuation">(</span><span class="token string"><span class="token punctuation">"</span>category<span class="token punctuation">"</span></span><span class="token punctuation">)</span> <span class="token operator">?</span><span class="token operator">?</span> <span class="token boolean">null</span> <span class="token delimiter punctuation">%}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> selectedYear <span class="token operator">=</span> craft<span class="token punctuation">.</span>app<span class="token punctuation">.</span>request<span class="token punctuation">.</span>getParam<span class="token punctuation">(</span><span class="token string"><span class="token punctuation">"</span>year<span class="token punctuation">"</span></span><span class="token punctuation">)</span> <span class="token operator">?</span><span class="token operator">?</span> <span class="token boolean">null</span> <span class="token delimiter punctuation">%}</span></span>

<span class="token twig language-twig"><span class="token comment">{# All blogposts query #}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> allBlogpostsQuery <span class="token operator">=</span> craft<span class="token punctuation">.</span>entries<span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">.</span>section<span class="token punctuation">(</span><span class="token string"><span class="token punctuation">"</span>blogposts<span class="token punctuation">"</span></span><span class="token punctuation">)</span>
  <span class="token punctuation">.</span>orderBy<span class="token punctuation">(</span><span class="token string"><span class="token punctuation">"</span>postDate desc<span class="token punctuation">"</span></span><span class="token punctuation">)</span>
  <span class="token punctuation">.</span>limit<span class="token punctuation">(</span><span class="token boolean">null</span><span class="token punctuation">)</span> <span class="token delimiter punctuation">%}</span></span>

<span class="token twig language-twig"><span class="token comment">{# all blogposts #}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> allBlogposts <span class="token operator">=</span> allBlogpostsQuery<span class="token punctuation">.</span>all<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token delimiter punctuation">%}</span></span>

<span class="token twig language-twig"><span class="token comment">{# total blogposts #}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> totalBlogposts <span class="token operator">=</span> allBlogpostsQuery<span class="token punctuation">.</span>count<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token delimiter punctuation">%}</span></span>

<span class="token twig language-twig"><span class="token comment">{# get all categories #}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> allBlogCategories <span class="token operator">=</span> craft<span class="token punctuation">.</span>categories<span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">.</span>group<span class="token punctuation">(</span><span class="token string"><span class="token punctuation">"</span>blogCategories<span class="token punctuation">"</span></span><span class="token punctuation">)</span>
  <span class="token punctuation">.</span>orderBy<span class="token punctuation">(</span><span class="token string"><span class="token punctuation">"</span>title<span class="token punctuation">"</span></span><span class="token punctuation">)</span>
  <span class="token punctuation">.</span>relatedTo<span class="token punctuation">(</span>allBlogposts<span class="token punctuation">)</span>
  <span class="token punctuation">.</span>all<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token delimiter punctuation">%}</span></span>

<span class="token twig language-twig"><span class="token comment">{# Years with blogposts published #}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> years <span class="token operator">=</span> allBlogposts<span class="token operator">|</span>group<span class="token punctuation">(</span><span class="token string"><span class="token punctuation">"</span>postDate|date('Y')<span class="token punctuation">"</span></span><span class="token punctuation">)</span><span class="token operator">|</span>keys <span class="token delimiter punctuation">%}</span></span>

<span class="token twig language-twig"><span class="token comment">{# override limit for pagination #}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> filteredBlogposts <span class="token operator">=</span> allBlogpostsQuery<span class="token punctuation">.</span>limit<span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token delimiter punctuation">%}</span></span>

<span class="token twig language-twig"><span class="token comment">{# add before/after params if needed #}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> selectedYear <span class="token delimiter punctuation">%}</span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">do</span> filteredBlogposts<span class="token punctuation">.</span>after<span class="token punctuation">(</span>selectedYear<span class="token punctuation">)</span><span class="token punctuation">.</span>before<span class="token punctuation">(</span>selectedYear <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token delimiter punctuation">%}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>

<span class="token twig language-twig"><span class="token comment">{# add relatedTo param if needed #}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> selectedCatId <span class="token delimiter punctuation">%}</span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">do</span> filteredBlogposts<span class="token punctuation">.</span>relatedTo<span class="token punctuation">(</span>selectedCatId<span class="token punctuation">)</span> <span class="token delimiter punctuation">%}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>

<span class="token twig language-twig"><span class="token comment">{# add search param if needed #}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> searchTerm <span class="token delimiter punctuation">%}</span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">do</span> filteredBlogposts<span class="token punctuation">.</span>search<span class="token punctuation">(</span><span class="token string"><span class="token punctuation">'</span>title:<span class="token punctuation">'</span></span> <span class="token operator">~</span> searchTerm<span class="token punctuation">)</span> <span class="token delimiter punctuation">%}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>

<span class="token twig language-twig"><span class="token comment">{# count results of modular query #}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> results <span class="token operator">=</span> filteredBlogposts<span class="token punctuation">.</span>count<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token delimiter punctuation">%}</span></span>


<span class="token twig language-twig"><span class="token comment">{# -------------------------------------------------
template
------------------------------------------------- #}</span></span>

<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">block</span> content <span class="token delimiter punctuation">%}</span></span>
  <span class="token twig language-twig"><span class="token comment">{# search form #}</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span> <span class="token attr-name">action</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> url<span class="token punctuation">(</span>craft<span class="token punctuation">.</span>app<span class="token punctuation">.</span>request<span class="token punctuation">.</span>pathInfo<span class="token punctuation">)</span> <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">"</span></span> <span class="token attr-name">method</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>post<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">></span></span>Search blogposts<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span>

    <span class="token twig language-twig"><span class="token comment">{# years #}</span></span>
    <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> years<span class="token operator">|</span>length <span class="token delimiter punctuation">%}</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">></span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>years<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Year: <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span>
        <span class="token twig language-twig"><span class="token comment">{# output years #}</span></span>
        <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">for</span> year <span class="token operator">in</span> years <span class="token delimiter punctuation">%}</span></span>
          <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> loop<span class="token punctuation">.</span>first <span class="token delimiter punctuation">%}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>select</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>year<span class="token punctuation">"</span></span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>years<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>

            <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> loop<span class="token punctuation">.</span>first <span class="token delimiter punctuation">%}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span>All<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>
            <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> selected <span class="token operator">=</span> <span class="token punctuation">(</span>selectedYear <span class="token operator">==</span> year<span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token string"><span class="token punctuation">"</span>selected<span class="token punctuation">"</span></span> <span class="token punctuation">:</span> <span class="token string"><span class="token punctuation">"</span><span class="token punctuation">"</span></span> <span class="token delimiter punctuation">%}</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> year <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> selected <span class="token delimiter punctuation">}}</span></span></span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> year <span class="token delimiter punctuation">}}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span>

          <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> loop<span class="token punctuation">.</span>last <span class="token delimiter punctuation">%}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>select</span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>
        <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endfor</span> <span class="token delimiter punctuation">%}</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
    <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>

    <span class="token twig language-twig"><span class="token comment">{# categories #}</span></span>
    <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> allBlogCategories<span class="token operator">|</span>length <span class="token delimiter punctuation">%}</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">></span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>cat<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Category: <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span>
        <span class="token twig language-twig"><span class="token comment">{# output categories #}</span></span>
        <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">for</span> category <span class="token operator">in</span> allBlogCategories <span class="token delimiter punctuation">%}</span></span>
          <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> loop<span class="token punctuation">.</span>first <span class="token delimiter punctuation">%}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>select</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>category<span class="token punctuation">"</span></span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>cat<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>

            <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> loop<span class="token punctuation">.</span>first <span class="token delimiter punctuation">%}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span>All<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>
            <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> selected <span class="token operator">=</span> <span class="token punctuation">(</span>selectedCatId <span class="token operator">==</span> category<span class="token punctuation">.</span>id<span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token string"><span class="token punctuation">"</span>selected<span class="token punctuation">"</span></span> <span class="token punctuation">:</span> <span class="token string"><span class="token punctuation">"</span><span class="token punctuation">"</span></span> <span class="token delimiter punctuation">%}</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> category<span class="token punctuation">.</span>id <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> selected <span class="token delimiter punctuation">}}</span></span></span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> category<span class="token punctuation">.</span>title <span class="token delimiter punctuation">}}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span>

          <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> loop<span class="token punctuation">.</span>last <span class="token delimiter punctuation">%}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>select</span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>
        <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endfor</span> <span class="token delimiter punctuation">%}</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
    <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>

    <span class="token twig language-twig"><span class="token comment">{# search #}</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">></span></span>
      <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> searchValue <span class="token operator">=</span> searchTerm <span class="token operator">?</span><span class="token operator">?</span> <span class="token string"><span class="token punctuation">"</span><span class="token punctuation">"</span></span> <span class="token delimiter punctuation">%}</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>q<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Search: <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>search<span class="token punctuation">"</span></span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>q<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>search<span class="token punctuation">"</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> searchValue <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>submit<span class="token punctuation">"</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Search entries<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>

  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">></span></span>

  <span class="token twig language-twig"><span class="token comment">{# display results / total #}</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">></span></span>Results<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> results <span class="token delimiter punctuation">}}</span></span> result<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> results <span class="token operator">></span> <span class="token number">1</span> <span class="token delimiter punctuation">%}</span></span>s<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span> found in <span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> totalBlogposts <span class="token delimiter punctuation">}}</span></span> blogposts.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span>

  <span class="token twig language-twig"><span class="token comment">{# paginate filtered entries #}</span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">paginate</span> filteredBlogposts as pagination<span class="token punctuation">,</span> entries <span class="token delimiter punctuation">%}</span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">for</span> entry <span class="token operator">in</span> entries <span class="token delimiter punctuation">%}</span></span>
    <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> loop<span class="token punctuation">.</span>first <span class="token delimiter punctuation">%}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>

    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>article</span><span class="token punctuation">></span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>time</span> <span class="token attr-name">datetime</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> entry<span class="token punctuation">.</span>postDate<span class="token operator">|</span>date<span class="token punctuation">(</span><span class="token string"><span class="token punctuation">'</span>Y-m-d<span class="token punctuation">'</span></span><span class="token punctuation">)</span> <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> entry<span class="token punctuation">.</span>postDate<span class="token operator">|</span>date<span class="token punctuation">(</span><span class="token string"><span class="token punctuation">'</span>F d, Y<span class="token punctuation">'</span></span><span class="token punctuation">)</span> <span class="token delimiter punctuation">}}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>time</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h3</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> entry<span class="token punctuation">.</span>url <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> entry<span class="token punctuation">.</span>title <span class="token delimiter punctuation">}}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h3</span><span class="token punctuation">></span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> entry<span class="token punctuation">.</span>commonSummary <span class="token delimiter punctuation">}}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span>

    <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> loop<span class="token punctuation">.</span>last <span class="token delimiter punctuation">%}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endfor</span> <span class="token delimiter punctuation">%}</span></span>

  <span class="token twig language-twig"><span class="token comment">{# pagination #}</span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> pagination<span class="token punctuation">.</span>totalPages <span class="token operator">></span> <span class="token number">1</span> <span class="token delimiter punctuation">%}</span></span>
    <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> pagination<span class="token punctuation">.</span>prevUrl <span class="token delimiter punctuation">%}</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> pagination<span class="token punctuation">.</span>prevUrl <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Previous Page<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span>
    <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>
    <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> pagination<span class="token punctuation">.</span>nextUrl <span class="token delimiter punctuation">%}</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> pagination<span class="token punctuation">.</span>nextUrl <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Next Page<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span>
    <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>

<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endblock</span> <span class="token delimiter punctuation">%}</span></span>
</code></pre>
<h2>Mainly a shift in syntax</h2>
<p>In most cases, modular element queries can be created using <code>{% do %}</code> and dot syntax instead of manipulating objects with Twig before feeding them to element queries as parameters. I would even argue that the code is a bit more legible.</p>
<p>The most important thing for me is that, combined with Twig as a templating language, Craft remains a very flexible and, dare I say it, elegant tool to retrieve and display data as well as to structure it.</p>

        ]]>
        </description>
      </item>
    
      <item>
        <title>Using Github actions to deploy Craft CMS websites</title>
        <link>https://www.webstoemp.com/blog/github-actions-deploy-craftcms/</link>
        <pubDate>Sat, 10 Oct 2020 00:00:00 +0000</pubDate>
        <guid>https://www.webstoemp.com/blog/github-actions-deploy-craftcms/</guid>
        <description>
        <![CDATA[
          <p>While I regularily use services like Buddy or DeployHQ to build and deploy Craft CMS websites, I wanted to experiment with Github Actions. Here is what I came up with and why.</p>
          <h2>Why Github Actions?</h2>
<p>Services like <a href="https://buddy.works/">Buddy</a> or <a href="https://www.deployhq.com/">DeployHQ</a> are really powerful. They allow you to setup complex build and deployment pipelines like <a href="https://nystudio107.com/blog/executing-atomic-deployments">atomic deployments</a> using a nice GUI interface.</p>
<p>That being said, that power and ease of use come with a price tag which, in my opinion, can become quite high for basic projects. Most of the small to medium websites I work on do not necessarily need that level of power and complexity.</p>
<p>Since I generally use Github to host my repositories, being able to setup a basic deployment pipeline with a single YAML file for free seemed like an alternative worth investigating.</p>
<h2>Deployment steps</h2>
<p>My goal was to use <a href="https://docs.github.com/en/free-pro-team@latest/actions">Github Actions</a> to create a basic build and deployment pipeline for Craft CMS websites. Here are the steps I needed:</p>
<ol>
<li>Pull repository in container</li>
<li>Load SSH key (used by rsync)</li>
<li>Use specific version of Node</li>
<li>Install PHP dependencies with composer</li>
<li>Install Node dependencies</li>
<li>Build assets using NPM scripts</li>
<li>Deploy everything to the production server with rsync</li>
<li>Log into the production server and execute Craft commands</li>
</ol>
<h2>Security</h2>
<p>Since this workflow needs to connect to servers using SSH, we have to create three <a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets">Github Secrets</a> in the repository we want to deploy:</p>
<ul>
<li><code>SSH_HOST</code>: the IP address of the server we need to connect to</li>
<li><code>SSH_USER</code>: the SSH user</li>
<li><code>SSH_KEY</code>: the SSH key. The recommended option here is to create a dedicated key pair for the repository rather than using your personal key. The public key will be stored on the server, while the private key will be stored in the Github Secret.</li>
</ul>
<p>We will now be able to reference those secrets in our workflow using the following syntax: <code>${{ secrets.MYSECRET }}</code></p>
<h2>The end result</h2>
<p>After reading a couple of helpful <a href="https://craftcms.com/knowledge-base/deployment-best-practices">articles</a> and <a href="https://blog.fortrabbit.com/how-to-use-github-actions">blogposts</a>, here is the <code>craftdeploy.yaml</code> file I came up with.</p>
<pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">name</span><span class="token punctuation">:</span> Craft CMS deployments

<span class="token key atrule">on</span><span class="token punctuation">:</span>
  <span class="token key atrule">push</span><span class="token punctuation">:</span>
    <span class="token key atrule">branches</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>master<span class="token punctuation">]</span>

<span class="token key atrule">jobs</span><span class="token punctuation">:</span>
  <span class="token key atrule">build-and-deploy</span><span class="token punctuation">:</span>
    <span class="token key atrule">runs-on</span><span class="token punctuation">:</span> ubuntu<span class="token punctuation">-</span>latest

    <span class="token key atrule">steps</span><span class="token punctuation">:</span>
      <span class="token comment"># Pull repository into the current pipeline</span>
      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Pull repository
        <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/checkout@v2

      <span class="token comment"># Setup container with private SSH Key (used by rsync)</span>
      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Load private SSH key
        <span class="token key atrule">uses</span><span class="token punctuation">:</span> webfactory/ssh<span class="token punctuation">-</span>agent@v0.4.1
        <span class="token key atrule">with</span><span class="token punctuation">:</span>
          <span class="token key atrule">ssh-private-key</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> secrets.SSH_KEY <span class="token punctuation">}</span><span class="token punctuation">}</span>

      <span class="token comment"># Use a specific version of Node</span>
      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Setup Node
        <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/setup<span class="token punctuation">-</span>node@v1
        <span class="token key atrule">with</span><span class="token punctuation">:</span>
          <span class="token key atrule">node-version</span><span class="token punctuation">:</span> <span class="token string">"12.x"</span>

      <span class="token comment"># Install PHP dependencies</span>
      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Composer install
        <span class="token key atrule">run</span><span class="token punctuation">:</span> composer install <span class="token punctuation">-</span><span class="token punctuation">-</span>no<span class="token punctuation">-</span>interaction <span class="token punctuation">-</span><span class="token punctuation">-</span>no<span class="token punctuation">-</span>progress <span class="token punctuation">-</span><span class="token punctuation">-</span>no<span class="token punctuation">-</span>suggest <span class="token punctuation">-</span><span class="token punctuation">-</span>optimize<span class="token punctuation">-</span>autoloader

      <span class="token comment"># Install NPM dependencies</span>
      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> NPM install
        <span class="token key atrule">run</span><span class="token punctuation">:</span> npm ci

      <span class="token comment"># Build assets using NPM scripts</span>
      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Build assets
        <span class="token key atrule">run</span><span class="token punctuation">:</span> npm run build

      <span class="token comment"># RSYNC</span>
      <span class="token comment"># - rsync [options] ~/localdir ssh_user@ssh_host:destination_directory</span>
      <span class="token comment"># - exclude web/uploads is there to avoid deleting user uploaded files w/ --delete-after</span>
      <span class="token comment"># - StrictHostKeyChecking=no will automatically add new host keys to the user known hosts files.</span>
      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Deploy with rsync
        <span class="token key atrule">run</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">
          rsync -azh --delete-after --exclude={"/web/uploads/","/node_modules/","/.git/","/.github/"} -e "ssh -o StrictHostKeyChecking=no" ./ ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:~/</span>

      <span class="token comment"># execute Craft commands on remote server</span>
      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Execute SSH commmands on remote server
        <span class="token key atrule">uses</span><span class="token punctuation">:</span> appleboy/ssh<span class="token punctuation">-</span>action@master
        <span class="token key atrule">with</span><span class="token punctuation">:</span>
          <span class="token key atrule">host</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> secrets.SSH_HOST <span class="token punctuation">}</span><span class="token punctuation">}</span>
          <span class="token key atrule">username</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> secrets.SSH_USER <span class="token punctuation">}</span><span class="token punctuation">}</span>
          <span class="token key atrule">key</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> secrets.SSH_KEY <span class="token punctuation">}</span><span class="token punctuation">}</span>
          <span class="token key atrule">script</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">
            chmod a+x craft
            php craft backup/db
            php craft migrate/all
            php craft project-config/apply
            php craft clear-caches/all</span></code></pre>
<p>By creating a <code>craftdeploy.yaml</code> file in <code>./.github/workflows/</code> and by setting the push event to the <code>master</code> branch, we ensure that this workflow will run every time we push code to the master branch of our repository.</p>
<p>We use <code>rsync</code> to deploy files from the Github container to the production server. In the script above, we are copying files from the root of our local install to the root of a remote server we connect to via SSH.</p>
<p><strong>Warning</strong>: <code>rsync</code> is a very powerful tool and can be quite destructive if you mess up your command. If you are new to it (and event if you aren't), <a href="https://linux.die.net/man/1/rsync">consult the man page</a> to see all available options and test it using the terminal. The <code>--dry-run</code> option, in particular, is very useful. It will not do anything and will print out the files that would have been transferred in your terminal.</p>
<p>I have been testing this script with several Craft projects for a couple of months and have not experienced any issue so far. The workflow is consistently taking between 1 and 3 minutes to run, which I find rather reasonable.</p>
<p>Granted, this is a very basic workflow and it can certainly be improved upon. Feel free to ping me me if you have ideas.</p>
<p>While I will probably continue to use Buddy and the likes for more complex needs, I am also quite happy with the convenient, cheap and reliable option Github Actions provides.</p>

        ]]>
        </description>
      </item>
    
      <item>
        <title>Performant data fetching with promises and Eleventy</title>
        <link>https://www.webstoemp.com/blog/performant-data-fetching-promises-eleventy/</link>
        <pubDate>Mon, 08 Jun 2020 00:00:00 +0000</pubDate>
        <guid>https://www.webstoemp.com/blog/performant-data-fetching-promises-eleventy/</guid>
        <description>
        <![CDATA[
          <p>Fetching a whole bunch of data from APIs at build time can be an intensive process. Getting that data in a performant way and caching it locally is an important part of Jamstack projects.</p>
          <h2>Don't use await in loops</h2>
<p>One of my older post is about <a href="https://www.webstoemp.com/blog/headless-cms-graphql-api-eleventy/">fetching data from a GraphQL API with Eleventy</a>. While the example code I wrote works, it does not retrieve data in a very performant way.</p>
<p>The problem is that I use await in a while loop (like a dummy). The direct consequence of this is that the API calls happen sequentially, as <a href="https://www.learnwithjason.dev/blog/keep-async-await-from-blocking-execution/">brilliantly explained by Jason Lengstorf</a>. Each call has to wait for the previous one to finish instead of running in parallel.</p>
<p>Retrieving all records from an API at build time is a pretty common use case with static site generators and headless CMSes. Generally, such APIs are limiting the number of records you can get in one single query and will make you use pagination.</p>
<p>Here is a rough outline of how to deal with this use case in a performant manner:</p>
<ol>
<li>Make a request to the API to retrieve a first batch of data as well as the total number of items to retrieve</li>
<li>Calculate the number of additional API requests we need to retrieve all data</li>
<li>If we need to make more calls, store those additional requests as promises</li>
<li>Use <code>Promise.all</code> to execute all additional requests in parallel</li>
<li>Sort our data if needed</li>
<li>Store all data in a cache file</li>
</ol>
<h2>Using promises and caching</h2>
<p>We will use the <a href="https://www.npmjs.com/package/axios">Axios</a> package and pagination with a limit of 1 on the <a href="https://jsonplaceholder.typicode.com/">JSON Placeholder API</a> in this example. That will allow us to make 99 API calls in parallel. We will also use the <a href="https://www.npmjs.com/package/flat-cache">flat-cache NPM package</a> to store our data for speedier local development.</p>
<p>Now, on with the code! In our Eleventy install, we create a <code>blogposts.js</code> file in our <code>_data</code> folder.</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// required packages</span>
<span class="token keyword">const</span> path <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"path"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> axios <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"axios"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> flatCache <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"flat-cache"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// Config</span>
<span class="token keyword">const</span> <span class="token constant">ITEMS_PER_REQUEST</span> <span class="token operator">=</span> <span class="token number">20</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token constant">CACHE_KEY</span> <span class="token operator">=</span> <span class="token string">"blogposts"</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token constant">CACHE_FOLDER</span> <span class="token operator">=</span> path<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token string">"./.cache"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token constant">CACHE_FILE</span> <span class="token operator">=</span> <span class="token string">"blogposts.json"</span><span class="token punctuation">;</span>

<span class="token comment">/**
 * Request blogposts
 * @param {Int} skipRecords - number or records to skip
 * @return {Object} - Total number of items and API data
 */</span>
<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">requestPosts</span><span class="token punctuation">(</span><span class="token parameter">skipRecords <span class="token operator">=</span> <span class="token number">0</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">try</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> url <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">https://jsonplaceholder.typicode.com/posts?_start=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>skipRecords<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&amp;_limit=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">ITEMS_PER_REQUEST</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">axios</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">method</span><span class="token operator">:</span> <span class="token string">"get"</span><span class="token punctuation">,</span>
      <span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token string-property property">"Accept-Encoding"</span><span class="token operator">:</span> <span class="token string">"gzip,deflate,compress"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// return the total number of items to fetch and the data</span>
    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">total</span><span class="token operator">:</span> <span class="token function">parseInt</span><span class="token punctuation">(</span>response<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">"x-total-count"</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token literal-property property">data</span><span class="token operator">:</span> response<span class="token punctuation">.</span>data<span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token comment">/**
 * Get all posts
 * - check if we have a cache
 * - if not make api requests and create cache
 * @return {Array} - array of API data (from cache if there is one or from API)
 */</span>
<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getAllPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// load cache</span>
  <span class="token keyword">const</span> cache <span class="token operator">=</span> flatCache<span class="token punctuation">.</span><span class="token function">load</span><span class="token punctuation">(</span><span class="token constant">CACHE_FILE</span><span class="token punctuation">,</span> <span class="token constant">CACHE_FOLDER</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> cachedItems <span class="token operator">=</span> cache<span class="token punctuation">.</span><span class="token function">getKey</span><span class="token punctuation">(</span><span class="token constant">CACHE_KEY</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// if we have a cache, return cached data</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>cachedItems<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"Blogposts from cache"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> cachedItems<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token comment">// if we do not, make queries</span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"Blogposts from API"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// variables</span>
  <span class="token keyword">const</span> requests <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> apiData <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> additionalRequests <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>

  <span class="token comment">// make first request and marge results with array</span>
  <span class="token keyword">const</span> request <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">requestPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  apiData<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token operator">...</span>request<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// calculate how many additional requests we need</span>
  additionalRequests <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">ceil</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>total <span class="token operator">/</span> <span class="token constant">ITEMS_PER_REQUEST</span><span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span>

  <span class="token comment">// create additional requests</span>
  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> additionalRequests<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> start <span class="token operator">=</span> i <span class="token operator">*</span> <span class="token constant">ITEMS_PER_REQUEST</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> request <span class="token operator">=</span> <span class="token function">requestPosts</span><span class="token punctuation">(</span>start<span class="token punctuation">)</span><span class="token punctuation">;</span>
    requests<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token comment">// resolve all additional requests in parallel</span>
  <span class="token keyword">const</span> allResponses <span class="token operator">=</span> <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span>requests<span class="token punctuation">)</span><span class="token punctuation">;</span>
  allResponses<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">response</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    apiData<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token operator">...</span>response<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// sort data as needed</span>
  apiData<span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> a<span class="token punctuation">.</span>id <span class="token operator">-</span> b<span class="token punctuation">.</span>id<span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// set and save cache</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>apiData<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    cache<span class="token punctuation">.</span><span class="token function">setKey</span><span class="token punctuation">(</span><span class="token constant">CACHE_KEY</span><span class="token punctuation">,</span> apiData<span class="token punctuation">)</span><span class="token punctuation">;</span>
    cache<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token comment">// return data</span>
  <span class="token keyword">return</span> apiData<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// export for 11ty</span>
module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token function">getAllPosts</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Data from our API is now available in our templates under the <code>blogposts</code> key. We can create our list of blogposts and, <a href="https://www.11ty.dev/docs/pages-from-data/">using <code>pagination</code> with a <code>size</code> of <code>1</code></a>, we can also create all our blogposts detail pages.</p>
<p>By having requests running in parallel, we fetch our data in a performant manner. When comparing this approach to the sequential one used in my previous blogpost with the same API, performance is eight to ten times faster.</p>
<p>Storing that data in a static cache allows us to not constantly hit the API during development. I usually delete that cache as part of my build process. That way, I am sure that to get fresh data from the API or from the headless CMS every time the site is built.</p>
<p>If you need your caching to be more versatile and configurable, check out the <a href="https://github.com/11ty/eleventy-cache-assets"><code>eleventy-cache-assets</code></a> plugin by Zach himself.</p>

        ]]>
        </description>
      </item>
    
      <item>
        <title>Basic custom taxonomies with Eleventy</title>
        <link>https://www.webstoemp.com/blog/basic-custom-taxonomies-with-eleventy/</link>
        <pubDate>Wed, 04 Mar 2020 00:00:00 +0000</pubDate>
        <guid>https://www.webstoemp.com/blog/basic-custom-taxonomies-with-eleventy/</guid>
        <description>
        <![CDATA[
          <p>Eleventy comes with a built-in tagging system. For a recent project, I wanted to use my own category system, which led me to dive a bit deeper into extending and configuring Eleventy.</p>
          <h2>Native approach using tags</h2>
<p>The <a href="https://www.11ty.dev/docs/collections/">tag system</a> Eleventy comes with allows you to group content items into collections automatically by specifying a <code>tags</code> key in those content items' front matters. For example, a post about milkshakes tagged as &quot;foods&quot; and &quot;drinks&quot; will appear in both <code>collections.foods</code> and <code>collections.drinks</code>.</p>
<p>You can then use pagination with a <code>size</code> of <code>1</code> to <a href="https://www.11ty.dev/docs/quicktips/tag-pages/">automatically generate tag pages</a> that will list all content items belonging to that tag's collection. Paginating the posts listed on those tag pages is another story, but we'll get to that.</p>
<h2>Using custom categories</h2>
<p>I generally don't use tags to create my collections. I prefer to use folders and the <code>getFilteredByGlob( glob )</code> API method to do so, but I often need basic custom taxonomies to categorize my content.</p>
<p>Eleventy being the flexible tool that it is, creating basic custom taxonomies is relatively straightforward.</p>
<p>Here is what we would need to create a custom &quot;categories&quot; taxonomy for our blogposts:</p>
<ol>
<li>a <code>categories</code> key and an array of values in the front matters of each of our blogposts.</li>
<li>an array of all unique <code>categories</code> values used in each blogpost front matter.</li>
<li>a way to filter all blogposts with a specific value in their front matters under the <code>categories</code> key.</li>
</ol>
<h3>Add keys and values to front matter</h3>
<p>We'll start by adding a <code>categories</code> key to all blogposts and by specifying an array of categories for each of them.</p>
<pre class="language-text"><code class="language-text">---
title: "This is an amazing travel blogpost with categories"
excerpt: "This blogpost is truly amazing and is listed under different categories, read on if you feel so inclined."
categories:
- awesomeness
- travel
---</code></pre>
<h3>Automatically create a collection of unique categories</h3>
<p>The easiest route here is to use the collections API in <code>.eleventy.js</code>. As a first step, we'll create a collection of all our blogposts.</p>
<pre class="language-js"><code class="language-js">module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">eleventyConfig</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// blogposts collection</span>
  eleventyConfig<span class="token punctuation">.</span><span class="token function">addCollection</span><span class="token punctuation">(</span><span class="token string">"blogposts"</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">collection</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> collection<span class="token punctuation">.</span><span class="token function">getFilteredByGlob</span><span class="token punctuation">(</span><span class="token string">"./src/blogposts/*.md"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">reverse</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Once we have done that, we need a collection of all categories used in all of our blogposts, so we can use it to generate our categories archive pages. Here are the steps we will follow:</p>
<ol>
<li>Loop over every item in our <code>blogposts</code> collection, collect all arrays of categories and flatten them in one big array.</li>
<li>Remove capitalisation.</li>
<li>Remove duplicates.</li>
<li>Sort categories alphabetically.</li>
<li>Create a slug for each category using the slugify package.</li>
</ol>
<p>We will use <code>lodash</code> to flatten the nested array and for some other tasks, so we need to install and import that as well.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> lodash <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"lodash"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> slugify <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"slugify"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">/**
 * Get all unique key values from a collection
 *
 * @param {Array} collectionArray - collection to loop through
 * @param {String} key - key to get values from
 */</span>
<span class="token keyword">function</span> <span class="token function">getAllKeyValues</span><span class="token punctuation">(</span><span class="token parameter">collectionArray<span class="token punctuation">,</span> key</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// get all values from collection</span>
  <span class="token keyword">let</span> allValues <span class="token operator">=</span> collectionArray<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">let</span> values <span class="token operator">=</span> item<span class="token punctuation">.</span>data<span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">?</span> item<span class="token punctuation">.</span>data<span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> values<span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// flatten values array</span>
  allValues <span class="token operator">=</span> lodash<span class="token punctuation">.</span><span class="token function">flattenDeep</span><span class="token punctuation">(</span>allValues<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token comment">// to lowercase</span>
  allValues <span class="token operator">=</span> allValues<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token operator">=></span> item<span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token comment">// remove duplicates</span>
  allValues <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token operator">...</span><span class="token keyword">new</span> <span class="token class-name">Set</span><span class="token punctuation">(</span>allValues<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token comment">// order alphabetically</span>
  allValues <span class="token operator">=</span> allValues<span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> a<span class="token punctuation">.</span><span class="token function">localeCompare</span><span class="token punctuation">(</span>b<span class="token punctuation">,</span> <span class="token string">"en"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">sensitivity</span><span class="token operator">:</span> <span class="token string">"base"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token comment">// return</span>
  <span class="token keyword">return</span> allValues<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">/**
 * Transform a string into a slug
 * Uses slugify package
 *
 * @param {String} str - string to slugify
 */</span>
<span class="token keyword">function</span> <span class="token function">strToSlug</span><span class="token punctuation">(</span><span class="token parameter">str</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> options <span class="token operator">=</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">replacement</span><span class="token operator">:</span> <span class="token string">"-"</span><span class="token punctuation">,</span>
    <span class="token literal-property property">remove</span><span class="token operator">:</span> <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">[&amp;,+()$~%.'":*?&lt;>{}]</span><span class="token regex-delimiter">/</span><span class="token regex-flags">g</span></span><span class="token punctuation">,</span>
    <span class="token literal-property property">lower</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token function">slugify</span><span class="token punctuation">(</span>str<span class="token punctuation">,</span> options<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">eleventyConfig</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// create blog collection</span>
  eleventyConfig<span class="token punctuation">.</span><span class="token function">addCollection</span><span class="token punctuation">(</span><span class="token string">"blogposts"</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">collection</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> collection<span class="token punctuation">.</span><span class="token function">getFilteredByGlob</span><span class="token punctuation">(</span><span class="token string">"./src/blogposts/*.md"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">reverse</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// create blog categories collection</span>
  eleventyConfig<span class="token punctuation">.</span><span class="token function">addCollection</span><span class="token punctuation">(</span><span class="token string">"blogCategories"</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">collection</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">let</span> allCategories <span class="token operator">=</span> <span class="token function">getAllKeyValues</span><span class="token punctuation">(</span>
      collection<span class="token punctuation">.</span><span class="token function">getFilteredByGlob</span><span class="token punctuation">(</span><span class="token string">"./src/blogposts/*.md"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token string">"categories"</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">let</span> blogCategories <span class="token operator">=</span> allCategories<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">category</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
      <span class="token literal-property property">title</span><span class="token operator">:</span> category<span class="token punctuation">,</span>
      <span class="token literal-property property">slug</span><span class="token operator">:</span> <span class="token function">strToSlug</span><span class="token punctuation">(</span>category<span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> blogCategories<span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// filters</span>
  eleventyConfig<span class="token punctuation">.</span><span class="token function">addFilter</span><span class="token punctuation">(</span><span class="token string">"date"</span><span class="token punctuation">,</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"./filters/dateformat.js"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  eleventyConfig<span class="token punctuation">.</span><span class="token function">addFilter</span><span class="token punctuation">(</span><span class="token string">"include"</span><span class="token punctuation">,</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"./filters/include.js"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// return modified config</span>
  <span class="token keyword">return</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">dir</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">input</span><span class="token operator">:</span> <span class="token string">"./src"</span><span class="token punctuation">,</span>
      <span class="token literal-property property">output</span><span class="token operator">:</span> <span class="token string">"./dist"</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<h3>Filter blogposts by categories</h3>
<p>We can now create our categories pages by paginating the <code>blogCategories</code> collection. In that template, we will use a custom <code>includes</code> filter to get only the blogposts containing the category we are interested in from the <code>blogposts</code> collection. That filter compares values without taking accentuated characters or capitalization into account. It also uses <code>lodash</code>.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> lodash <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"lodash"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">/**
 * Select all objects in an array
 * where the path includes the value to match
 * capitalisation and diacritics are removed from compared values
 *
 * @param {Array} arr - array of objects to inspect
 * @param {String} path - path to inspect for each object
 * @param {String} value - value to match
 * @return {Array} - new array
 */</span>

module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">arr<span class="token punctuation">,</span> path<span class="token punctuation">,</span> value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  value <span class="token operator">=</span> lodash<span class="token punctuation">.</span><span class="token function">deburr</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">return</span> arr<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">let</span> pathValue <span class="token operator">=</span> lodash<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>item<span class="token punctuation">,</span> path<span class="token punctuation">)</span><span class="token punctuation">;</span>
    pathValue <span class="token operator">=</span> lodash<span class="token punctuation">.</span><span class="token function">deburr</span><span class="token punctuation">(</span>pathValue<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> pathValue<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>And here is a bare-bones template using pagination with a <code>size</code> of <code>1</code> and that <code>includes</code> filter to create our categories pages.</p>
<pre class="language-twig"><code class="language-twig">---
pagination:
  data: collections.blogCategories
  size: 1
  alias: category
permalink: /blog/category/<span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> category<span class="token punctuation">.</span>slug <span class="token delimiter punctuation">}}</span></span>/index.html
---
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">extends</span> <span class="token string"><span class="token punctuation">"</span>layouts/base.njk<span class="token punctuation">"</span></span> <span class="token delimiter punctuation">%}</span></span>

<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">block</span> content <span class="token delimiter punctuation">%}</span></span>

  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">></span></span>Blog category: <span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> category<span class="token punctuation">.</span>title <span class="token delimiter punctuation">}}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> posts <span class="token operator">=</span> collections<span class="token punctuation">.</span>blogposts <span class="token operator">|</span> include<span class="token punctuation">(</span><span class="token string"><span class="token punctuation">"</span>data.categories<span class="token punctuation">"</span></span><span class="token punctuation">,</span> category<span class="token punctuation">.</span>title<span class="token punctuation">)</span> <span class="token delimiter punctuation">%}</span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">for</span> post <span class="token operator">in</span> posts <span class="token delimiter punctuation">%}</span></span>
    <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> loop<span class="token punctuation">.</span>first <span class="token delimiter punctuation">%}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>article</span><span class="token punctuation">></span></span>
          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>time</span> <span class="token attr-name">datetime</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> post<span class="token punctuation">.</span>date <span class="token operator">|</span> date<span class="token punctuation">(</span><span class="token string"><span class="token punctuation">'</span>YYYY-MM-DD<span class="token punctuation">'</span></span><span class="token punctuation">)</span> <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> post<span class="token punctuation">.</span>date <span class="token operator">|</span> date<span class="token punctuation">(</span><span class="token string"><span class="token punctuation">"</span>MMMM Do, YYYY<span class="token punctuation">"</span></span><span class="token punctuation">)</span> <span class="token delimiter punctuation">}}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>time</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span>
          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> post<span class="token punctuation">.</span>url <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> post<span class="token punctuation">.</span>data<span class="token punctuation">.</span>title <span class="token delimiter punctuation">}}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span>
    <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> loop<span class="token punctuation">.</span>last <span class="token delimiter punctuation">%}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endfor</span> <span class="token delimiter punctuation">%}</span></span>

  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h3</span><span class="token punctuation">></span></span>Categories<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h3</span><span class="token punctuation">></span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">for</span> category <span class="token operator">in</span> collections<span class="token punctuation">.</span>blogCategories <span class="token delimiter punctuation">%}</span></span>
    <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> loop<span class="token punctuation">.</span>first <span class="token delimiter punctuation">%}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/blog/<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>All<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>

    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/blog/category/<span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> category<span class="token punctuation">.</span>slug <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> category<span class="token punctuation">.</span>title <span class="token delimiter punctuation">}}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span>

    <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> loop<span class="token punctuation">.</span>last <span class="token delimiter punctuation">%}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endfor</span> <span class="token delimiter punctuation">%}</span></span>

<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endblock</span> <span class="token delimiter punctuation">%}</span></span></code></pre>
<p>As a first step, we replicated the native tag-based functionality using our own custom category system.</p>
<p>Since we already used pagination to create our categories pages, we cannot use it in the same template to paginate our posts. That being said, <a href="https://github.com/11ty/eleventy/issues/332#issuecomment-445236776">Zach Leatherman's answer to this issue on Github</a> points to a way to format our data to get around that current limitation of Eleventy.</p>
<h2>Two levels of pagination</h2>
<p>In order to use Zach's solution, we need to massage our data into a collection where each item is a page of the paginated results we want for each theme. We can then use pagination with a <code>size</code> of <code>1</code> to create category pages with paginated posts.</p>
<p>If we have three blogposts in the &quot;travel&quot; category and one in the &quot;awesomeness&quot; category and we want two posts per page, we will need data in the following format:</p>
<pre class="language-text"><code class="language-text">[
  {
    title: 'travel',
    slug: 'travel',
    pageNumber: 0,
    totalPages: 2,
    pageSlugs: {
      all: [],
      next: 'travel/2',
      previous: null,
      first: 'travel',
      last: 'travel/2'
    },
    items: [{},{}]
  },
  {
    title: 'travel',
    slug: 'travel/2',
    pageNumber: 1,
    totalPages: 2,
    pageSlugs: {
      all: [],
      next: null,
      previous: 'travel',
      first: 'travel',
      last: 'travel/2'
    },
    items: [{}]
  },
  {
    title: 'awesomeness',
    slug: 'awesomeness',
    pageNumber: 0,
    totalPages: 1,
    pageSlugs: {
      all: [],
      next: null,
      previous: null,
      first: 'awesomeness',
      last: 'awesomeness'
    },
    items: [{}]
  }
]</code></pre>
<p>Using some array manipulation, we can create such a collection in our <code>.eleventy.js</code> file.</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// create flattened paginated blogposts per categories collection</span>
<span class="token comment">// based on Zach Leatherman's solution - https://github.com/11ty/eleventy/issues/332</span>
eleventyConfig<span class="token punctuation">.</span><span class="token function">addCollection</span><span class="token punctuation">(</span><span class="token string">"blogpostsByCategories"</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">collection</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> itemsPerPage <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> blogpostsByCategories <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> allBlogposts <span class="token operator">=</span> collection
    <span class="token punctuation">.</span><span class="token function">getFilteredByGlob</span><span class="token punctuation">(</span><span class="token string">"./src/blogposts/*.md"</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">reverse</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> blogpostsCategories <span class="token operator">=</span> <span class="token function">getAllKeyValues</span><span class="token punctuation">(</span>allBlogposts<span class="token punctuation">,</span> <span class="token string">"categories"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// walk over each unique category</span>
  blogpostsCategories<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">category</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">let</span> sanitizedCategory <span class="token operator">=</span> lodash<span class="token punctuation">.</span><span class="token function">deburr</span><span class="token punctuation">(</span>category<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// create array of posts in that category</span>
    <span class="token keyword">let</span> postsInCategory <span class="token operator">=</span> allBlogposts<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">post</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
      <span class="token keyword">let</span> postCategories <span class="token operator">=</span> post<span class="token punctuation">.</span>data<span class="token punctuation">.</span>categories <span class="token operator">?</span> post<span class="token punctuation">.</span>data<span class="token punctuation">.</span>categories <span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
      <span class="token keyword">let</span> sanitizedPostCategories <span class="token operator">=</span> postCategories<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token operator">=></span>
        lodash<span class="token punctuation">.</span><span class="token function">deburr</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
      <span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">return</span> sanitizedPostCategories<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>sanitizedCategory<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// chunck the array of posts</span>
    <span class="token keyword">let</span> chunkedPostsInCategory <span class="token operator">=</span> lodash<span class="token punctuation">.</span><span class="token function">chunk</span><span class="token punctuation">(</span>postsInCategory<span class="token punctuation">,</span> itemsPerPage<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// create array of page slugs</span>
    <span class="token keyword">let</span> pagesSlugs <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> chunkedPostsInCategory<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">let</span> categorySlug <span class="token operator">=</span> <span class="token function">strToSlug</span><span class="token punctuation">(</span>category<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">let</span> pageSlug <span class="token operator">=</span> i <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">?</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>categorySlug<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>i <span class="token operator">+</span> <span class="token number">1</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span> <span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>categorySlug<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
      pagesSlugs<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>pageSlug<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token comment">// create array of objects</span>
    chunkedPostsInCategory<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">posts<span class="token punctuation">,</span> index</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
      blogpostsByCategories<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
        <span class="token literal-property property">title</span><span class="token operator">:</span> category<span class="token punctuation">,</span>
        <span class="token literal-property property">slug</span><span class="token operator">:</span> pagesSlugs<span class="token punctuation">[</span>index<span class="token punctuation">]</span><span class="token punctuation">,</span>
        <span class="token literal-property property">pageNumber</span><span class="token operator">:</span> index<span class="token punctuation">,</span>
        <span class="token literal-property property">totalPages</span><span class="token operator">:</span> pagesSlugs<span class="token punctuation">.</span>length<span class="token punctuation">,</span>
        <span class="token literal-property property">pageSlugs</span><span class="token operator">:</span> <span class="token punctuation">{</span>
          <span class="token literal-property property">all</span><span class="token operator">:</span> pagesSlugs<span class="token punctuation">,</span>
          <span class="token literal-property property">next</span><span class="token operator">:</span> pagesSlugs<span class="token punctuation">[</span>index <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">||</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
          <span class="token literal-property property">previous</span><span class="token operator">:</span> pagesSlugs<span class="token punctuation">[</span>index <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">||</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
          <span class="token literal-property property">first</span><span class="token operator">:</span> pagesSlugs<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">||</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
          <span class="token literal-property property">last</span><span class="token operator">:</span> pagesSlugs<span class="token punctuation">[</span>pagesSlugs<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">||</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token literal-property property">items</span><span class="token operator">:</span> posts<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> blogpostsByCategories<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Now, by using the following template with a pagination <code>size</code> of <code>1</code>, we will get paginated blogposts for our categories pages.</p>
<pre class="language-twig"><code class="language-twig">---
pagination:
  data: collections.blogpostsByCategories
  size: 1
  alias: category
permalink: /blog/category/<span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> category<span class="token punctuation">.</span>slug <span class="token delimiter punctuation">}}</span></span>/index.html
---
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">extends</span> <span class="token string"><span class="token punctuation">"</span>layouts/base.njk<span class="token punctuation">"</span></span> <span class="token delimiter punctuation">%}</span></span>

<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">block</span> content <span class="token delimiter punctuation">%}</span></span>

  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">></span></span>Blog category: <span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> category<span class="token punctuation">.</span>title <span class="token delimiter punctuation">}}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">for</span> post <span class="token operator">in</span> category<span class="token punctuation">.</span>items <span class="token delimiter punctuation">%}</span></span>
    <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> loop<span class="token punctuation">.</span>first <span class="token delimiter punctuation">%}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>article</span><span class="token punctuation">></span></span>
          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>time</span> <span class="token attr-name">datetime</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> post<span class="token punctuation">.</span>date <span class="token operator">|</span> date<span class="token punctuation">(</span><span class="token string"><span class="token punctuation">'</span>YYYY-MM-DD<span class="token punctuation">'</span></span><span class="token punctuation">)</span> <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> post<span class="token punctuation">.</span>date <span class="token operator">|</span> date<span class="token punctuation">(</span><span class="token string"><span class="token punctuation">"</span>MMMM Do, YYYY<span class="token punctuation">"</span></span><span class="token punctuation">)</span> <span class="token delimiter punctuation">}}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>time</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span>
          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> post<span class="token punctuation">.</span>url <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> post<span class="token punctuation">.</span>data<span class="token punctuation">.</span>title <span class="token delimiter punctuation">}}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span>
    <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> loop<span class="token punctuation">.</span>last <span class="token delimiter punctuation">%}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endfor</span> <span class="token delimiter punctuation">%}</span></span>

  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> category<span class="token punctuation">.</span>totalPages <span class="token operator">></span> <span class="token number">1</span> <span class="token delimiter punctuation">%}</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">></span></span>
      <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> category<span class="token punctuation">.</span>pageSlugs<span class="token punctuation">.</span>previous <span class="token delimiter punctuation">%}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/blog/category/<span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> category<span class="token punctuation">.</span>pageSlugs<span class="token punctuation">.</span>previous <span class="token delimiter punctuation">}}</span></span>/<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Previous<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>
      <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> category<span class="token punctuation">.</span>pageSlugs<span class="token punctuation">.</span>next <span class="token delimiter punctuation">%}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/blog/category/<span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> category<span class="token punctuation">.</span>pageSlugs<span class="token punctuation">.</span>next <span class="token delimiter punctuation">}}</span></span>/<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Next<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>

  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h3</span><span class="token punctuation">></span></span>Categories<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h3</span><span class="token punctuation">></span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">for</span> category <span class="token operator">in</span> collections<span class="token punctuation">.</span>blogCategories <span class="token delimiter punctuation">%}</span></span>
    <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> loop<span class="token punctuation">.</span>first <span class="token delimiter punctuation">%}</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/blog/<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>All<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span>
    <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/blog/category/<span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> category<span class="token punctuation">.</span>slug <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> category<span class="token punctuation">.</span>title <span class="token delimiter punctuation">}}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endfor</span> <span class="token delimiter punctuation">%}</span></span>

<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endblock</span> <span class="token delimiter punctuation">%}</span></span></code></pre>
<h2>My two cents on pagination</h2>
<p>From my perspective, paginating posts on tag or category pages is a relatively common requirement, even for simple websites that have a fair amount of content, like blogs for example. In my humble opinion, pagination as it currently stands has two distinct drawbacks:</p>
<ol>
<li>It can be used to generate single pages from data/collections and to paginate arrays and objects, which can make it confusing for newcomers.</li>
<li>Since it lives in the front-matter of templates, it can only be used once per template instead of being usable in context with any iterable.</li>
</ol>
<p>If most use cases are limited to two levels of depth, then distinguishing pagination from single pages generation would probably fit the bill, since we could then combine both usages. Maybe something like a <code>generatePages()</code> collection method would be a good candidate.</p>
<p>If what’s needed is unlimited levels of depth, most other systems I have used (SSG/CMS) have something like a filter or tag that paginates any iterable in a template where needed. They return a chunked nested array of items, along with variables needed to create pagination interfaces. The closest thing I can think about in Eleventy today is <a href="https://www.11ty.dev/docs/plugins/navigation/">the navigation plugin</a>, but a pagination plugin is a big departure from what currently exists.</p>

        ]]>
        </description>
      </item>
    
      <item>
        <title>Teaching in the open: Eleventy</title>
        <link>https://www.webstoemp.com/blog/teaching-eleventy/</link>
        <pubDate>Thu, 16 Jan 2020 00:00:00 +0000</pubDate>
        <guid>https://www.webstoemp.com/blog/teaching-eleventy/</guid>
        <description>
        <![CDATA[
          <p>Content Management Systems and structured data are part of my teaching duties. From the get go, I decided to cover both a database-driven CMS and a Static Site Generator. As far as the latter goes, I switched from Jekyll to Eleventy this year.</p>
          <h2>Decoupling</h2>
<p>Both as a teacher and as a freelancer, I firmly believe that the <a href="https://jamstack.org/">JAMstack movement</a> and the general trend towards decoupling the view and data layers are here to stay.</p>
<p>I have been teaching <a href="https://craftcms.com/">Craft CMS</a> as a database-driven CMS since it was first released. <a href="https://pixelandtonic.com/">Pixel &amp; Tonic</a> releasing <a href="https://craftcms.com/blog/craft-33">a native headless mode and a solid native GraphQL API</a> only confirmed my choice.</p>
<p><a href="https://jekyllrb.com/">Jekyll</a>, while very good at generating a site based on Markdown and static data files, requires either a <a href="https://github.com/brockfanning/jekyll-get-json">plugin</a> or a <a href="https://twitter.com/philhawksworth/status/1159193504851144705">build process</a> <a href="https://david.darn.es/tutorial/2019/08/11/use-ghost-with-jekyll/">in front of it</a> to consume data from APIs or headless CMSes.</p>
<p>Students (and myself) also have to install and maintain a Ruby environment to be able to use it. <a href="https://www.11ty.dev/">Eleventy</a> is built on Node, which is easier to install and maintain as a development environment. It also means that students can leverage their existing knowledge of Javascript.</p>
<h2>Extensibility</h2>
<p>Working within a familiar environment and being able to extend and configure Eleventy using a language students already know and understand is a game changer.</p>
<p>Need custom <a href="https://www.11ty.dev/docs/filters/">filters</a> or <a href="https://www.11ty.dev/docs/shortcodes/">shortcodes</a>? Use Javascript. Create custom <a href="https://www.11ty.dev/docs/collections/">order or filters for your collections</a> before using them in your templates? Same deal. Use NPM packages to help you build the functionalities your project needs? You're already in Node.</p>
<p>For students, this is invaluable. Granted, Eleventy might feel a bit closer to the metal compared to Jekyll but the increased flexibility, extensibility and familiarity is definitely worth it.</p>
<h2>Simplicity</h2>
<p>With React, Vue or Svelte being all the rage these days, SSG like <a href="https://www.gatsbyjs.org/">Gatsby</a> or <a href="https://gridsome.org/">Gridsome</a> were also on my radar.</p>
<p>Personally, I feel that those solutions, while extremely valuable when you have to deal with app-like functionalities and / or state-based UI, come with a lot of baggage and are a bit overkill when building content-driven websites.</p>
<p>By comparison, Eleventy is a lot simpler and looks a lot less like a black box to me (if I am being completely honest). <a href="https://www.webstoemp.com/blog/headless-cms-graphql-api-eleventy/">Fetching data from a GraphQL API</a> is quite trivial and students can still use what they learned with my colleagues teaching them Vue or Node if the project requires it.</p>
<h2>Workshop course and sample website</h2>
<p>All things considered, I am really happy with how the course went this year. Students seemed to enjoy it and got the hang of it pretty quickly.</p>
<p>My &quot;<a href="https://github.com/jeromecoupe/iad_eleventy_introduction">introduction to Eleventy</a>&quot; course is available on Github in French and English, along with a <a href="https://github.com/jeromecoupe/sample-11ty-blog">sample website</a> hosted on <a href="https://www.netlify.com/">Netlify</a>. Feel free to check both out and to give feedback, open issues or make pull requests if you think something could be improved.</p>
<p>As I have <a href="https://www.webstoemp.com/blog/teaching-in-the-open-craft-jekyll-workshops/">said in the past</a>, teaching in the open is part of my attempt to stay relevant while teaching front-end design in this ever-moving, ever-changing messy web of ours.</p>

        ]]>
        </description>
      </item>
    
      <item>
        <title>Language switcher for multilingual JAMstack sites</title>
        <link>https://www.webstoemp.com/blog/language-switcher-multilingual-jamstack-sites/</link>
        <pubDate>Thu, 12 Sep 2019 00:00:00 +0000</pubDate>
        <guid>https://www.webstoemp.com/blog/language-switcher-multilingual-jamstack-sites/</guid>
        <description>
        <![CDATA[
          <p>Following my blogpost on multilingual websites with Eleventy, I had several questions about how to build a language switcher. Here is the approach I generally use.</p>
          <h2>Set the stage</h2>
<p>With most static sites generators supporting a templating languages, structured data and URL control, building a multilingual website is a relatively easy task. I wrote a small <a href="https://www.webstoemp.com/blog/multilingual-sites-eleventy/">blogpost on how to do it with Eleventy</a>. Other systems like Hugo and Jekyll have either <a href="https://gohugo.io/content-management/multilingual/">clear documentation</a> or <a href="https://www.sylvaindurand.org/making-jekyll-multilingual/">numerous</a> <a href="https://forestry.io/blog/creating-a-multilingual-blog-with-jekyll/">articles</a> on the topic.</p>
<p>What we want here is to redirect users that are on a certain piece of content in one language to the same one in other languages. If such a piece of content is not available, we will redirect users to the homepage of the site in the language they chose.</p>
<p>We will be using Eleventy (11ty) but the general approach is usable with most other SSGs. Here is what we will need:</p>
<ol>
<li>A way to loop through all languages used in the site</li>
<li>Make sure each piece of content has a <code>locale</code> key with its value set to the language code of that content. Here is a refresher about <a href="https://www.webstoemp.com/blog/multilingual-sites-eleventy/">how to do it with Eleventy directory data files</a> if you need one.</li>
<li>Set a common unique <code>translationKey</code> to create a relation between the same content in various languages</li>
</ol>
<h2>Site languages</h2>
<p>The first thing we need is an array of all the languages the site uses. I usually store that in my <code>./src/_data/site.js</code> file under a <code>languages</code> key.</p>
<pre class="language-js"><code class="language-js">module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">title</span><span class="token operator">:</span> <span class="token string">"Webstoemp"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">description</span><span class="token operator">:</span>
    <span class="token string">"Webstoemp is the portfolio and blog of Jérôme Coupé, a designer and front-end developer from Brussels, Belgium."</span><span class="token punctuation">,</span>
  <span class="token literal-property property">url</span><span class="token operator">:</span> <span class="token string">"https://www.webstoemp.com"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">baseUrl</span><span class="token operator">:</span> <span class="token string">"/"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">author</span><span class="token operator">:</span> <span class="token string">"Jerôme Coupé"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">authorTwitter</span><span class="token operator">:</span> <span class="token string">"@jeromecoupe"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">buildTime</span><span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token literal-property property">languages</span><span class="token operator">:</span> <span class="token punctuation">[</span>
    <span class="token punctuation">{</span>
      <span class="token literal-property property">label</span><span class="token operator">:</span> <span class="token string">"english"</span><span class="token punctuation">,</span>
      <span class="token literal-property property">code</span><span class="token operator">:</span> <span class="token string">"en"</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span>
      <span class="token literal-property property">label</span><span class="token operator">:</span> <span class="token string">"français"</span><span class="token punctuation">,</span>
      <span class="token literal-property property">code</span><span class="token operator">:</span> <span class="token string">"fr"</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Using this array, we will be able to loop through our site languages and match the value of the <code>code</code> key against the value of the <code>locale</code> variable available in all our content files.</p>
<h2>Setup translation keys</h2>
<p>We now need an explicit relation between the same pieces of content in various languages. With a static site generator storing data as files, we can rely on using the same translation key in the YAML front matters of all those files. That translation key can be any string, provided that it is unique for each piece of content.</p>
<p>For example, we could use the following to link together our contact pages in various languages.</p>
<p><code>./src/en/pages/about.njk</code></p>
<pre class="language-twig"><code class="language-twig">---
permalink: "/<span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> locale <span class="token delimiter punctuation">}}</span></span>/about/index.html"
translationKey: "aboutPage"
---</code></pre>
<p><code>./src/fr/pages/about.njk</code></p>
<pre class="language-twig"><code class="language-twig">---
permalink: "/<span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> locale <span class="token delimiter punctuation">}}</span></span>/a-propos/index.html"
translationKey: "aboutPage"
---</code></pre>
<p>The same principle can apply to our collection items, for example blogposts.</p>
<p><code>./src/en/bogposts/2019-09-12-my-awesome-blogpost.njk</code></p>
<pre class="language-text"><code class="language-text">---
title: "My awesome blogpost"
translationKey: "awesome-blogpost"
---</code></pre>
<p><code>./src/fr/bogposts/2019-09-12-mon-magnifique-blogpost.njk</code></p>
<pre class="language-text"><code class="language-text">---
title: "Mon magnifique blogpost"
translationKey: "awesome-blogpost"
---</code></pre>
<p>We now have an explicit relation between the same content pieces in all languages.</p>
<h2>Coding our language switcher</h2>
<p>Here is an outline of what we are going to do with that short piece of code:</p>
<ol>
<li>Loop through all languages declared for the site in <code>./src/_data/site.js</code></li>
<li>By default, set <code>translatedUrl</code> to the homepage of the language we are looping though. This will be overridden in step 4 if a match is found</li>
<li>Within our first loop, we will loop through all the content pieces in the site. With Eleventy, we can use the handy <a href="https://www.11ty.dev/docs/collections/#the-special-all-collection"><code>collections.all</code></a> shortcut.</li>
<li>For each content piece we loop through, check if its <code>translationKey</code> matches the current item's <code>translationKey</code> and if its <code>locale</code> matches the <code>code</code> of the language we are looping through in our first loop. If we find a match, set <code>translatedUrl</code> to the url of that content item.</li>
<li>Use the value of <code>translatedUrl</code> to create the links in our language switcher.</li>
</ol>
<pre class="language-twig"><code class="language-twig"><span class="token twig language-twig"><span class="token comment">{# loop though site.languages #}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">for</span> lgg <span class="token operator">in</span> site<span class="token punctuation">.</span>languages <span class="token delimiter punctuation">%}</span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> loop<span class="token punctuation">.</span>first <span class="token delimiter punctuation">%}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>c-lggnav<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>

  <span class="token twig language-twig"><span class="token comment">{# set translatedUrl to the homepage of that language by default #}</span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> translatedUrl <span class="token operator">=</span> <span class="token string"><span class="token punctuation">"</span>/<span class="token punctuation">"</span></span> <span class="token operator">+</span> lgg<span class="token punctuation">.</span>code <span class="token operator">+</span> <span class="token string"><span class="token punctuation">"</span>/<span class="token punctuation">"</span></span> <span class="token delimiter punctuation">%}</span></span>

  <span class="token twig language-twig"><span class="token comment">{# set current language class #}</span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> activeClass <span class="token operator">=</span> <span class="token string"><span class="token punctuation">"</span>is-active<span class="token punctuation">"</span></span> <span class="token keyword">if</span> lgg<span class="token punctuation">.</span>code <span class="token operator">==</span> locale else <span class="token string"><span class="token punctuation">"</span><span class="token punctuation">"</span></span> <span class="token delimiter punctuation">%}</span></span>

  <span class="token twig language-twig"><span class="token comment">{# loop through all the content of the site #}</span></span>
  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">for</span> item <span class="token operator">in</span> collections<span class="token punctuation">.</span>all <span class="token delimiter punctuation">%}</span></span>

    <span class="token twig language-twig"><span class="token comment">{# for each item in the loop, check if
    - its translationKey matches the current item translationKey
    - its locale matches the code of the language we are looping through #}</span></span>
    <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> item<span class="token punctuation">.</span>data<span class="token punctuation">.</span>translationKey <span class="token operator">==</span> translationKey <span class="token operator">and</span> item<span class="token punctuation">.</span>data<span class="token punctuation">.</span>locale <span class="token operator">==</span> lgg<span class="token punctuation">.</span>code <span class="token delimiter punctuation">%}</span></span>
      <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> translatedUrl <span class="token operator">=</span> item<span class="token punctuation">.</span>url <span class="token delimiter punctuation">%}</span></span>
    <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>

  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endfor</span> <span class="token delimiter punctuation">%}</span></span>

  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>c-lggnav__item<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>c-lggnav__link  <span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> activeClass <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> translatedUrl <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> lgg<span class="token punctuation">.</span>label <span class="token delimiter punctuation">}}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span>

  <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> loop<span class="token punctuation">.</span>last <span class="token delimiter punctuation">%}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span><span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span>
<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endfor</span> <span class="token delimiter punctuation">%}</span></span></code></pre>
<p>There we go, job done with a minimal amount of effort. Those loops will happen on every page of the site but, since Eleventy is already creating <code>collections.all</code> anyway and has very fast IO, the impact on build time should be pretty low, even with large sites.</p>

        ]]>
        </description>
      </item>
    
  </channel>
</rss>
