<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Dalia</title>
    <description>The latest articles on Forem by Dalia (@irrelevantspace).</description>
    <link>https://forem.com/irrelevantspace</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1251731%2Fc6b5e36f-bc12-4f46-bb0f-f1b1ff0fb792.jpg</url>
      <title>Forem: Dalia</title>
      <link>https://forem.com/irrelevantspace</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/irrelevantspace"/>
    <language>en</language>
    <item>
      <title>One Worker to Track Them All: Injecting Analytics Scripts into Multiple Websites with Cloudflare Workers</title>
      <dc:creator>Dalia</dc:creator>
      <pubDate>Mon, 15 Jan 2024 01:00:00 +0000</pubDate>
      <link>https://forem.com/irrelevantspace/one-worker-to-track-them-all-injecting-analytics-scripts-into-multiple-websites-with-cloudflare-workers-1j5b</link>
      <guid>https://forem.com/irrelevantspace/one-worker-to-track-them-all-injecting-analytics-scripts-into-multiple-websites-with-cloudflare-workers-1j5b</guid>
      <description>&lt;p&gt;For a while now, I've been creating mini web tools to test out ideas or as tiny helpers for myself. I usually publish them on individual subdomains, which might not be the best idea, but I like the concept of a short, easy-to-remember URL. Recently, I discovered that some of these tools actually have a few users, which made me consider adding analytics to them. After a bit of research, I settled on &lt;a href="https://umami.is/"&gt;umami&lt;/a&gt;. It's a great little privacy-conscious tool with exactly what I need and nothing more.&lt;/p&gt;

&lt;p&gt;The issue with having so many subdomains is that you'd have to add a different tracking script to each one of them, treating them as individual websites. Modifying each project to add the script sounds a bit tedious. If only there was a way to map each subdomain to its script.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;Except that there is. &lt;a href="https://cloudflare.com/"&gt;Cloudflare&lt;/a&gt; is pretty great for free SSL certificates and DNS management, but they also offer a free Workers plan. A &lt;a href="https://developers.cloudflare.com/workers/"&gt;Cloudflare worker&lt;/a&gt; is basically JavaScript code that runs on Cloudflare's edge network and handles HTTP traffic. You can do a lot with workers, including modifying/rewriting HTML responses. You can probably see where this is going: If a worker can modify HTML responses, then it can inject the umami script into every HTML response.&lt;/p&gt;

&lt;p&gt;I modified one of the existing workers examples, and it worked on the first attempt. Initially, the first version of the worker had a dictionary mapping each hostname to the relevant Umami ID and injected that accordingly. I've changed that since to use environment variables. This way, adding a new subdomain is as simple as adding the hostname and the Umami ID to the worker's environment variables, which you can do from Cloudflare's dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Execution
&lt;/h2&gt;

&lt;p&gt;Getting started with Cloudflare workers is pretty simple. You can follow the &lt;a href="https://developers.cloudflare.com/workers/get-started/guide/"&gt;get started guide&lt;/a&gt; to get a basic worker up and running. The worker I ended up with is pretty simple. When a request comes in, the worker checks if the response is HTML. If it is, it looks up the hostname in the environment variables and uses &lt;a href="https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/"&gt;&lt;code&gt;HTTPRewriter&lt;/code&gt;&lt;/a&gt; to rewrite the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; tag and inject the umami script. If the response is not HTML or the hostname is not found in the environment variables, the worker just passes the response through.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BASE_UMAMI_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://eu.umami.is/script.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// get the umami id from the environment variables&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;umamiId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ScriptInjector&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;script async data-website-id="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;umamiId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" src="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE_UMAMI_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&amp;lt;/script&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// html: true rewrites the string as HTML, otherwise it's escaped as text&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contentType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isHtml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;contentType&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// if the response is html and we have an umami id for the hostname, transform the &amp;lt;head&amp;gt; tag&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;umamiId&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;isHtml&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HTMLRewriter&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;head&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ScriptInjector&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After creating the worker, the &lt;a href="https://developers.cloudflare.com/workers/configuration/environment-variables/"&gt;environment variables can be set from the dashboard or using Wrangler&lt;/a&gt;. Once the worker is deployed, it can be added to a route. I added it to the &lt;code&gt;*.&amp;lt;domain&amp;gt;.com/*&lt;/code&gt; route, which means that it will run on all subdomains of &lt;code&gt;&amp;lt;domain&amp;gt;.com&lt;/code&gt;. You don't have to stick to one domain only. You can add multiple domains to the same worker and have it inject the script into all of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Instead of using environment variables, you should be able to use &lt;a href="https://developers.cloudflare.com/kv/"&gt;KV&lt;/a&gt; to store the hostname-Umami ID mapping. I haven't tried this yet, but I plan to do so soon.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Word of Caution
&lt;/h2&gt;

&lt;p&gt;This solution ended up working almost perfectly on projects that use plain HTML/JS. However, two projects had the script vanish mysteriously right after the page loads. These specific projects use VueJS and Svelte, so I'm guessing Vue and Svelte might be the reason behind this disappearing act. Investigating this is a task for future me, so I'll leave it at that for now.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>webdev</category>
      <category>cloudflare</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>CSS Trivia: Masonry Grid Layout</title>
      <dc:creator>Dalia</dc:creator>
      <pubDate>Sun, 14 Jan 2024 06:00:00 +0000</pubDate>
      <link>https://forem.com/irrelevantspace/css-trivia-masonry-grid-layout-50ck</link>
      <guid>https://forem.com/irrelevantspace/css-trivia-masonry-grid-layout-50ck</guid>
      <description>&lt;p&gt;Pinterest's layout has always been the defining feature of the site since its inception. It has been used by many others since then, and I've always been fascinated by it. Recently, I've come to learn that this type of layout is called a Masonry Layout, a name that comes from &lt;a href="https://en.wikipedia.org/wiki/Masonry" rel="noopener noreferrer"&gt;the craft of stonemasonry&lt;/a&gt;. Another thing I've learnt recently is that CSS has an experimental masonry grid layout, with &lt;strong&gt;very limited&lt;/strong&gt; browser support. The specifications are outlined in &lt;a href="https://drafts.csswg.org/css-grid-3/" rel="noopener noreferrer"&gt;CSS Grid Layout Module Level 3 Editor's Draft&lt;/a&gt; for those who are interested.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9fcj7sb0b1cfaqpcr8th.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9fcj7sb0b1cfaqpcr8th.jpg" alt="A monitor screen showing bricks tightly packed together"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;(DALL.E's interpretation of a masonry layout)&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating a Masonry Layout
&lt;/h2&gt;

&lt;p&gt;There are a few different ways to achieve this layout. Horizontal masonry layouts are simpler to create than vertical ones&lt;sup id="fnref1"&gt;1&lt;/sup&gt;. You can simply use &lt;code&gt;inline-block&lt;/code&gt; elements, or use a &lt;code&gt;flex&lt;/code&gt; container with &lt;code&gt;flex-wrap: wrap&lt;/code&gt; if you want more control over the layout.&lt;/p&gt;

&lt;p&gt;For a vertical layout, &lt;code&gt;flex&lt;/code&gt; doesn't do as well unless you have a fixed height container. An approach I like personally is to use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/columns" rel="noopener noreferrer"&gt;columns&lt;/a&gt;&lt;sup id="fnref2"&gt;2&lt;/sup&gt;. Column layouts are responsive and allow setting the maximum number of columns. The following CSS creates a column layout with a maximum of 4 columns, each column having the ideal width of &lt;code&gt;180px&lt;/code&gt;, and a gap of &lt;code&gt;10px&lt;/code&gt; between columns&lt;sup id="fnref3"&gt;3&lt;/sup&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nf"&gt;#column-layout&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="m"&gt;180px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;column-gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main issue with this approach is that the order of the child elements in the layout is vertical, not horizontal. The example below shows the above CSS in action. Note the order of the elements.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/irrelevant-space/embed/dyrOWVP?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://css-tricks.com/piecing-together-approaches-for-a-css-masonry-layout/" rel="noopener noreferrer"&gt;Approaches for a CSS Masonry Layout&lt;/a&gt; is a great read on the different ways a masonry layout can be achieved. It covers the cases above and more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Masonry CSS Grids
&lt;/h2&gt;

&lt;p&gt;So far, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout" rel="noopener noreferrer"&gt;CSS grid layout&lt;/a&gt; hasn't been flexible enough to create a masonry layout, but this might be about to change. The &lt;a href="https://drafts.csswg.org/css-grid-3/" rel="noopener noreferrer"&gt;Editor's Draft&lt;/a&gt; introduces a new &lt;code&gt;masonry&lt;/code&gt; value to be used for &lt;code&gt;grid-template-columns&lt;/code&gt; and &lt;code&gt;grid-template-rows&lt;/code&gt; properties. The snippet below creates a responsive vertical masonry grid with columns having an ideal width of &lt;code&gt;180px&lt;/code&gt;, and a gap of &lt;code&gt;10px&lt;/code&gt; between the elements in the grid.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nf"&gt;#masonry-grid&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto-fit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;180px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;masonry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result would look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9q0zyw5aauqiqhg5wm2w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9q0zyw5aauqiqhg5wm2w.png" alt="CSS Masonry grid layout"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;(CSS Grid with &lt;code&gt;grid-template-rows: masonry&lt;/code&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If your browser supports masonry grids, you can see it in action here:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/irrelevant-space/embed/BabQRdR?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Examining the result, you'll notice that the order of the elements is horizontal, but they're not exactly in order. The masonry layout algorithm places elements in the columns with the most remaining space to try to achieve an even look for all columns&lt;sup id="fnref4"&gt;4&lt;/sup&gt;. If order is important, using &lt;code&gt;masonry-auto-flow: next&lt;/code&gt; will place the elements in order, but the final result won't be as even.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftk7v2n1tjq0oa0w2fxib.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftk7v2n1tjq0oa0w2fxib.png" alt="CSS Masonry grid layout with ordered elements"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;(Masonry Grid with &lt;code&gt;masonry-auto-flow: next&lt;/code&gt;)&lt;/em&gt;&lt;/p&gt;



&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/irrelevant-space/embed/vYPyZge?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser Support and Fallbacks
&lt;/h2&gt;

&lt;p&gt;As of writing this, the &lt;code&gt;masonry&lt;/code&gt; value is only supported in Safari Technology Preview&lt;sup id="fnref5"&gt;5&lt;/sup&gt; and in Firefox&lt;sup id="fnref6"&gt;6&lt;/sup&gt; as an experimental feature. If any of the code samples above didn't look right, it might be because the browser you're using doesn't support it yet. If you really want to use it (I know I do), then make sure to provide a fallback for other browsers.&lt;/p&gt;

&lt;p&gt;The default fallback when using &lt;code&gt;masonry&lt;/code&gt; in an unsupported browser, is to ignore it and use &lt;code&gt;auto&lt;/code&gt; instead. This example shows what the fallback would look like.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/irrelevant-space/embed/zYbowKM?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;If that's not the look you're going for, you can detect support for the &lt;code&gt;masonry&lt;/code&gt; value using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@supports" rel="noopener noreferrer"&gt;&lt;code&gt;@supports&lt;/code&gt;&lt;/a&gt; and provide alternative CSS for unsupported browsers. Here's how to fallback to the column layout we saw earlier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nf"&gt;#masonry-layout&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="m"&gt;180px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;column-gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@supports&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grid-template-rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;masonry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;#masonry-layout&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;column-gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto-fit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;180px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="py"&gt;grid-template-rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;masonry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a live example, my &lt;a href="https://relevant.space/" rel="noopener noreferrer"&gt;personal website&lt;/a&gt; falls back to a hacky &lt;code&gt;flex&lt;/code&gt; layout if the browser doesn't support masonry grids.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fasmw2u8yznqq6u21u67n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fasmw2u8yznqq6u21u67n.png" alt="CSS Masonry grid layout"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;(The masonry grid in action)&lt;/em&gt;&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Google Images layout is an example of a horizontal masonry layout, while Pinterest is an example of a vertical one. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;A multi-column CSS layout, flows an element's content into columns. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;The &lt;code&gt;column-gap&lt;/code&gt; property only applies to the space between columns, not between rows (as the name might suggest). The space between rows can be set using &lt;code&gt;margin&lt;/code&gt; on the child elements. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;The algorithm is explained in more detail in the &lt;a href="https://drafts.csswg.org/css-grid-3/#masonry-layout-algorithm" rel="noopener noreferrer"&gt;editor's draft&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;Enabled since version 163. &lt;a href="https://developer.apple.com/documentation/safari-technology-preview-release-notes/stp-release-163/#Masonry-Layout" rel="noopener noreferrer"&gt;See the release notes&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn6"&gt;
&lt;p&gt;Available since version 77 and enabled by default in Nightly. Can be enabled from &lt;code&gt;about:config&lt;/code&gt; by setting &lt;code&gt;layout.css.grid-template-masonry-value.enabled&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;. &lt;a href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Experimental_features#masonry_grid_layout" rel="noopener noreferrer"&gt;See the release notes&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>css</category>
      <category>webdev</category>
      <category>frontend</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Adding "Created At" and "Last Updated" Dates to Jekyll</title>
      <dc:creator>Dalia</dc:creator>
      <pubDate>Wed, 10 Jan 2024 00:36:44 +0000</pubDate>
      <link>https://forem.com/irrelevantspace/adding-created-at-and-last-updated-dates-to-jekyll-4cdc</link>
      <guid>https://forem.com/irrelevantspace/adding-created-at-and-last-updated-dates-to-jekyll-4cdc</guid>
      <description>&lt;p&gt;In making the Jekyll theme I am using on &lt;a href="https://ir.relevant.space"&gt;my personal blog&lt;/a&gt;, I wanted each note to show a Created At and a Last Updated date. Jekyll by default supports dates for post types. The date would be specified in the title of the post, e.g. &lt;code&gt;2020-08-01-My-Post-Title.md&lt;/code&gt;. The date is then parsed by Jekyll and is available in &lt;code&gt;post.date&lt;/code&gt;. Since I'm using collections instead of posts, and mainly using Obsidian to create and edit the files, the markdown files as they are being generated at the moment don't have a date in the title. I was already writing a set of plugins to add Obsidian support to Jekyll, so I thought I'd write a plugin that tells Jekyll to use the file's creation date as the post date unless it is specified in the frontmatter.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First (Failed) Attempt
&lt;/h2&gt;

&lt;p&gt;This was simple enough. All I had to do was to add a &lt;code&gt;:documents :post_init&lt;/code&gt; hook&lt;sup id="fnref1"&gt;1&lt;/sup&gt; to set the &lt;code&gt;created_at&lt;/code&gt; and &lt;code&gt;last_updated_at&lt;/code&gt; variables to &lt;code&gt;File.ctime(doc.path)&lt;/code&gt;&lt;sup id="fnref2"&gt;2&lt;/sup&gt; and &lt;code&gt;File.mtime(doc.path)&lt;/code&gt;&lt;sup id="fnref3"&gt;3&lt;/sup&gt; respectively if they are not already set in the frontmatter. The resulting code looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Jekyll&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Hooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt; &lt;span class="ss"&gt;:documents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:post_init&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="c1"&gt;# set created at date to file creation date if not already set&lt;/span&gt;
  &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ctime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# set last updated date to file modification date if not already set&lt;/span&gt;
  &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"last_updated_at"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This worked perfectly when I was testing it locally. But once pushed to GitHub, all the notes showed the same date, the date of when they were pushed to GitHub. What was going on?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Pb6qDg4D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4447t13ts50tknpzu73r.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Pb6qDg4D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4447t13ts50tknpzu73r.jpg" alt="It works on my machine" width="728" height="610"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;(Image Source: &lt;a href="https://simply-the-test.blogspot.com/2010/05/it-works-on-my-machine.html"&gt;SIMPLY THE TEST&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  What was going on?
&lt;/h3&gt;

&lt;p&gt;To understand the issue, it is important to understand the blog's deployment setup. When a new commit gets pushed to GitHub, a GitHub action is triggered which uses &lt;a href="https://github.com/actions/checkout"&gt;&lt;code&gt;actions/checkout@v4&lt;/code&gt;&lt;/a&gt; to checkout the repo, then uses a custom action that is not relevant to this post to build the site and deploy it to GitHub Pages. When &lt;code&gt;actions/checkout@v4&lt;/code&gt; checks out the repo, all the files are being created at the same time, which is the time of the checkout. This is why &lt;code&gt;File.ctime&lt;/code&gt; and &lt;code&gt;File.mtime&lt;/code&gt; are unsuitable for this case. What we need instead is a way to get the creation date of the file in the repo, not the creation date of the file in the local machine.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Second (Working) Attempt
&lt;/h2&gt;

&lt;p&gt;My next plan was to use git history to set &lt;code&gt;created_at&lt;/code&gt; to the file's first commit date, and the &lt;code&gt;last_updated_at&lt;/code&gt; to the file's last commit date. By default, &lt;code&gt;actions/checkout@v4&lt;/code&gt; checks out the repo with the &lt;code&gt;fetch-depth&lt;/code&gt; option set to 1, so only the latest commit is fetched. Since I need the full git history to get the first commit date as well as the last, the GitHub actions workflow needed a small tweak:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; - name: Checkout
   uses: actions/checkout@v4
&lt;span class="gi"&gt;+  with:
+    fetch-depth: 0 # fetch all history for all tags and branches
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have the full git history, we can use &lt;code&gt;git log&lt;/code&gt; &lt;sup id="fnref4"&gt;4&lt;/sup&gt; to get the first and last commit dates. Using &lt;code&gt;git log --follow --format=%ad --date=iso-strict -- "#{doc.path}"&lt;/code&gt; we get a list of all the commit dates for the file from latest to oldest. We parse the list and assign the first and last lines to &lt;code&gt;last_updated_at&lt;/code&gt; and &lt;code&gt;created_at&lt;/code&gt;. A rough idea of the final code is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# WARNING&lt;/span&gt;
&lt;span class="c1"&gt;# The below snippet is just a rough idea of what could work.&lt;/span&gt;
&lt;span class="c1"&gt;# Don't use it as it is. The next section gives some pointers&lt;/span&gt;
&lt;span class="c1"&gt;# on how to improve it.&lt;/span&gt;
&lt;span class="no"&gt;Jekyll&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Hooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt; &lt;span class="ss"&gt;:documents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:post_init&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;git_dates_log_command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`git log --follow --format=%ad --date=iso-strict -- "&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;"`&lt;/span&gt;
  &lt;span class="n"&gt;git_dates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;git_dates_log_command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;git_dates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
  &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"last_updated_at"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;git_dates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Things to Keep in Mind
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Check that the dates are valid
&lt;/h3&gt;

&lt;p&gt;The first and last lines of the &lt;code&gt;git log&lt;/code&gt; output might not be valid dates. For example, git might output a warning or an error message instead of a date. Blindly assigning the first and last lines to &lt;code&gt;created_at&lt;/code&gt; and &lt;code&gt;last_updated_at&lt;/code&gt; might result in breaking the build. So it is important to check that the dates are valid and that they are actually on the first and last lines.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Allow for overriding the dates in the frontmatter
&lt;/h3&gt;

&lt;p&gt;While this is a neat trick, the priority should always be given to the dates specified in the frontmatter.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. To --follow or not to --follow
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;--follow&lt;/code&gt; &lt;sup id="fnref5"&gt;5&lt;/sup&gt; option in &lt;code&gt;git log&lt;/code&gt; is used to follow the history of a file across renames. Is a renamed post a new post or an update to an existing post? That's your decision.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Avoid using &lt;code&gt;timeago&lt;/code&gt; for post dates
&lt;/h3&gt;

&lt;p&gt;After hours of trying to figure out why Jekyll was still showing "Today" for a post I modified last week, I remembered that I am using the &lt;code&gt;timeago&lt;/code&gt; filter from &lt;a href="https://github.com/markets/jekyll-timeago"&gt;&lt;code&gt;jekyll-timeago&lt;/code&gt;&lt;/a&gt; plugin. I was rendering the dates using &lt;code&gt;{{ doc.last_modified_at | timeago }}&lt;/code&gt;. As you know, Jekyll is a &lt;strong&gt;static&lt;/strong&gt; site generator, and it renders this as HTML at the time of build, and &lt;strong&gt;&lt;u&gt;only&lt;/u&gt;&lt;/strong&gt; then. This means any date rendered with &lt;code&gt;timeago&lt;/code&gt; is hardcoded as is in the HTML and won't change until the next build. I switched all the dates to the &lt;code&gt;"%-d %b %y"&lt;/code&gt; format for now. Might use &lt;a href="https://momentjs.com/"&gt;&lt;code&gt;moment.js&lt;/code&gt;&lt;/a&gt; in the future to get the &lt;code&gt;timeago&lt;/code&gt; dates back.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;a href="https://jekyllrb.com/docs/plugins/hooks/"&gt;Jekyll Hooks&lt;/a&gt; are a way to run code at specific points in the Jekyll build process. The &lt;code&gt;:documents :post_init&lt;/code&gt; hook runs after the documents have been read and parsed, but before it is rendered. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;&lt;a href="https://ruby-doc.org/core-2.5.1/File.html#method-c-ctime"&gt;File.ctime&lt;/a&gt; returns the creation time of the file. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;&lt;a href="https://ruby-doc.org/core-2.5.1/File.html#method-c-mtime"&gt;File.mtime&lt;/a&gt; returns the last modification time of the file. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;&lt;a href="https://git-scm.com/docs/git-log"&gt;git log&lt;/a&gt; shows the commit logs. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;&lt;a href="https://git-scm.com/docs/git-log#Documentation/git-log.txt---follow"&gt;git log --follow&lt;/a&gt; follows the history of a file across renames. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>jekyll</category>
      <category>webdev</category>
      <category>learning</category>
      <category>coding</category>
    </item>
  </channel>
</rss>
