HugoMade MistakesMade Mistakes is the personal website and blog of Michael Rose.2024-03-13T19:01:54+00:00© 2024 Michael Rose. Some rights reserved.https://mademistakes.com/images/made-mistakes-icon.528a92b2bcbac265e82e29a951516983c0b924df0a72f56d82ccdf619be42e39.svghttps://mademistakes.com/images/made-mistakes-logo.ccaccc4d501fb3fc62eaf7566fa48fc0069a3bad14a7f8bc27af16650d37d6c7.svgMichael Rosehttps://mademistakes.com/And just like that, I was done with Netlifyhttps://mademistakes.com/notes/done-with-netlify/2024-02-29T09:53:40-05:002024-02-29T09:53:40-05:00<p>As someone who uses <strong>Netlify’s</strong> Starter plan to host this site, the following situation is the kind of nonsense that keeps me up late at night…</p>
<blockquote>
<p>On February 24th, an invoice was issued to a Starter plan user of Netlify for a substantial amount of bandwidth usage.</p>
<p>Our internal systems should have identified this rare case, and it should have been flagged by our systems before being sent to this user. We sincerely apologize for this situation and the anxiety it caused. Please know that we are taking this situation very seriously.</p>
<p>This situation has reinforced the urgency of our ongoing work to put safeguards in place in our billing system to ensure that this never happens again. No user of Netlify should ever be concerned that this could happen to them, and we are committed to fixing this issue immediately.</p>
<p>We will also be looking at our company practices, as there clearly was an opportunity for us to have better handled this situation.</p>
<p>Again, we apologize to the impacted user, as well as the community at large. We promise to do better.</p>
<p><cite>Netlify (<a href="https://twitter.com/Netlify/" rel="noopener">@Netlify</a>) <a href="https://twitter.com/Netlify/status/1762518910107033798?ref_src=twsrc%5Etfw">February 27, 2024</a></cite></p>
</blockquote>
<p>The fact that a small static site hosted <em>for free</em> could potentially <a href="https://www.reddit.com/r/webdev/comments/1b14bty/netlify_just_sent_me_a_104k_bill_for_a_simple/" rel="noopener">cost thousands of dollars</a> is insane. Up until now, I assumed hitting any bandwidth limits would take the site down until the next billing period (or pay any overage fees to turn it back on).</p>
<p>To mitigate the risk of this happening to me, I shut down Netlify deployments and migrated to <a href="https://pages.cloudflare.com" rel="noopener"><strong>Cloudflare Pages</strong></a>. On paper Cloudflare’s features are way more attractive and don’t imply free plans will be charged for going over any limits.</p>
<h2 id="free-plan-comparison"><a href="#free-plan-comparison" title="Permalink to Free plan comparison">Free plan comparison</a></h2><table>
<thead>
<tr>
<th>-</th>
<th style="text-align:center"><a href="https://www.cloudflare.com/plans/" rel="noopener">Cloudflare</a></th>
<th style="text-align:center"><a href="https://www.netlify.com/pricing/#core-pricing-table" rel="noopener">Netlify</a></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Bandwidth</strong></td>
<td style="text-align:center">Unlimited</td>
<td style="text-align:center">100GB per month (then $55 per 100GB)</td>
</tr>
<tr>
<td><strong>Builds</strong></td>
<td style="text-align:center">1 concurrent build, 500 times per month</td>
<td style="text-align:center">1 concurrent build, 300 minutes per month (then $7 per 500)</td>
</tr>
<tr>
<td><strong>Sites</strong></td>
<td style="text-align:center">Unlimited</td>
<td style="text-align:center">500</td>
</tr>
<tr>
<td><strong>Preview deployments</strong></td>
<td style="text-align:center">✔</td>
<td style="text-align:center">✔</td>
</tr>
<tr>
<td><strong>Custom domains</strong></td>
<td style="text-align:center">100 per project</td>
<td style="text-align:center">~90 apex domains</td>
</tr>
<tr>
<td><strong>SSL certificate</strong></td>
<td style="text-align:center">✔</td>
<td style="text-align:center">✔</td>
</tr>
<tr>
<td><strong>Site analytics</strong></td>
<td style="text-align:center">Free</td>
<td style="text-align:center">$9 per site per month</td>
</tr>
<tr>
<td><strong>Files</strong></td>
<td style="text-align:center">Sites can contain 20,000 files — maximum asset size is 25 MiB</td>
<td style="text-align:center">Limit of 54,000 files per directory — maximum asset size is 100 MiB</td>
</tr>
<tr>
<td><strong>Header rules</strong></td>
<td style="text-align:center">Maximum of 100 rules</td>
<td style="text-align:center">-</td>
</tr>
<tr>
<td><strong>Redirects</strong></td>
<td style="text-align:center">Maximum of 2,000 static redirects and 100 dynamic redirects. <a href="https://developers.cloudflare.com/rules/url-forwarding/bulk-redirects/" rel="noopener">Bulk Redirects</a> allow for much more at the account level.</td>
<td style="text-align:center">No apparent limit, recommended to keep under 10k redirects</td>
</tr>
<tr>
<td><strong>Content scraping protection</strong></td>
<td style="text-align:center">Protects text, images and email addresses from web scrapers</td>
<td style="text-align:center">-</td>
</tr>
<tr>
<td><strong>Members</strong></td>
<td style="text-align:center">Pages site can be managed by an unlimited number of users via the Cloudflare dashboard</td>
<td style="text-align:center">Limited to 1 free member, with unlimited Git Contributors</td>
</tr>
</tbody>
</table>
<h2 id="cloudflare-pages"><a href="#cloudflare-pages" title="Permalink to Cloudflare Pages">Cloudflare Pages</a></h2><p><a href="https://developers.cloudflare.com/pages/migrations/migrating-from-netlify/" title="Netlify to Cloudflare migration guide" rel="noopener">Migrating from Netlify to Cloudflare</a> was painless:</p>
<ol>
<li>Connect GitHub repository to Cloudflare Pages.</li>
<li>Configure Hugo build command.</li>
<li>Validate <code>*.pages.dev</code> site builds when pushing commits to GitHub.</li>
<li>Copy DNS records from Netlify over to Cloudflare.</li>
</ol>
<p>The hardest part was waiting for all the records to propagate across the internet.</p>
<p>My custom <a href="https://developers.cloudflare.com/pages/configuration/headers/" rel="noopener"><code>_headers</code></a> and <a href="https://developers.cloudflare.com/pages/configuration/redirects/" rel="noopener"><code>_redirects</code></a> files used by Netlify ported over great too as Cloudflare Pages uses the same.</p>
<div class="c-browser-frame">
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/cloudflare-pages-mm-build-log_hu561bf3032f812293a7be93ca51b3c84e_392510_400x0_resize_q75_box.jpg 400w, /images/cloudflare-pages-mm-build-log_hu561bf3032f812293a7be93ca51b3c84e_392510_800x0_resize_q75_box.jpg 800w, /images/cloudflare-pages-mm-build-log.jpg 2652w" src="/images/cloudflare-pages-mm-build-log_hu561bf3032f812293a7be93ca51b3c84e_392510_800x0_resize_q75_box.jpg"
alt="mademistakes.com Cloudflare Pages build log output" loading="lazy" decoding="async" width="2652" height="2038" style="background: #f7f7f7"/></p>
</div>
<p>One snag I encountered during the migration was long build times. In my previous workflow, I set up <strong>GitHub Actions</strong> and Netlify to cache all generated static assets between builds. For a site like mine with thousands of images, this slashes a cold build from 18 minutes down to a minute for incremental builds.</p>
<div class="c-browser-frame">
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/cloudflare-build-cache-beta_hu561bf3032f812293a7be93ca51b3c84e_97641_400x0_resize_q75_box.jpg 400w, /images/cloudflare-build-cache-beta_hu561bf3032f812293a7be93ca51b3c84e_97641_800x0_resize_q75_box.jpg 800w, /images/cloudflare-build-cache-beta.jpg 1966w" src="/images/cloudflare-build-cache-beta_hu561bf3032f812293a7be93ca51b3c84e_97641_800x0_resize_q75_box.jpg"
alt="Build cache settings in Cloudflare dashboard." loading="lazy" decoding="async" width="1966" height="638" style="background: #fefefe"/></p>
</div>
<p>Cloudflare Pages does support <a href="https://developers.cloudflare.com/pages/configuration/build-caching/" rel="noopener">build caching</a>, but only for <strong>Gatsby</strong>, <strong>Next.js</strong>, and <strong>Astro</strong> frameworks. It’s a beta feature, so I expect <a href="https://gohugo.io" rel="noopener"><strong>Hugo</strong></a> to be added in the future. For now I’ll have to live with 18-minute builds — unless I decide to use a GitHub Action again to build then deploy to Cloudflare using their <a href="https://developers.cloudflare.com/workers/wrangler/" rel="noopener"><strong>Wrangler</strong></a> CLI. We’ll see…</p>
<div class="c-browser-frame">
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/cloudflare-mm-web-analytics_hu561bf3032f812293a7be93ca51b3c84e_337619_400x0_resize_q75_box.jpg 400w, /images/cloudflare-mm-web-analytics_hu561bf3032f812293a7be93ca51b3c84e_337619_800x0_resize_q75_box.jpg 800w, /images/cloudflare-mm-web-analytics.jpg 1932w" src="/images/cloudflare-mm-web-analytics_hu561bf3032f812293a7be93ca51b3c84e_337619_800x0_resize_q75_box.jpg"
alt="Cloudflare Web Analytics dashboard showing Core Web Vital scores for mademistakes.com" loading="lazy" decoding="async" width="1932" height="1938" style="background: #fafcfa"/></p>
</div>
<p>Another bonus, Cloudflare gives you <a href="https://developers.cloudflare.com/analytics/web-analytics" rel="noopener"><em>privacy-first</em> site analytics</a> for free too. Which could be an option if I decide <strong>Google Analytics</strong> at some point.</p>
<p>And hell, if Cloudflare Pages turns out to be a dud or too restrictive I can always go back to self-hosting on the cheap.</p><p><a href="https://mademistakes.com/notes/done-with-netlify/?utm_source=atom_feed">And just like that, I was done with Netlify</a> was originally published on Made Mistakes.</p>DIY record cube back spacershttps://mademistakes.com/notes/diy-record-cube-back-spacers/2024-02-22T00:00:00-05:002024-02-22T00:00:00-05:00<p><img src="/images/record-spines-not-flush.jpg" alt="" loading="lazy" decoding="async" width="1280" height="960"></p><p>For the longest time, it has driven me mad that my records get out of alignment any time I slide one out to play. To solve this problem, <a href="https://turntablerevival.com/collections/premiumbackspacer" rel="noopener"><strong>Turntable Revival</strong></a> sells wooden back spacers for cube shelves (<em>Ikea Kallax</em>, <a href="https://www.target.com/p/6-cube-organizer-black-oak-brightroom-8482/-/A-83498092?preselect=83498092#lnk=sametab" rel="noopener"><em>Target Threshold</em></a>, <em>Walmart - Better Homes & Gardens</em>) to straighten your records out.</p>
<p>To save a buck I decided to MacGyver a cheap solution with <a href="https://www.michaels.com/product/16-x-20-black-foam-boards-3ct-10308906" rel="noopener">black foam board</a> and <a href="https://www.amazon.com/Acrylic-Adhesive-Waterproof-Removable-Mounting/dp/B0852XL3CC?crid=2MDPBZWLB6X5A&dib=eyJ2IjoiMSJ9.YhF0V2ykhhhV15a-ym4XOIp7IkPRBZr1Qrd4KCcmqkPKmgw4c8esIdE4a57y6OHX6231o3gnZB_JiOs2kB0q0JUhHAI8HDq6hnHJWhiTJfrxS7DxHncm7gH0QiHsx_R5VtkQaZhhckXa615FxJfNmsdUWLPt-aGb9XZYtw8OtXD1MKFkUoMaYktBfgSuNO4F0roV5X4U0NnAoC2cRb7yeeTZaxMrgAY6G1qSDwtkEJ6cngTLGoj0q23H7AFFDxrE6H6ozF4zxs2dfDariQuvaNTHWfEz77y7TTxVGYiGkfM.rjgM63bdYrwnbd4pF7EDu9bUKyqDMiuSOC2xzjLKyRM&dib_tag=se&keywords=double%2Bsided%2Bmounting%2Btape&qid=1708622444&sprefix=double%2Bsided%2Bmounting%2Btape%2Caps%2C260&sr=8-40&th=1&linkCode=ll1&tag=mademist-20&linkId=05b3e856c284606a34e325ba525dc266&language=en_US&ref_=as_li_ss_tl" rel="noopener">double-sided tape</a>.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/records-black-foam-board_hu73746b0f02e7f1fa41b948fd486c55f1_549611_400x0_resize_q75_box.jpg 400w, /images/records-black-foam-board_hu73746b0f02e7f1fa41b948fd486c55f1_549611_800x0_resize_q75_box.jpg 800w, /images/records-black-foam-board.jpg 1280w" src="/images/records-black-foam-board_hu73746b0f02e7f1fa41b948fd486c55f1_549611_800x0_resize_q75_box.jpg"
alt="Sheet of 16" x 20" black foam board was placed in front of cube shelves filled with vinyl records." loading="lazy" decoding="async" width="1280" height="960" style="background: #36332c"/></p>
<h2 id="procedure"><a href="#procedure" title="Permalink to Procedure">Procedure</a></h2><p><strong>Step 1:</strong> Measure the length of each cube. I have two 6-cube <a href="https://www.target.com/p/6-cube-organizer-black-oak-brightroom-8482/-/A-83498092?preselect=83498092#lnk=sametab" rel="noopener"><em>Target Threshold</em></a> organizers, with each cube measuring 13 ½" wide.</p>
<figure data-columns="auto">
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/empty-cube-shelf_hub3abc4624ad8ab6b415e9141629e0acf_570373_400x0_resize_q75_box.jpg 400w, /images/empty-cube-shelf_hub3abc4624ad8ab6b415e9141629e0acf_570373_800x0_resize_q75_box.jpg 800w, /images/empty-cube-shelf.jpg 1280w" src="/images/empty-cube-shelf_hub3abc4624ad8ab6b415e9141629e0acf_570373_800x0_resize_q75_box.jpg"
alt="Empty cube shelf." loading="lazy" decoding="async" width="1280" height="960" style="background: #40392f"/></p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/cube-shelf-depth_hu81ed3890366f247189ad68420cdf0102_388397_400x0_resize_q75_box.jpg 400w, /images/cube-shelf-depth_hu81ed3890366f247189ad68420cdf0102_388397_800x0_resize_q75_box.jpg 800w, /images/cube-shelf-depth.jpg 1280w" src="/images/cube-shelf-depth_hu81ed3890366f247189ad68420cdf0102_388397_800x0_resize_q75_box.jpg"
alt="Measuring space from the record spine to the shelf’s front." loading="lazy" decoding="async" width="1280" height="960" style="background: #434038"/></p>
<figcaption>Measure the empty shelf space to determine the back spacer’s size.</figcaption>
</figure>
<p><strong>Step 2:</strong> Grab a record, place it to the far left and align it with the cube’s back edge. Then measure the distance from the record’s spine to the front of the cube. For my shelving, this ended up being 2" deep.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/record-in-empty-shelf_hufb245a7ff92448a634478e5bd9f6a85f_514782_400x0_resize_q75_box.jpg 400w, /images/record-in-empty-shelf_hufb245a7ff92448a634478e5bd9f6a85f_514782_800x0_resize_q75_box.jpg 800w, /images/record-in-empty-shelf.jpg 1280w" src="/images/record-in-empty-shelf_hufb245a7ff92448a634478e5bd9f6a85f_514782_800x0_resize_q75_box.jpg"
alt="Kaospilot LP album placed on a cube shelf." loading="lazy" decoding="async" width="1280" height="960" style="background: #443f38"/></p>
<aside class="c-notice"><strong class="c-notice_heading">Pro tip</strong><div class="c-notice_content"><p>To leave room for lighting (or if you want the records recessed some) subtract about an inch from the measurement in step 2.</p></div>
</aside>
<p><strong>Step 3:</strong> With a ruler and a pencil add guidelines to the foam board and use a sharp utility blade to cut the spacers.</p>
<p>I took a <a href="https://www.michaels.com/product/16-x-20-black-foam-boards-3ct-10308906" rel="noopener">16" x 20" foam board</a>, cut the shorter side down to 13 ½" and then cut that down at 2" intervals — ending up with 10 back spacers.</p>
<p><strong>Step 4:</strong> Using rubbing alcohol and a paper towel, clean the bottom side of each cube you’re adding a spacer to. Once dry, cut a strip of <a href="https://www.amazon.com/Acrylic-Adhesive-Waterproof-Removable-Mounting/dp/B0852XL3CC?crid=2MDPBZWLB6X5A&dib=eyJ2IjoiMSJ9.YhF0V2ykhhhV15a-ym4XOIp7IkPRBZr1Qrd4KCcmqkPKmgw4c8esIdE4a57y6OHX6231o3gnZB_JiOs2kB0q0JUhHAI8HDq6hnHJWhiTJfrxS7DxHncm7gH0QiHsx_R5VtkQaZhhckXa615FxJfNmsdUWLPt-aGb9XZYtw8OtXD1MKFkUoMaYktBfgSuNO4F0roV5X4U0NnAoC2cRb7yeeTZaxMrgAY6G1qSDwtkEJ6cngTLGoj0q23H7AFFDxrE6H6ozF4zxs2dfDariQuvaNTHWfEz77y7TTxVGYiGkfM.rjgM63bdYrwnbd4pF7EDu9bUKyqDMiuSOC2xzjLKyRM&dib_tag=se&keywords=double%2Bsided%2Bmounting%2Btape&qid=1708622444&sprefix=double%2Bsided%2Bmounting%2Btape%2Caps%2C260&sr=8-40&th=1&linkCode=ll1&tag=mademist-20&linkId=05b3e856c284606a34e325ba525dc266&language=en_US&ref_=as_li_ss_tl" rel="noopener">double-sided tape</a> for each spacer and remove one side of the backing.</p>
<p>Place the tape (sticky side down) onto the spacer and run your finger over it to make a good connection. Then remove the top piece of backing and place the spacer into the shelf making sure its edge is aligned with the back of the cube.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/black-foam-board-double-sided-tape_hu278eff5957b2b7a034e6dca0a325e218_273242_400x0_resize_q75_box.jpg 400w, /images/black-foam-board-double-sided-tape_hu278eff5957b2b7a034e6dca0a325e218_273242_800x0_resize_q75_box.jpg 800w, /images/black-foam-board-double-sided-tape.jpg 1276w" src="/images/black-foam-board-double-sided-tape_hu278eff5957b2b7a034e6dca0a325e218_273242_800x0_resize_q75_box.jpg"
alt="Black foam board with a strip of double-sided tape stuck to the back." loading="lazy" decoding="async" width="1276" height="787" style="background: #36302d"/></p>
<p>Press down firmly on the foam board, using a circular motion to fully adhere the tape to the shelf.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/black-foam-board-shelf-aligned_huf65bc0706f85a8a2195140c3f00c05d4_556956_400x0_resize_q75_box.jpg 400w, /images/black-foam-board-shelf-aligned_huf65bc0706f85a8a2195140c3f00c05d4_556956_800x0_resize_q75_box.jpg 800w, /images/black-foam-board-shelf-aligned.jpg 1280w" src="/images/black-foam-board-shelf-aligned_huf65bc0706f85a8a2195140c3f00c05d4_556956_800x0_resize_q75_box.jpg"
alt="Black foam board back spacer flush mounted to the shelf’s back." loading="lazy" decoding="async" width="1280" height="960" style="background: #2c271e"/></p>
<p><strong>Step 5:</strong> Slide a record into the cube shelf and verify you are happy with the placement. Repeat the process for the remaining cubes.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/favorite-lps-record-player-2024_hu07c59bce5d294e8594ad24284f120ed0_797168_400x0_resize_q75_box.jpg 400w, /images/favorite-lps-record-player-2024_hu07c59bce5d294e8594ad24284f120ed0_797168_800x0_resize_q75_box.jpg 800w, /images/favorite-lps-record-player-2024.jpg 961w" src="/images/favorite-lps-record-player-2024_hu07c59bce5d294e8594ad24284f120ed0_797168_800x0_resize_q75_box.jpg"
alt="Records flush aligned in Target Threshold cube shelves." loading="lazy" decoding="async" width="961" height="1280" style="background: #393734"/></p><p><a href="https://mademistakes.com/notes/diy-record-cube-back-spacers/?utm_source=atom_feed">DIY record cube back spacers</a> was originally published on Made Mistakes.</p>Goodbye Gatsby, Hello Hugohttps://mademistakes.com/notes/goodbye-gatsby-hello-hugo/2023-01-14T14:06:05-05:002023-01-14T14:06:05-05:00<p><img src="/images/goodbye-gatsby-cover.jpg" alt="" loading="lazy" decoding="async" width="2520" height="1633"></p><p>Personal sites are a great testing ground for mixing it up and experimenting. Back when <a href="https://www.gatsbyjs.com" rel="noopener"><strong>Gatsby</strong></a> first started buzzing in Jamstack circles, I used that as my excuse to migrate off Jekyll and learn something new.</p>
<p>As someone who avoids writing JavaScript whenever possible, there were considerable growing pains as dug into the <a href="https://reactjs.org" rel="noopener"><strong>React</strong></a>, <a href="https://graphql.org/" rel="noopener"><strong>GraphQL</strong></a>, and NPM ecosystems. It took time, but I got the site to a place on par with what I had achieved with <a href="https://jekyllrb.com/" rel="noopener"><strong>Jekyll</strong></a>.</p>
<p>As I used Gatsby more, the reliance on plugins started frustrating me. External dependencies lagging behind Gatsby made updating a chore, and in the case of <a href="https://www.gatsbyjs.com/docs/reference/release-notes/migrating-from-v4-to-v5/" rel="noopener">migrating to Gatsby 5</a> — impossible.</p>
<p>For example, critical remark plugins related to <a href="https://github.com/zestedesavoir/zmarkdown/issues/416" rel="noopener">Markdown parsing became stuck</a> blocking me from upgrading, unless I wanted to stop using them… which I didn’t.</p>
<h2 id="static-site-generator-choices"><a href="#static-site-generator-choices" title="Permalink to Static site generator choices">Static site generator choices</a></h2><p>So I started looking around. <a href="/articles/going-static/">Jekyll seemed like a logical</a> choice, but returning to Ruby… no thanks. Not to mention the <a href="https://talk.jekyllrb.com/t/is-the-jekyll-project-dead/6820/20" rel="noopener">appearance of Jekyll fizzling out</a> wasn’t doing it any favors.</p>
<p><a href="https://www.11ty.dev/" rel="noopener"><strong>Eleventy</strong> (11ty)</a> is what all the cool kids are using, and it shares a lot of Jekyll-isms. And then there is <a href="https://gohugo.io/" rel="noopener"><strong>Hugo</strong></a>, which made a splash years back when <a href="https://www.smashingmagazine.com/2019/05/switch-wordpress-hugo/" rel="noopener"><strong>Smashing Magazine</strong> switched</a> from WordPress to it. Maybe these were good static site generators to explore?</p>
<p>For a few weeks, I worked on a proof of concept, migrating layouts and content from Gatsby over to 11ty. It all felt <em>Jekylly</em> but with added flexibility and none of the Ruby pain.</p>
<p>Then I encountered a roadblock (more of a detour) with <a href="https://www.11ty.dev/docs/collections/" rel="noopener">collections</a> and how 11ty uses tags to assign them. I’m sure I could have worked around it, but conceptually it clashes with how I organize content: <strong>categories as sections > tags for loosely related topics</strong>.</p>
<p>On the other hand, Hugo seemed purpose-built for me. Full of strong conventions on <em>how to do things</em>, but with enough flexibility to bend as needed. This was a stark contrast with what I had experienced trying to organize and write content with Gatsby, 11ty, and to a lesser degree Jekyll. With those SSGs, I often felt like I was rolling my own solution to do something that <em>should be standard</em>.</p>
<h2 id="feature-comparisons"><a href="#feature-comparisons" title="Permalink to Feature comparisons">Feature comparisons</a></h2><p>Want to dump a bunch of Markdown files in a folder with a sprinkling of front matter… you can. But if you want to level up, Hugo offers up ways of <a href="https://gohugo.io/content-management/organization/#page-bundles" rel="noopener">bundling pages</a> into <a href="https://gohugo.io/content-management/sections/" rel="noopener">sections</a> alongside <a href="https://gohugo.io/content-management/page-resources/" rel="noopener">resources</a> so you can keep your content nice and tidy.</p>
<p>When following conventions Hugo expects, the file structure is used to inform a page’s <strong>section</strong>, <strong>content type</strong>, whether a branch or leaf node, its layout, and more. Making the building of menus, <a href="https://gohugo.io/content-management/sections/#example-breadcrumb-navigation" rel="noopener">breadcrumb navigation</a>, list pages, and <a href="https://gohugo.io/templates/pagination/" rel="noopener">pagination</a> straightforward as they are fully integrated into Hugo’s core features.</p>
<p>Building basic blog sites with posts listed in reverse chronological order, it’s easy to miss out on all that Hugo offers. And to be honest, unless you have prior experience with static site generators or fully read through Hugo’s (at times obtuse) documentation, you can miss out on the benefits of having the following features.</p>
<h3 id="markdown-render-hooks"><a href="#markdown-render-hooks" title="Permalink to Markdown render hooks">Markdown render hooks</a></h3><p>Coming from Gatsby, I now expect all static site generators to take image references written in Markdown and spit out resized versions with appropriate responsive <code>srcset</code> markup.</p>
<p>I give you this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">![<span class="nt">Chicken wings covered in hot sauce</span>](<span class="na">chicken-wings.jpg</span>)</span></span></code></pre></div>
<p>You give me this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p"><</span><span class="nt">img</span> <span class="na">alt</span><span class="o">=</span><span class="s">"Chicken wings covered in hot sauce"</span> <span class="na">src</span><span class="o">=</span><span class="s">"chicken-wings.jpg"</span> <span class="na">srcset</span><span class="o">=</span><span class="s">"chicken-wings-300.jpg 300w, chicken-wings-600.jpg 600w, chicken-wings-900 900w"</span> <span class="na">sizes</span><span class="o">=</span><span class="s">"(max-width: 900px) 100vw, 900px"</span> <span class="na">loading</span><span class="o">=</span><span class="s">"lazy"</span> <span class="na">decoding</span><span class="o">=</span><span class="s">"async"</span> <span class="na">width</span><span class="o">=</span><span class="s">"900"</span> <span class="na">height</span><span class="o">=</span><span class="s">"600"</span><span class="p">></span></span></span></code></pre></div>
<p>I fought with Jekyll for years trying to do this. The common pitfalls are:</p>
<ul>
<li>Image assets live outside of your <code>_posts</code> or collection folders. Linking to them with relative paths in Markdown files is more difficult than it should be.</li>
<li>Every Jekyll <a href="https://github.com/wildlyinaccurate/jekyll-responsive-image" rel="noopener">responsive images plugin</a> I tried used some flavor of <strong>ImageMagick</strong> to convert images. ImageMagick is slow and slogging through thousands of files crushed my build times. You can sort of get around this by using a combination of <a href="https://sharp.pixelplumbing.com/" rel="noopener"><strong>Sharp</strong></a> and <a href="https://gulpjs.com/" rel="noopener"><strong>Gulp</strong></a>, but it hardly feels like the <em>Jekyll way</em>.</li>
<li>Using Liquid tags e.g., <code>{% responsive_image path: chicken-wings.jpg alt: "Chicken wings covered in hot sauce" %}</code> is less than ideal if you’re committed to an all-Markdown workflow. A <a href="https://gist.github.com/tadamcz/869802c919c72172486f219751904108" rel="noopener"><code>pre_render</code> hook plugin</a> that replaces Markdown image references with an appropriate <code>{% responsive_image %}</code> tag can get around this, but feels hacky.</li>
</ul>
<p>11ty with the Sharp-based <a href="https://github.com/11ty/eleventy-img" rel="noopener"><strong>eleventy-img</strong></a> plugin and a <a href="https://tomichen.com/blog/posts/20220416-responsive-images-in-markdown-with-eleventy-image/" rel="noopener">custom image renderer</a> for <strong>markdown-it</strong> can get you closer. Still, for me, there was enough flakiness going on — missing images and random build crashes — encouraging me to look elsewhere. It’s an <a href="https://github.com/11ty/eleventy/issues/2428#issuecomment-1152703912" rel="noopener">open issue</a>, so here’s hoping that someday there will be an enhancement to improve it.</p>
<p>Then there’s Hugo, which lets you customize how Markdown outputs to HTML with what it calls <a href="https://gohugo.io/templates/render-hooks/" rel="noopener"><strong>render hooks</strong></a>. These render hook templates are standard HTML with the same <a href="https://gohugo.io/templates/introduction/" rel="noopener">templating logic</a> used to write shortcodes, partials, and layouts. Something I appreciate, as writing HTML comes more naturally to me than JavaScript or Ruby.</p>
<p>I’m using a render hook (<code>render-image.html</code>) to process Markdown images into responsive image markup like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl">{{- $sizes := (slice "300" "768" "1024" "1280") -}}
</span></span><span class="line"><span class="cl">{{- /* Get file that matches the filename as specified as src="" */ -}}
</span></span><span class="line"><span class="cl">{{- $src := .Page.Resources.GetMatch .Destination -}}
</span></span><span class="line"><span class="cl">{{- if and (not $src) .Page.File -}}
</span></span><span class="line"><span class="cl"> {{- $path := path.Join .Page.File.Dir .Destination -}}
</span></span><span class="line"><span class="cl"> {{- $src = resources.Get $path -}}
</span></span><span class="line"><span class="cl">{{- end -}}
</span></span><span class="line"><span class="cl">{{- $alt := .PlainText | safeHTML -}}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">{{- /* Conditional for resources that aren't in page bundle. */ -}}
</span></span><span class="line"><span class="cl">{{- if $src -}}
</span></span><span class="line"><span class="cl"> {{- $color := delimit (first 1 $src.Colors) "" -}}
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">img</span> <span class="na">sizes</span><span class="o">=</span><span class="s">"(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"</span>
</span></span><span class="line"><span class="cl"> <span class="na">srcset</span><span class="o">=</span><span class="s">"
</span></span></span><span class="line"><span class="cl"><span class="s"> {{- range $sizes }}
</span></span></span><span class="line"><span class="cl"><span class="s"> {{- if ge $src.Width . }}{{ ($src.Resize (printf "</span><span class="err">%</span><span class="na">sx</span><span class="err">"</span> <span class="err">.)).</span><span class="na">RelPermalink</span> <span class="err">}}</span> <span class="err">{{</span> <span class="err">(</span><span class="na">printf</span> <span class="err">"%</span><span class="na">sw</span><span class="err">"</span> <span class="err">.)</span> <span class="err">}},</span> <span class="err">{{</span> <span class="na">end</span> <span class="err">}}</span>
</span></span><span class="line"><span class="cl"> <span class="err">{{</span><span class="na">-</span> <span class="na">end</span> <span class="na">-</span><span class="err">}}</span> <span class="err">{{</span> <span class="err">$</span><span class="na">src</span><span class="err">.</span><span class="na">RelPermalink</span> <span class="err">}}</span> <span class="err">{{</span> <span class="err">$</span><span class="na">src</span><span class="err">.</span><span class="na">Width</span> <span class="err">}}</span><span class="na">w</span><span class="err">"</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="err">{{</span><span class="na">-</span> <span class="err">/*</span> <span class="na">If</span> <span class="na">smaller</span> <span class="na">than</span> <span class="na">768px</span> <span class="na">wide</span><span class="err">,</span> <span class="na">then</span> <span class="na">load</span> <span class="na">the</span> <span class="na">original</span> <span class="err">*/</span> <span class="na">-</span><span class="err">}}</span>
</span></span><span class="line"><span class="cl"> <span class="err">{{</span> <span class="na">if</span> <span class="na">ge</span> <span class="err">$</span><span class="na">src</span><span class="err">.</span><span class="na">Width</span> <span class="err">"</span><span class="na">768</span><span class="err">"</span> <span class="err">}}</span> <span class="na">src</span><span class="o">=</span><span class="s">"{{ ($src.Resize "</span><span class="na">768x</span><span class="err">").</span><span class="na">RelPermalink</span> <span class="err">}}"</span>
</span></span><span class="line"><span class="cl"> <span class="err">{{</span> <span class="na">else</span> <span class="err">}}</span><span class="na">src</span><span class="o">=</span><span class="s">"{{ $src.RelPermalink }}"</span>
</span></span><span class="line"><span class="cl"> <span class="err">{{</span><span class="na">-</span> <span class="na">end</span> <span class="na">-</span><span class="err">}}</span>
</span></span><span class="line"><span class="cl"> <span class="na">alt</span><span class="o">=</span><span class="s">"{{ $alt }}"</span> <span class="na">loading</span><span class="o">=</span><span class="s">"lazy"</span> <span class="na">decoding</span><span class="o">=</span><span class="s">"async"</span> <span class="na">width</span><span class="o">=</span><span class="s">"{{ $src.Width }}"</span> <span class="na">height</span><span class="o">=</span><span class="s">"{{ $src.Height }}"</span> <span class="err">{{</span> <span class="na">if</span> <span class="na">ne</span> <span class="err">$</span><span class="na">src</span><span class="err">.</span><span class="na">MediaType</span><span class="err">.</span><span class="na">SubType</span> <span class="err">"</span><span class="na">png</span><span class="err">"</span> <span class="err">}}</span><span class="na">style</span><span class="o">=</span><span class="s">"background: {{ $color }}"</span><span class="err">{{</span><span class="na">-</span> <span class="na">end</span> <span class="na">-</span><span class="err">}}</span>
</span></span><span class="line"><span class="cl"> <span class="p">/></span>
</span></span><span class="line"><span class="cl">{{- else -}}
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">img</span> <span class="na">src</span><span class="o">=</span><span class="s">"{{ .Destination | safeURL }}"</span> <span class="na">alt</span><span class="o">=</span><span class="s">"{{ $alt }}"</span> <span class="na">loading</span><span class="o">=</span><span class="s">"lazy"</span> <span class="na">decoding</span><span class="o">=</span><span class="s">"async"</span> <span class="p">/></span>
</span></span><span class="line"><span class="cl">{{- end -}}</span></span></code></pre></div>
<p>Add anchor links (<code>render-heading.html</code>) to headings:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p"><</span><span class="nt">h</span><span class="err">{{</span> <span class="err">.</span><span class="na">Level</span> <span class="err">}}</span> <span class="na">id</span><span class="o">=</span><span class="s">"{{ .Anchor | safeURL }}"</span><span class="p">></span>
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"#{{ .Anchor | safeURL }}"</span> <span class="na">title</span><span class="o">=</span><span class="s">"Permalink to {{ .Text | plainify }}"</span><span class="p">></span>{{ .Text | safeHTML }}<span class="p"></</span><span class="nt">a</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="err"><</span>/h{{ .Level }}></span></span></code></pre></div>
<p>And add a <code>rel="noopener"</code> attribute to external links (<code>render-link.html</code>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"{{ .Destination | safeURL }}"</span><span class="err">{{</span> <span class="na">with</span> <span class="err">.</span><span class="na">Title</span><span class="err">}}</span> <span class="na">title</span><span class="o">=</span><span class="s">"{{ . }}"</span><span class="err">{{</span> <span class="na">end</span> <span class="err">}}{{</span> <span class="na">if</span> <span class="na">strings</span><span class="err">.</span><span class="na">HasPrefix</span> <span class="err">.</span><span class="na">Destination</span> <span class="err">"</span><span class="na">http</span><span class="err">"</span> <span class="err">}}</span> <span class="na">rel</span><span class="o">=</span><span class="s">"noopener"</span><span class="err">{{</span> <span class="na">end</span> <span class="err">}}</span><span class="p">></span>{{ .Text | safeHTML }}<span class="p"></</span><span class="nt">a</span><span class="p">></span></span></span></code></pre></div>
<h3 id="archetype-templates"><a href="#archetype-templates" title="Permalink to Archetype templates">Archetype templates</a></h3><p>I write new posts so infrequently that it’s not uncommon for me to forget the names of front matter fields used in site layouts. To get around this I clone a <em>starter</em> Markdown document with a pre-configured placeholder front matter and draft a new post from that.</p>
<p>Hugo improves on this with the concept of <a href="https://gohugo.io/content-management/archetypes/" rel="noopener">archetypes</a> — directory-based template files used for creating new content.</p>
<p>Archetype templates look a lot like one of my starter <code>.md</code> files but with template logic and variables to pre-populate front matter fields like <code>title</code> and <code>date</code>.</p>
<p>When running the <code>hugo new</code> command to create a new file, an appropriate archetype is used based on the document’s file path. This flexibility allows you to have different sets of archetype templates based on content type or section.</p>
<p>My <code>/archetypes/default.md</code> template looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s2">"{{ replace .Name "</span>-<span class="s2">" "</span><span class="w"> </span><span class="s2">" | title }}"</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">date</span><span class="p">:</span><span class="w"> </span>{{<span class="w"> </span><span class="l">.Date }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">last_modified_at</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">draft</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">description</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">image</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">toc</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span></span></span></code></pre></div>
<p>Which when used with <code>hugo new notes/my-new-note.md</code> generates a new Markdown file with the following starter contents:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s2">"My New Note"</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">date</span><span class="p">:</span><span class="w"> </span><span class="ld">2023-01-12T14:04:39</span><span class="m">-05</span><span class="p">:</span><span class="m">00</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">last_modified_at</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">draft</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">description</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">image</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">toc</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span></span></span></code></pre></div>
<p>The simplicity of creating templates in the same YAML and Markdown format as the rest of the site content makes a ton of sense to me. While other static site generators require: a content management system, scripts, task runners, or in Jekyll-land the <a href="https://github.com/jekyll/jekyll-compose" rel="noopener"><strong>jekyll-compose</strong></a> Ruby gem — to do what Hugo does natively.</p>
<h3 id="taxonomies"><a href="#taxonomies" title="Permalink to Taxonomies">Taxonomies</a></h3><p>If you use taxonomies like <code>categories</code> or <code>tags</code> to group content, the popular SSGs have you covered as Jekyll has <a href="https://jekyllrb.com/docs/collections/" rel="noopener">collection support</a> to do custom grouping, but it’s not as robust as what Hugo offers.</p>
<p>11ty also has collections but <a href="https://www.11ty.dev/docs/collections/" rel="noopener">confusingly calls them <code>tags</code></a>, which is inconvenient if you’re coming from Jekyll or any other blogging platform.</p>
<p>Gatsby has no concept of categories or tags and it is up to you to structure, organize, assign relationships, and write custom logic to filter content. I had a hell of a time writing my <code>gatsby-node.js</code> file to generate paginated archive pages for categories and tags.</p>
<p>And then there is Hugo, which <a href="https://gohugo.io/content-management/taxonomies/" rel="noopener">automatically creates taxonomy</a> pages for categories and tags. If that’s not enough for you, Hugo also natively supports:</p>
<ul>
<li>Creating custom taxonomies to handle relationships other than categories and tags.</li>
<li>Ordering of taxonomies by weights allows the same piece of content to appear in different positions in different taxonomies.</li>
<li><a href="https://gohugo.io/content-management/taxonomies/#add-custom-metadata-to-a-taxonomy-or-term" rel="noopener">Adding metadata to a taxonomy or term</a>, something I used to hack together with YAML data files in Jekyll and Gatsby.</li>
</ul>
<h3 id="content-excerpts"><a href="#content-excerpts" title="Permalink to Content excerpts">Content excerpts</a></h3><p>Gatsby, Jekyll, and Hugo all have support for automatically creating shortened excerpts of a page’s main content to be used as teaser/preview text in RSS feeds, meta descriptions, and the like. Each of these SSGs uses separators to mark what part of the content should be used as an excerpt.</p>
<ul>
<li>Gatsby (via <a href="https://www.gatsbyjs.com/plugins/gatsby-transformer-remark/#excerpts" rel="noopener"><strong>gatsby-transformer-remark</strong></a>) allows you to configure the length of excerpts and their format (plain text, HTML, or Markdown).</li>
<li>Jekyll uses the <a href="https://jekyllrb.com/docs/posts/#post-excerpts" rel="noopener">first paragraph as an excerpt</a>.</li>
<li>Hugo calls them <a href="https://gohugo.io/content-management/summaries/" rel="noopener">content summaries</a> and uses the first 70 words as an excerpt.</li>
</ul>
<p>11ty requires <a href="https://www.11ty.dev/docs/data-frontmatter-customize/" rel="noopener">front matter customizations to parse excerpts</a> and separators from content to function how the three SSGs above do.</p>
<h3 id="image-processing"><a href="#image-processing" title="Permalink to Image processing">Image processing</a></h3><p>Gatsby, Hugo, and 11ty all support image processing. Gatsby has the advantage if you’re using components as it can do responsive images with <a href="https://using-gatsby-image.gatsbyjs.org/" rel="noopener"><em>trendy loading effects</em></a> like blur up, background color, and traced SVG via props and GraphQL queries.</p>
<p>On the flip side, if you’re bringing your own HTML and need a utility to transform and manipulate images — Hugo and 11ty have you covered.</p>
<p>The differences come down to how images are processed and rendered:</p>
<ol>
<li>Gatsby has <a href="https://www.gatsbyjs.com/plugins/gatsby-plugin-image/#using-the-gatsby-image-components" rel="noopener">React image components</a> and the magical black box that is <a href="https://www.gatsbyjs.com/plugins/gatsby-remark-images/" rel="noopener"><strong>gatsby-remark-images</strong></a>.</li>
<li>11ty has <a href="https://www.11ty.dev/docs/plugins/image/" rel="noopener"><strong>eleventy-img</strong></a> which can be used in layouts and shortcodes.</li>
<li>Hugo has image filters you can weave into layouts, shortcodes, and render hooks.</li>
</ol>
<p>No surprise here, but I prefer the Hugo way. Using variables and piping on filters feels more natural than GraphQL queries and JavaScript. For example, a <code>feature</code> image defined in the front matter can be transformed (resized to <code>600px</code> wide) like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl">{{ $source := .Page.Resources.GetMatch .Params.feature }}
</span></span><span class="line"><span class="cl">{{ $image := $source.Resize "600x" }}
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">img</span> <span class="na">src</span><span class="o">=</span><span class="s">"{{ $image.RelPermalink }}"</span> <span class="na">width</span><span class="o">=</span><span class="s">"{{ $image.Width }}"</span> <span class="na">height</span><span class="o">=</span><span class="s">"{{ $image.Height }}"</span> <span class="na">alt</span><span class="o">=</span><span class="s">""</span><span class="p">></span></span></span></code></pre></div>
<p>Adjusting an image’s hue, brightness, sharpness, and adding overlays are a <a href="https://gohugo.io/functions/images/" rel="noopener">set of filters</a> away with Hugo.</p>
<h4 id="colors"><a href="#colors" title="Permalink to Colors">Colors</a></h4><p>I started using Hugo’s new <a href="https://gohugo.io/content-management/image-processing/#colors" rel="noopener">colors method</a> to return the dominant colors of an image. I then limit this array to the first value and set it as a <code>background-color</code> to serve as a <em>low quality image placeholder</em> (LQIP) before it fully loads.</p>
<p>Don’t worry, I’m also setting a <code>width</code> and <code>height</code> on all images to avoid layout shifts during load.</p>
<div class="c-browser-frame">
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/mm-gallery-lqip-screenshot_hu7fba9da1679ce4a56c592454604cb9c1_329251_400x0_resize_q75_box.jpg 400w, /images/mm-gallery-lqip-screenshot_hu7fba9da1679ce4a56c592454604cb9c1_329251_800x0_resize_q75_box.jpg 800w, /images/mm-gallery-lqip-screenshot.jpg 2732w" src="/images/mm-gallery-lqip-screenshot_hu7fba9da1679ce4a56c592454604cb9c1_329251_800x0_resize_q75_box.jpg"
alt="Screenshot of gallery page with loading images showing solid backgrounds" loading="lazy" decoding="async" width="2732" height="2236" style="background: #efefee"/></p>
</div>
<aside class="c-notice"><strong class="c-notice_heading">Pro tip</strong><div class="c-notice_content"><p>Gradient image placeholders pair well with Hugo’s <code>.Color</code> method described in this article on <a href="https://www.brycewray.com/posts/2022/09/new-way-lqips-hugo-0-104-0/" rel="noopener">how to generate LQIPs</a>.</p></div>
</aside>
<h3 id="related-content"><a href="#related-content" title="Permalink to Related content">Related content</a></h3><p>To encourage browsing, I include links to related content at the end of each post. I’ve managed these groupings and relationships:</p>
<ol>
<li>Manually in the front matter.</li>
<li>Automatically with plugins.</li>
</ol>
<p>Thankfully, both Hugo and Jekyll have built-in methods for accessing related content automatically.</p>
<p>Hugo can list a page’s related content based on the front matter parameters you define. While Jekyll’s related posts module uses <a href="https://github.com/jekyll/classifier-reborn" rel="noopener"><strong>classifier-reborn</strong></a> to create relationships, is limited to the posts collection, and is slow.</p>
<p>Each can be looped through using Go and Liquid:</p>
<p><strong>Related posts via Go:</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl">{{ $related := .Site.RegularPages.Related . | first 3 }}
</span></span><span class="line"><span class="cl">{{ with $related }}
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">h3</span><span class="p">></span>Related posts<span class="p"></</span><span class="nt">h3</span><span class="p">></span>
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">ul</span><span class="p">></span>
</span></span><span class="line"><span class="cl"> {{ range . }}
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">li</span><span class="p">><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"{{ .RelPermalink }}"</span><span class="p">></span>{{ .Title }}<span class="p"></</span><span class="nt">a</span><span class="p">></</span><span class="nt">li</span><span class="p">></span>
</span></span><span class="line"><span class="cl"> {{ end }}
</span></span><span class="line"><span class="cl"> <span class="p"></</span><span class="nt">ul</span><span class="p">></span>
</span></span><span class="line"><span class="cl">{{ end }}</span></span></code></pre></div>
<p><strong>Related posts via Liquid:</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl">{% if site.related_posts.size >= 1 %}
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">h3</span><span class="p">></span>Related posts<span class="p"></</span><span class="nt">h3</span><span class="p">></span>
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">ul</span><span class="p">></span>
</span></span><span class="line"><span class="cl"> {% for related_post in site.related_posts limit: 3 %}
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">li</span><span class="p">><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"{{ related_post.url }}"</span><span class="p">></span>{{ related_post.title }}<span class="p"></</span><span class="nt">a</span><span class="p">></</span><span class="nt">li</span><span class="p">></span>
</span></span><span class="line"><span class="cl"> {% endfor %}
</span></span><span class="line"><span class="cl"> <span class="p"></</span><span class="nt">ul</span><span class="p">></span>
</span></span><span class="line"><span class="cl">{% endif %}</span></span></code></pre></div>
<p>With Gatsby, I struggled with GraphQL queries and an earlier version of <a href="https://www.npmjs.com/package/gatsby-remark-related-posts/v/1.0.8" rel="noopener"><strong>gatsby-remark-related-posts</strong></a> to automatically determine related posts. From the documentation, <a href="https://github.com/sititou70/gatsby-remark-related-posts" rel="noopener">version 2 of the plugin</a> seems to have improved on the clunkiness. But I can’t verify if that’s true as I haven’t tried upgrading yet.</p>
<p>11ty didn’t appear to have a native way of <a href="https://github.com/11ty/eleventy/discussions/2534" rel="noopener">listing related content</a>. Methods I dug up involved manual effort and maintenance — add tags to content in the front matter then filter on those tags.</p>
<h3 id="automatic-table-of-contents"><a href="#automatic-table-of-contents" title="Permalink to Automatic table of contents">Automatic table of contents</a></h3><p>Gatsby’s <a href="https://www.gatsbyjs.com/plugins/gatsby-transformer-remark/#configuring-the-tableofcontents" rel="noopener">remark transformer</a>, Jekyll via a <a href="https://kramdown.gettalong.org/converter/html.html#toc" rel="noopener">Kramdown <code>{: toc}</code> reference</a>, 11ty plugin <a href="https://github.com/jdsteinbach/eleventy-plugin-toc" rel="noopener"><strong>eleventy-plugin-toc</strong></a>, and Hugo all take Markdown files and generate a <a href="https://gohugo.io/content-management/toc/" rel="noopener">table of contents</a> from the headings. Useful for creating jump links to skip lengthy bits of content.</p>
<h2 id="conclusion"><a href="#conclusion" title="Permalink to Conclusion">Conclusion</a></h2><p>If it wasn’t clear already — I ditched Gatsby and migrated this site over to Hugo. Hugo is often viewed as the fastest static site generator… it is.</p>
<p>It excels at <a href="https://gohugo.io/content-management/" rel="noopener">content management</a>, with a flexible organizational structure, multilingual mode, and menu system that all integrate with one another. It is clear that Hugo’s maintainers carefully consider each feature and how they should work together.</p>
<p><em>Hugo has ruined me.</em> Other static site generators that rely on community plugins and copypasta to fill out their gaps don’t cut it anymore…</p><p><a href="https://mademistakes.com/notes/goodbye-gatsby-hello-hugo/?utm_source=atom_feed">Goodbye Gatsby, Hello Hugo</a> was originally published on Made Mistakes.</p>Adding last modified timestamps with Githttps://mademistakes.com/notes/adding-last-modified-timestamps-with-git/2021-08-04T20:34:59-04:002021-10-28T10:00:22-04:00<p>A common ask of <strong>Jekyll</strong>, <strong>Gatsby</strong>, and other static site generator users is, “how do I automatically set the date in YAML front matter?” Today I learned you can do just that with a Git <a href="https://git-scm.com/docs/githooks#_pre_commit" rel="noopener">pre-commit</a> hook.</p>
<p>If your project has been setup with <code>git init</code> you should have a Git hooks directory with several sample files to inspect.</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── .git
│ └── hooks
│ ├── applypatch-msg.sample
│ ├── commit-msg.sample
│ ├── post-update.sample
│ ├── pre-applypatch.sample
│ ├── pre-commit.sample
│ ├── ...</code></pre></div>
<p>You can either create a new file named <code>pre-commit</code> inside of the <code>hooks</code> directory (or rename the <code>.sample</code> file). Then add the following shell script:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="cp">#!/bin/sh
</span></span></span><span class="line"><span class="cl"><span class="cp"></span><span class="c1"># Contents of .git/hooks/pre-commit</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Replace `last_modified_at` timestamp with current time</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">git diff --cached --name-status <span class="p">|</span> egrep -i <span class="s2">"^(A|M).*\.(md)</span>$<span class="s2">"</span> <span class="p">|</span> <span class="k">while</span> <span class="nb">read</span> a b<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl"> cat <span class="nv">$b</span> <span class="p">|</span> sed <span class="s2">"/---.*/,/---.*/s/^last_modified_at:.*</span>$<span class="s2">/last_modified_at: </span><span class="k">$(</span>date -u <span class="s2">"+%Y-%m-%dT%H:%M:%S"</span><span class="k">)</span><span class="s2">/"</span> > tmp
</span></span><span class="line"><span class="cl"> mv tmp <span class="nv">$b</span>
</span></span><span class="line"><span class="cl"> git add <span class="nv">$b</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span></span></span></code></pre></div>
<p>Now when you commit a modified file with Git, the value of <code>last_modified_at</code> will be replaced with the current time i.e., <code>YYYY-MM-DDThh:mm:ss</code>. <em>If you’re using a different front matter variable for modified timestamps, adjust the script above accordingly.</em></p>
<table>
<thead>
<tr>
<th>Before commit</th>
<th>After commit</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>last_modified_at: </code></td>
<td><code>last_modified_at: 2021-08-04T20:34:59</code></td>
</tr>
</tbody>
</table>
<p>I’ve always followed the convention of using <code>date</code> for published timestamp and <code>last_modified_at</code> for the modified timestamp. Mostly because core <strong>Jekyll</strong> plugins like <a href="https://github.com/jekyll/jekyll-sitemap" rel="noopener">jekyll-sitemap</a> and <a href="https://github.com/jekyll/jekyll-feed" rel="noopener">jekyll-feed</a> support that front matter value.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s2">"My awesome Markdown post"</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">date</span><span class="p">:</span><span class="w"> </span><span class="ld">2020-01-01</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">last_modified_at</span><span class="p">:</span><span class="w"> </span><span class="ld">2021-08-04T20:34:59</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span></span></span></code></pre></div>
<aside class="c-notice"><div class="c-notice_content"><p><strong>Note:</strong> The only real gotcha I’ve hit with this method is making sure <code>last_modified_at</code> exists in the front matter. It can be a blank value, but if the variable isn’t present then no timestamp is added when the file is committed.</p></div>
</aside>
<p>I’d be interested if the script could be improved on to append the date if <code>last_modified_at</code> hasn’t already been added, and replace the value if it has. Let me know below if you have any improves there.</p>
<p>I’m also not sure if this pre-commit hook is automatically installed when the Git repository is cloned. Do I need to install it on both my iMac running macOS and laptop running Windows 10? Or does it come along for the ride when push/pulling from remote?</p><p><a href="https://mademistakes.com/notes/adding-last-modified-timestamps-with-git/?utm_source=atom_feed">Adding last modified timestamps with Git</a> was originally published on Made Mistakes.</p>URLs and links in Jekyllhttps://mademistakes.com/mastering-jekyll/how-to-link/2021-07-20T10:22:43-04:002023-12-08T09:18:01-05:00<p><img src="/images/how-to-link-illustration.png" alt="" loading="lazy" decoding="async" width="2270" height="1276"></p><p>Before you can link pages and assets together with Jekyll, you need to know how it assigns URLs (or permalinks<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>) to each. If you are a seasoned Jekyll users and have a good understanding of how this all works, skip down below to <a href="#how-to-link">linking methods</a>.</p>
<p>This tutorial will explain the various URL types, how to use them when building with Jekyll, and then how to link content and assets together.</p>
<h2 id="how-urls-work-in-jekyll"><a href="#how-urls-work-in-jekyll" title="Permalink to How URLs work in Jekyll">How URLs work in Jekyll</a></h2><p>Jekyll’s system for creating URLs is simple and flexible, allowing you to structure files how you want. That is…once you understand how permalinks work for posts, pages, collection documents, and static files.</p>
<p>Below are the core concepts and conventions you’ll need to get started with each. For an exhaustive list of possible URL patterns and styles, be sure to read <a href="https://jekyllrb.com/docs/permalinks/" rel="noopener">Jekyll’s permalink documentation</a>.</p>
<h2 id="global-permalinks"><a href="#global-permalinks" title="Permalink to Global permalinks">Global permalinks</a></h2><p>Changing the global output path for posts and pages is one of the first settings you’ll configure in <code>_config.yml</code>. The default pattern used by Jekyll is <code>permalink: date</code>, which is short for:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">permalink</span><span class="p">:</span><span class="w"> </span><span class="l">/:categories/:year/:month/:day/:title:output_ext</span></span></span></code></pre></div>
<p>This permalink pattern is made up of the <code>:categories</code>, <code>:year</code>, <code>:month</code>, <code>:day</code>, <code>:title</code>, and <code>:output_ext</code> placeholders. For an entire list of placeholders review <a href="https://jekyllrb.com/docs/permalinks/#placeholders" rel="noopener">Jekyll’s placeholder documentation</a>.</p>
<table>
<thead>
<tr>
<th>Placeholder</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>categories</code></td>
<td>Assigned categories for a post from its YAML front matter. Multiple categories e.g., <code>categories: [development, CSS]</code> would output as <code>/development/css</code>.</td>
</tr>
<tr>
<td><code>year</code></td>
<td>Year value (four digits e.g., <code>2021</code>) from the post’s filename.</td>
</tr>
<tr>
<td><code>month</code></td>
<td>Month value (two digits <code>01</code> through <code>12</code>) from the post’s filename.</td>
</tr>
<tr>
<td><code>day</code></td>
<td>Day value (two digits <code>01</code> through <code>31</code>) from the post’s filename.</td>
</tr>
<tr>
<td><code>title</code></td>
<td>String from the document’s filename.</td>
</tr>
<tr>
<td><code>output_ext</code></td>
<td>Extension of the output file, usually <code>.html</code>.</td>
</tr>
</tbody>
</table>
<p>In addition to the <code>date</code> permalink, Jekyll has the following patterns built-in for posts. Note: date based placeholders are ignored for documents like pages and collections.</p>
<table>
<thead>
<tr>
<th>Permalink alias</th>
<th>Permalink pattern</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>date</code></td>
<td><code>/:categories/:year/:month/:day/:title:output_ext</code></td>
</tr>
<tr>
<td><code>pretty</code></td>
<td><code>/:categories/:year/:month/:day/:title/</code></td>
</tr>
<tr>
<td><code>ordinal</code></td>
<td><code>/:categories/:year/:y_day/:title:output_ext</code></td>
</tr>
<tr>
<td><code>weekdate</code></td>
<td><code>/:categories/:year/W:week/:short_day/:title:output_ext</code></td>
</tr>
<tr>
<td><code>none</code></td>
<td><code>/:categories/:title:output_ext</code></td>
</tr>
</tbody>
</table>
<h3 id="url-defaults"><a href="#url-defaults" title="Permalink to URL defaults">URL defaults</a></h3><p>When building a fresh Jekyll site using the <code>jekyll new</code> command, the following URL defaults apply. The same is true if your <code>_config.yml</code> omits the <code>permalink</code> configuration as Jekyll falls back to the <code>/:categories/:year/:month/:day/:title:output_ext</code>.</p>
<h4 id="post-url-defaults"><a href="#post-url-defaults" title="Permalink to Post URL defaults">Post URL defaults</a></h4><p>Posts are a special collection in Jekyll and have filename requirements as described in <a href="https://jekyllrb.com/docs/posts/#creating-posts" rel="noopener">their posts documentation</a>. Because of this <code>YYYY-MM-DD-title.ext</code> convention, Jekyll is able to determine a post’s <code>title</code> and <code>date</code> without either being manually added via YAML front matter.</p>
<p>For example, let’s create a new Markdown post and name it <code>2021-06-30-new-post.md</code>, making sure it’s in the <code>_posts</code> directory.</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── _posts
│ └── 2021-06-30-new-post.md</code></pre></div>
<p>Jekyll knows the title of this post is <code>new post</code> and the date is <code>2021-06-30</code> for the reasons stated above.</p>
<p>When Jekyll builds the site it will use the default permalink pattern of <code>permalink: date</code> and output a HTML file into the <code>_site</code> directory with a root-relative URL of <code>/2021/06/30/new-post.html</code>.</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── _site
│ └── 2021
│ └── 06
│ └── 30
│ └── new-post.html</code></pre></div>
<p>Posts (unlike pages and other collections) have complete support for <code>categories</code>, which become part of their URL when using the default permalink pattern.</p>
<p>Using the example post created above and adding categories to it via YAML front matter:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">categories</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"development"</span><span class="p">,</span><span class="w"> </span><span class="s2">"CSS"</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span></span></span></code></pre></div>
<p>The resulting URL would become <code>/development/css/2021/06/30/new-post.html</code> with this file structure:</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── _site
| └── development
| └── css
│ └── 2021
│ └── 06
│ └── 30
│ └── new-post.html</code></pre></div>
<aside class="c-notice"><strong class="c-notice_heading">Note: Categories array is hierarchical</strong><div class="c-notice_content"><p>Order of categories is important as Jekyll applies a hierarchy to them. In the example above <code>development</code> precedes <code>CSS</code> implying a parent-child relationship to the terms, nesting <code>/css</code> inside of <code>/development</code>.</p></div>
</aside>
<aside class="c-notice"><strong class="c-notice_heading">Pro tip: Slugified categories (Jekyll v4.1+)</strong><div class="c-notice_content"><p>Use <code>slugified_categories</code> permalink placeholder to downcase and replace non-alphanumeric characters with hyphens.</p>
<p>For example <code>categories: [Ruby on Rails]</code> would convert to <code>ruby-on-rails</code> in the context of a permalink.</p></div>
</aside>
<h4 id="page-url-defaults"><a href="#page-url-defaults" title="Permalink to Page URL defaults">Page URL defaults</a></h4><p>A common Jekyll page convention is to add HTML or Markdown files in the root directory. In fact when running the <code>jekyll new</code> command it scaffolds out the following two pages in the root:</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── about.markdown
├── index.html</code></pre></div>
<p>When Jekyll builds the site it will use the default permalink pattern of <code>permalink: date</code> and output two HTML files into the <code>_site</code> directory with root-relative URLs of <code>/about.html</code> and <code>/index.html</code>.</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── _site
| └── about.html
| └── index.html</code></pre></div>
<aside class="c-notice"><strong class="c-notice_heading">Don’t let <code>permalink: date</code> fool you</strong><div class="c-notice_content"><p>Because pages are not date based like posts, even if you add a date via YAML front matter to <code>about.md</code> like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">date</span><span class="p">:</span><span class="w"> </span><span class="ld">2021-07-07</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span></span></span></code></pre></div>
<p>It will not output the file into a year/month/day directory structure. You’ll still end up with:</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── _site
| └── about.html</code></pre></div>
<p>Note: There are <a href="https://github.com/jekyll/jekyll/issues/7296#issue-366619778" rel="noopener">other inconsistencies</a> between posts, collections, and pages that have been documented in this GitHub issue.</p></div>
</aside>
<p>Pages can be organized into sub-directories and Jekyll will respect that structure and output matching folders and files. For example, if we create a page named <code>privacy-policy.md</code> and place it a directory named <code>terms</code>.</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── terms
│ └── privacy-policy.md</code></pre></div>
<p>When Jekyll builds the privacy policy page it will output a HTML file into the <code>_site</code> directory with a root-relative URL of <code>/terms/privacy-policy.html</code>.</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── _site
│ └── terms
│ └── privacy-policy.html</code></pre></div>
<aside class="c-notice"><strong class="c-notice_heading">Note: Page categories and permalinks</strong><div class="c-notice_content"><p>Pages ignore post specific placeholder parameters like <code>date</code> and <code>categories</code>. This is because pages are not date based nor are they included in the <code>site.categories</code> object. Jekyll will omit these values when constructing URLs for pages.</p>
<p>For example, if we add <code>categories: [legal]</code> to the <code>privacy-policy.md</code> page above, it has no affect on the permalink of the output file.</p></div>
</aside>
<h4 id="collection-url-defaults"><a href="#collection-url-defaults" title="Permalink to Collection URL defaults">Collection URL defaults</a></h4><p>Collection documents have a default permalink style of <code>permalink: /:collection/:path</code> when <code>output: true</code> is enabled. These files output in a similar way to pages. Meaning they ignore some permalink placeholders like categories.</p>
<p>For example, if we create a collection named recipes with the following minimal configuration:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">collections</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">recipes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">output</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span></span></span></code></pre></div>
<p>And then create two documents — <code>apple-pie.md</code> and <code>minestrone.md</code>, and place them in the <code>_recipes</code> directory.</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── _recipes
│ ├── apple-pie.md
│ └── minestrone.md</code></pre></div>
<p>When Jekyll builds the site it will use the default permalink pattern of <code>permalink: /:collection/:path</code> and output two HTML files into the <code>_site</code> directory with root-relative URLs of <code>/recipes/apple-pie.html</code> and <code>/recipes/minestrone.html</code>.</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── _site
│ └── recipes
│ ├── apple-pie.html
│ └── minestrone.html</code></pre></div>
<p>Collection documents can be organized into sub-directories (just like pages) and Jekyll will respect that structure and output matching folders and files.</p>
<p>For example, if we group the apple pie and minestrone recipes into deserts and soups directories inside of <code>_recipes</code>:</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── _recipes
│ ├── deserts
│ │ └── apple-pie.md
│ └── soups
│ └── minestrone.md</code></pre></div>
<p>Now when Jekyll builds the site these two recipes will output into the following structure:</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── _site
│ └── recipes
│ ├── deserts
│ │ └── apple-pie.html
│ └── soups
│ └── minestrone.html</code></pre></div>
<h4 id="static-file-url-defaults"><a href="#static-file-url-defaults" title="Permalink to Static file URL defaults">Static file URL defaults</a></h4><p>Files that do not contain any YAML front matter (aka static files), Jekyll outputs in the same structure it sourced from. This applies to images, videos, PDFs, and other assets that aren’t processed via Jekyll’s <a href="https://sass-lang.com/" rel="noopener">Sass</a> and <a href="https://coffeescript.org/" rel="noopener">CoffeeScript</a> pipelines.</p>
<p>Like pages and collection documents, static files can be organized in whichever way you choose and Jekyll will match naming and structure. For example, if we have a image named <code>logo.png</code> and place it in a <code>static/images</code> sub-directory.</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── static
│ └── images
│ └── logo.png</code></pre></div>
<p>When Jekyll builds the site it will and output the logo into the <code>_site</code> directory with a root-relative URL of <code>/static/images/logo.png</code>.</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── _site
│ └── static
│ └── images
│ └── logo.png</code></pre></div>
<h2 id="file-specific-permalinks"><a href="#file-specific-permalinks" title="Permalink to File specific permalinks">File specific permalinks</a></h2><p>Permalinks can be set in a file’s YAML front matter, overriding globally set patterns in the <code>_config.yml</code> file. Organizing files in a <code>_pages</code><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> folder and outputting them with a root-relative URL is a common use case.</p>
<p>For example, if we create a <code>resume.md</code> page and place it in a <code>_pages</code> subdirectoy.</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── _pages
│ └── resume.md</code></pre></div>
<p>When Jekyll builds the site it will output the resume page into <code>_pages/resume.html</code> which isn’t what we’re looking to achieve. We’d much rather it end up in <code>pages/resume.html</code> without the leading <code>_</code>.</p>
<p>You could of course rename the <code>_pages</code> directory to <code>pages</code> and get the desired result. But if you’re looking to follow the convention of naming Jekyll source directories with underscores, then setting a permalink override is the solution.</p>
<p>Inside of <code>_pages/resume.md</code> we can add the following path to force where it outputs to.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">permalink</span><span class="p">:</span><span class="w"> </span><span class="l">/resume.html</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span></span></span></code></pre></div>
<p>Now when Jekyll builds the site it will output the resume page into:</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── _site
│ └── resume.html</code></pre></div>
<p>We can even change the filename it outputs as:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">permalink</span><span class="p">:</span><span class="w"> </span><span class="l">/resume/index.html</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span></span></span></code></pre></div>
<aside class="c-notice"><strong class="c-notice_heading">Pro tip: Permalink placeholders in YAML front matter</strong><div class="c-notice_content"><p>Any of the placeholders you’d typically use to configure a global <code>permalink</code> pattern in <code>_config.yml</code> can also be configured in YAML front matter.</p>
<p>Note: aliases like <code>permalink: pretty</code> do not work in front matter, you’ll need to use the full pattern of <code>/:categories/:year/:month/:day/:title/</code>.</p></div>
</aside>
<h3 id="duplicate-permalinks"><a href="#duplicate-permalinks" title="Permalink to Duplicate permalinks">Duplicate permalinks</a></h3><p>Different source files with the same <code>permalink</code> can cause all sorts of confusion when troubleshooting a Jekyll build. Maybe you made some changes to your home page’s content and can’t figure out why it’s not updating. Or maybe you see nothing at all and the page is blank.</p>
<p>This is quite common when forking or copying other Jekyll repositories and ending up with both <code>index.html</code> and <code>index.md</code> files in your project. Since they share a filename, they’ll output to the same location and conflict 💥.</p>
<p>This could also happen if you had a file like <code>_pages/home.md</code> with <code>permalink: /</code> or <code>permalink: index.html</code> added to its YAML front matter.
Thankfully in newer versions, Jekyll’s CLI will warn you about such conflicts.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">Conflict: The following destination is shared by multiple files.
</span></span><span class="line"><span class="cl">The written file may end up with unexpected contents.
</span></span><span class="line"><span class="cl">C:/Users/michael/sites/jekyll-site/_site/index.html
</span></span><span class="line"><span class="cl"> - home.md
</span></span><span class="line"><span class="cl"> - index.md</span></span></code></pre></div>
<hr>
<h2 id="how-to-link"><a href="#how-to-link" title="Permalink to How to link">How to link</a></h2><p>How do you link posts, pages, documents, images, videos, and other resources together with Jekyll? Before answering that question, it’s useful to learn about the following URL types:</p>
<h3 id="url-types"><a href="#url-types" title="Permalink to URL types">URL types</a></h3><table>
<thead>
<tr>
<th></th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><strong>Document-relative</strong></td>
<td>URL contains path relative to the current page.</td>
</tr>
<tr>
<td>2</td>
<td><strong>Root-relative</strong></td>
<td>URL contains path relative to the site’s <em>root directory</em> — starts with a forward slash <code>/</code>.</td>
</tr>
<tr>
<td>3</td>
<td><strong>Absolute</strong></td>
<td>Full URL including protocol, domain, port, and or path.</td>
</tr>
</tbody>
</table>
<h4 id="document-relative-urls"><a href="#document-relative-urls" title="Permalink to Document-relative URLs">Document-relative URLs</a></h4><p>A relative URL that does not start with <code>/</code>, is <strong>document-relative</strong> and will instruct the browser to look for the document or file in the context of the current page.</p>
<p>Meaning if the browser has the following page open: <code>https://mademistakes.com/articles/index.html</code>, and there is a document-relative link pointing to <code>article-two/index.html</code> it would browse to the <code>article-two</code> sub-directory inside of the <code>articles</code> directory. Then look for a file named <code>index.html</code>.</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── https://mademistakes.com/articles
│ └── index.html (current document)
│ └── articles-two
│ └── index.html</code></pre></div>
<h5 id="when-do-you-use-document-relative-urls"><a href="#when-do-you-use-document-relative-urls" title="Permalink to When do you use document-relative URLs?">When do you use document-relative URLs?</a></h5><p>In my experience document-relative URLs are not all that common when working with Jekyll unless you have a flat directory structure. The one use case where I think they are helpful is if you want to organize supporting assets with a post in the same directory.</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── _posts
│ └── 2021-07-12
│ ├── 2021-07-12-weeknotes-8.md
│ ├── bbq.jpg
│ └── fireworks.jpg</code></pre></div>
<p>Unfortunately Jekyll doesn’t make this easy out of the box as it doesn’t know what to do with the non-Markdown files above.</p>
<p><code>2021-07-12-weeknotes-8.md</code> would be read and output as expected to <code>_site/2021/07/12/weeknotes-8.html</code>, but the static assets <code>bbq.jpg</code> and <code>fireworks.jpg</code> would not.</p>
<p>With the help of Nicolas Hoizey’s <a href="https://nhoizey.github.io/jekyll-postfiles/" rel="noopener"><strong>jekyll-postfiles</strong></a> plugin this can be fixed. Once installed, static assets will follow the same <code>permalink</code> pattern as posts and output alongside them like this:</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── _site
│ └── 2021
│ └── 07
│ └── 12
│ ├── weeknotes-8.html
│ ├── impossible-burgers.jpg
│ └── fireworks.jpg</code></pre></div>
<p>Now we can add document-relative links to images (or other assets) in Markdown and HTML files without them 404ing.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">![<span class="nt">Impossible burgers on the grill</span>](<span class="na">impossible-burgers.jpg</span>)</span></span></code></pre></div>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p"><</span><span class="nt">img</span> <span class="na">src</span><span class="o">=</span><span class="s">"fireworks.jpg"</span> <span class="na">alt</span><span class="o">=</span><span class="s">"Fireworks exploding in the sky"</span><span class="p">></span></span></span></code></pre></div>
<h4 id="root-relative-urls"><a href="#root-relative-urls" title="Permalink to Root-relative URLs">Root-relative URLs</a></h4><p>As the name implies, root-relative URLs make the path relative to the root directory of the site, denoted by a forward slash <code>/</code>.</p>
<p>Meaning if the browser has the following page open: <code>https://mademistakes.com/about/index.html</code>, and there is a root-relative link pointing to <code>/legal/privacy-policy/index.html</code> it would browse to the <code>legal</code> directory from the root directory. Then down into the <code>privacy-policy</code> sub-directory, where it would open a file named <code>index.html</code>.</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── https://mademistakes.com
│ ├── about
│ │ └── index.html (current document)
│ └── legal
│ └── privacy-policy
| └── index.html</code></pre></div>
<p>The forward slash <code>/</code> is import as it starts from the root of the server. If the <code>/</code> was omitted and the link was <code>legal/privacy-policy.html</code>, the browser would try to open <code>https://mademistakes.com/about/legal/privacy-policy/index.html</code> instead. Which would lead to a 404 error page as this file does not exist on the server.</p>
<h5 id="when-do-you-use-root-relative-urls"><a href="#when-do-you-use-root-relative-urls" title="Permalink to When do you use root-relative URLs?">When do you use root-relative URLs?</a></h5><p>Root-relative are most often used to link internal pages (e.g., related posts, category and tag archives, an about page, etc.) from the same site together. In my experience they are helpful when building sites with a deep directory structure.</p>
<p>It’s much easier to avoid broken links and 404 pages this way as you don’t have to worry page context when crafting URLs.</p>
<p>Some examples of root-relative URLs are:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">[<span class="nt">root-relative file</span>](<span class="na">/root-relative-file.html</span>)</span></span></code></pre></div>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="c"><!-- anchor link --></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"/root-relative-file.html"</span><span class="p">></span>root-relative file<span class="p"></</span><span class="nt">a</span><span class="p">></span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"><!-- sitemap --></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">link</span> <span class="na">href</span><span class="o">=</span><span class="s">"/sitemap.xml"</span> <span class="na">rel</span><span class="o">=</span><span class="s">"sitemap"</span> <span class="na">type</span><span class="o">=</span><span class="s">"application/xml"</span><span class="p">></span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"><!-- JavaScript file --></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">"/app.js"</span><span class="p">></</span><span class="nt">script</span><span class="p">></span></span></span></code></pre></div>
<h4 id="absolute-urls"><a href="#absolute-urls" title="Permalink to Absolute URLs">Absolute URLs</a></h4><p>Unlike relative URLs, absolutes or full URLs have all of the necessary elements (protocol, domain, port, and path) needed to resolve a URL.</p>
<h5 id="when-do-you-use-absolute-urls"><a href="#when-do-you-use-absolute-urls" title="Permalink to When do you use absolute URLs?">When do you use absolute URLs?</a></h5><p>Absolute URLs are used when linking to pages and files that live outside of the current site.</p>
<p>For example, the about page on this site links out to Twitter with the following absolute URL <code>https://twitter.com/mmistakes</code> which contains the protocol <code>https://</code>, domain <code>twitter.com</code> and path of <code>/mmistakes</code>.</p>
<p>Some examples of absolute URLs are:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">[<span class="nt">file on another domain</span>](<span class="na">https://not-mademistakes.com/absolute-file.html</span>)</span></span></code></pre></div>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="c"><!-- external anchor link --></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"https://not.mademistakes.com/absolute-file.html"</span><span class="p">></span>absolute file<span class="p"></</span><span class="nt">a</span><span class="p">></span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"><!-- externally hosted CSS file --></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">link</span> <span class="na">href</span><span class="o">=</span><span class="s">"https://not.mademistakes.com/assets/styles.min.css"</span> <span class="na">rel</span><span class="o">=</span><span class="s">"stylesheet"</span><span class="p">></span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"><!-- externally hosted JavaScript file --></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">"https://not.mademistakes.com/assets/app.js"</span><span class="p">></</span><span class="nt">script</span><span class="p">></span></span></span></code></pre></div>
<h3 id="about-siteurl-and-sitebaseurl"><a href="#about-siteurl-and-sitebaseurl" title="Permalink to About site.url and site.baseurl">About <code>site.url</code> and <code>site.baseurl</code></a></h3><p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/jekyll-site-url-baseurl-illustration_hub6f04c58f3ac2e3501a3905ce656cb2c_192632_400x0_resize_q75_box.jpg 400w, /images/jekyll-site-url-baseurl-illustration_hub6f04c58f3ac2e3501a3905ce656cb2c_192632_800x0_resize_q75_box.jpg 800w, /images/jekyll-site-url-baseurl-illustration.jpg 2660w" src="/images/jekyll-site-url-baseurl-illustration_hub6f04c58f3ac2e3501a3905ce656cb2c_192632_800x0_resize_q75_box.jpg"
alt="Illustration of Jekyll’s site.url and site.baseurl variables" loading="lazy" decoding="async" width="2660" height="1496" style="background: #292824"/></p>
<p>These two Jekyll <code>_config.yml</code> variables are notorious for breaking links, stylesheets, JavaScript and more. Knowing how they function is important for building working relative and absolute URLs in Jekyll theme files like layouts and includes.</p>
<aside class="c-notice"><strong class="c-notice_heading">Level-up opportunity</strong><div class="c-notice_content"><p>In this <a href="/mastering-jekyll/site-url-baseurl/" title="About site.url and site.baseurl"><strong>Mastering Jekyll</strong> tutorial</a> learn what Jekyll’s site <code>url</code> and <code>baseurl</code> variables are and how to properly set them — avoiding broken links and 404 errors.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/site-url-baseurl-permalink-example_huca3eeba290fb825f741c9a9a4c48b942_259520_400x0_resize_q75_box.jpg 400w, /images/site-url-baseurl-permalink-example_huca3eeba290fb825f741c9a9a4c48b942_259520_800x0_resize_q75_box.jpg 800w, /images/site-url-baseurl-permalink-example.jpg 2702w" src="/images/site-url-baseurl-permalink-example_huca3eeba290fb825f741c9a9a4c48b942_259520_800x0_resize_q75_box.jpg"
alt="illustration describing site url and baseurl permalink structure" loading="lazy" decoding="async" width="2702" height="1206" style="background: #28252c"/></p>
<p><strong>TL/DR:</strong> <code>{{ site.url }}</code> is the protocol and domain your site lives at e.g., <code>https://mmistakes.github.io</code>. While <code>{{ site.baseurl }}</code> is something you almost never use unless you’re building your site in a non-root directory e.g., <code>/blog</code> or hosting a project site on GitHub Pages.</p></div>
</aside>
<h3 id="linking-posts"><a href="#linking-posts" title="Permalink to Linking posts">Linking posts</a></h3><p>There are multiple ways to link to other Jekyll posts:</p>
<ol>
<li>Reference the post’s full root-relative URL e.g., <code>/2021-01-01-how-to-make-pizza.html</code>.</li>
<li>Use Jekyll’s <a href="https://jekyllrb.com/docs/liquid/tags/#linking-to-posts" rel="noopener"><code>{% post_url %}</code> tag</a>.</li>
<li>Use Jekyll’s <a href="https://jekyllrb.com/docs/liquid/tags/#link" rel="noopener"><code>{% link %}</code> tag</a>.</li>
</ol>
<p>The best way I’ve found is to use Jekyll’s <code>{% post_url %}</code> tag as it generates the correct URL even if the <code>permalink</code> style changes — <code>baseurl</code> folder and all.</p>
<h4 id="-post_url--and--link--tags"><a href="#-post_url--and--link--tags" title="Permalink to {% post_url %} and {% link %} tags"><code>{% post_url %}</code> and <code>{% link %}</code> tags</a></h4><p>Jekyll comes with a set of Liquid tags that makes linking easier for content authors and editors. If following the Jekyll convention of organizing posts in the <code>_posts</code> folder…</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── _posts
│ ├── 2021-01-01-how-to-make-pizza.md
│ └── 2021-05-20-birthday-bash.md</code></pre></div>
<p>You’d link to post <code>2021-01-01-how-to-make-pizza</code> by referencing its filename, omitting the extension at the end, e.g., <code>.md</code>.</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-liquid" data-lang="liquid">{% post_url 2021-01-01-how-to-make-pizza %}</code></pre></div>
<p>Jekyll will then output the following root-relative URL (if using the default permalink pattern of <code>permalink: date</code>.)</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">/2021/01/01/how-to-make-pizza.html</code></pre></div>
<hr>
<p>Jekyll also has the <code>{% link %}</code> tag, which can be used to link to a post, page, collection document, or file. The big difference between <code>link</code> and <code>post_url</code> is that the link tag requires the file’s full path e.g., directory and extension.</p>
<p>Using the same “How to make pizza” post from above, here’s how to link to it using Jekyll’s <code>link</code> tag:</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-liquid" data-lang="liquid">{% link _posts/2021-01-01-how-to-make-pizza.md %}</code></pre></div>
<p>Jekyll then outputs the same root-relative URL as before, <code>/2021/01/01/how-to-make-pizza.html</code>.</p>
<p>For links created using either <code>{% post_url %}</code> or <code>{% link %}</code>, if the <code>baseurl</code> config has been set because the site is hosted on GitHub Pages, for example something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># _config.yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">basewebsite</span><span class="p">:</span><span class="w"> </span><span class="l">/project-name</span></span></span></code></pre></div>
<p>Jekyll will output this root-relative URL instead: <code>/project-name/2021/01/01/post-to-link-to.html</code>.</p>
<aside class="c-notice"><strong class="c-notice_heading">⚠ <code>post_url</code> and <code>link</code> with <code>baseurl</code></strong><div class="c-notice_content"><p>Jekyll sites build with version 3.0 and configured with a <code>baseurl</code>, need to prepend <code>post_url</code> or <code>link</code> tags with <code>site.baseurl</code> like this:</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-liquid" data-lang="liquid">{{ site.baseurl }}{% post_url 2021-01-01-how-to-make-pizza %}
{{ site.baseurl }}{% link _posts/2021-01-01-how-to-make-pizza.md %}</code></pre></div>
<p>Check these <a href="/mastering-jekyll/site-url-baseurl/#examples">baseurl URL examples</a> if you’re still unsure on how to set them up.</p></div>
</aside>
<aside class="c-notice"><strong class="c-notice_heading">Pro tip: Link validation</strong><div class="c-notice_content"><p>Using the <code>{% post_url %}</code> tag has the added benefit of letting you know if the post linked to is valid. If a post was renamed or removed this would help identify a potential 404 error before being pushed into production.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">Could not find post <span class="s2">"2099-01-01-welcome-to-the-future.markdown"</span> in tag <span class="s1">'post_url'</span>. <span class="o">(</span>Jekyll::Errors::PostURLError<span class="o">)</span>
</span></span><span class="line"><span class="cl">Make sure the post exists and the name is correct.</span></span></code></pre></div></div>
</aside>
<h4 id="post-link-examples"><a href="#post-link-examples" title="Permalink to Post link examples">Post link examples</a></h4><p>Creating internal links to other posts is the quickest when <a href="https://daringfireball.net/projects/markdown/syntax#link" title="inline link syntax" rel="noopener">written in Markdown</a> as it doesn’t require as much markup. If you know a post’s full path you can reference that entire URL or use Jekyll’s <code>post_url</code> or <code>link</code> tags that reference the source file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">[<span class="nt">How to make pizza</span>](<span class="na">/2021/01/01/how-to-make-pizza.html</span>)
</span></span><span class="line"><span class="cl">[<span class="nt">How to make pizza</span>](<span class="na">{% post_url 2021-01-01-how-to-make-pizza %}</span>)
</span></span><span class="line"><span class="cl">[<span class="nt">How to make pizza</span>](<span class="na">{% link _posts/2021-01-01-how-to-make-pizza.md %}</span>)</span></span></code></pre></div>
<p>Or if you prefer to create links in HTML instead, you can.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"/2021/01/01/how-to-make-pizza.html"</span><span class="p">></span>How to make pizza<span class="p"></</span><span class="nt">a</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"{% post_url 2021-01-01-how-to-make-pizza %}"</span><span class="p">></span>How to make pizza<span class="p"></</span><span class="nt">a</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"{% link _posts/2021-01-01-how-to-make-pizza.md %}"</span><span class="p">></span>How to make pizza<span class="p"></</span><span class="nt">a</span><span class="p">></span></span></span></code></pre></div>
<p>Either way you create the link, Jekyll outputs the same HTML:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"/2021/01/01/how-to-make-pizza.html"</span><span class="p">></span>How to make pizza<span class="p"></</span><span class="nt">a</span><span class="p">></span></span></span></code></pre></div>
<aside class="c-notice"><strong class="c-notice_heading"><code>post_url</code> and permalinks</strong><div class="c-notice_content"><p>The <code>post_url</code> tag respects file specific permalinks set in YAML front matter. Meaning if you have a Markdown link like <code>[How to make pizza]({% post_url 2021-01-01-how-to-make-pizza %})</code> and this front matter:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">permalink</span><span class="p">:</span><span class="w"> </span><span class="l">how-to-make-pizza.html</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span></span></span></code></pre></div>
<p>Jekyll will respect the <code>permalink</code> override set in front matter and output the following link:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"/how-to-make-pizza.html"</span><span class="p">></span>How to make pizza<span class="p"></</span><span class="nt">a</span><span class="p">></span></span></span></code></pre></div></div>
</aside>
<h3 id="linking-pages"><a href="#linking-pages" title="Permalink to Linking pages">Linking pages</a></h3><p>Like posts, pages can be linked to in multiple ways:</p>
<ol>
<li>Reference the page’s full root-relative URL e.g., <code>/about.html</code>.</li>
<li>Use Jekyll’s <a href="https://jekyllrb.com/docs/liquid/tags/#link" rel="noopener"><code>{% link %}</code> tag</a>.</li>
</ol>
<p>My preferred method is to use Jekyll’s <code>{% link %}</code> tag as it has permalink “smarts” built into it and will warn you if trying to link to an invalid path.</p>
<p>Let’s say you created a fresh Jekyll site using the <code>jekyll new</code> command. After running that, you’d find an <code>about.markdown</code> file in the root of the site:</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── _posts
├── _config.yml
├── about.markdown
├── index.html</code></pre></div>
<p>To link to that page you could create the link yourself if you know that the output path will be <code>/articles.html</code>. But what if you’re new to Jekyll and not sure <a href="#global-permalinks">how permalinks work</a>?</p>
<h4 id="page-link-examples"><a href="#page-link-examples" title="Permalink to Page link examples">Page link examples</a></h4><p>Jekyll’s <code>link</code> tag solves this problem. All you need to know is the path (i.e., the source file), and then it can be linked like this:</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-liquid" data-lang="liquid">{% link about.markdown %}</code></pre></div>
<p>With files that are nested in sub-directories, you include the full path. For example, if we have a privacy policy page inside of a <code>/terms</code> directory:</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── terms
│ └── privacy-policy.md</code></pre></div>
<p>It can be linked to like this (with or without the leading forward slash <code>/</code>) in Markdown or HTML:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">[<span class="nt">Privacy policy</span>](<span class="na">{% link terms/privacy-policy.md %}</span>)</span></span></code></pre></div>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"{% link terms/privacy-policy.md %}"</span><span class="p">></span>Privacy policy<span class="p"></</span><span class="nt">a</span><span class="p">></span></span></span></code></pre></div>
<p>Jekyll will output the same HTML for both:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"/terms/privacy-policy.html"</span><span class="p">></span>Privacy policy<span class="p"></</span><span class="nt">a</span><span class="p">></span></span></span></code></pre></div>
<p>Like the <code>post_url</code> tag, if you link to a file that doesn’t exist or use the wrong path — Jekyll will warn you:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">Liquid Exception: Could not find document <span class="s1">'terms/privacy-policy.md'</span> in tag <span class="s1">'link'</span>. Make sure the document exists and the path is correct. in index.markdown</span></span></code></pre></div>
<h3 id="jekyll-url-filters"><a href="#jekyll-url-filters" title="Permalink to Jekyll URL filters">Jekyll URL filters</a></h3><p>For theme developers and anyone looking to build or modify layouts, Jekyll has a set of Liquid filters that make working with URLs easier.
If you’re new to <a href="https://shopify.github.io/liquid/" rel="noopener"><strong>Liquid</strong></a> (the template language used by Jekyll), <a href="https://jekyllrb.com/docs/liquid/filters/#standard-liquid-filters" title="standard Liquid filters" rel="noopener">filters</a> are used to change the output of a string, object, or variable.</p>
<p>You will find them inside of double curly braces <code>{{ }}</code> and after the pipe character <code>|</code>. For example this <code>page.lang</code> variable that has a filter applied to it to assign a <a href="https://shopify.github.io/liquid/filters/default/" rel="noopener">default value</a> of <code>"en"</code>.</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-liquid" data-lang="liquid">{{ page.lang | default: "en" }}</code></pre></div>
<table>
<thead>
<tr>
<th>-</th>
<th>Filter</th>
<th>Description</th>
<th>Example output</th>
</tr>
</thead>
<tbody>
<tr>
<td>Relative URL</td>
<td><code>relative_url</code></td>
<td>Prepends <code>baseurl</code> configuration value to the input creating a relative URL. Recommended for sites placed in a sub-directory e.g., projects hosted on GitHub Pages.</td>
<td><code>/my-project/assets/styles.css</code></td>
</tr>
<tr>
<td>Absolute URL</td>
<td><code>absolute_url</code></td>
<td>Prepends <code>url</code> and <code>baseurl</code> configurations values to the input creating an absolute URL.</td>
<td><code>https://mmistakes.github.io/my-project/assets/styles.css</code></td>
</tr>
</tbody>
</table>
<p>The <code>relative_url</code> and <code>absolute_url</code> filters were created to ease the pain of prepending <code>url</code> and <code>baseurl</code> to links. Instead of needing to write <code>{{ site.baseurl }}assets/css/styles.css</code>. You could write the following:</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-liquid" data-lang="liquid">{{ 'assets/css/styes.css' | relative_url }}</code></pre></div>
<p>Or if you needed to output the full URL of a page and were using something like: <code>{{ site.url }}{{ site.baseurl }}{{ page.url }}</code> or <code>{{ page.url | prepend: site.baseurl | prepend: site.url }}</code> in your layouts.</p>
<p>Both could be shortened by using Jekyll’s <code>absolute_url</code> filter:</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-liquid" data-lang="liquid">{{ page.url | absolute_url }}</code></pre></div>
<p>Since filters are generally used in Jekyll layouts and includes, it’s unlikely content authors will have much use for them. In that case, the <a href="/#-post_url--and--link--tags"><code>{% post_url %}</code> and <code>{% link %}</code> tags</a> will be more useful when creating internal links.</p>
<aside class="c-notice"><strong class="c-notice_heading">URL filter examples</strong><div class="c-notice_content"><p>Review these <a href="/mastering-jekyll/site-url-baseurl/#root-relative-url-links" title="root-relative URL links">relative</a> and <a href="/mastering-jekyll/site-url-baseurl/#absolute-url-links" title="absolute URL links">absolute</a> URL examples to learn more about when and when not to use each.</p></div>
</aside>
<h2 id="jekyll-relative-links-plugin"><a href="#jekyll-relative-links-plugin" title="Permalink to Jekyll relative links plugin">Jekyll relative links plugin</a></h2><p>Another option for dealing with relative links may be to install the <a href="https://github.com/benbalter/jekyll-relative-links" rel="noopener"><strong>jekyll-relative-links</strong></a> plugin.</p>
<p>Perhaps you have repository of Markdown files on GitHub with links like <code>[foo](bar.md)</code>. On GitHub.com these links are valid and work. But on the documentation site you built using these same Markdown source files — the links are broken.</p>
<p>Using the <strong>jekyll-relative-links</strong> plugin can solve this by converting relative links to Markdown files into links to the appropriate Jekyll generated <code>.html</code> files.</p>
<p>For example, say we have two Markdown files in the root directory:</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">├── install.md
├── upgrade.md</code></pre></div>
<p>And in <code>install.md</code> there is the following Markdown that links to <code>upgrade.md</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">To [<span class="nt">upgrade the package</span>](<span class="na">upgrade.md</span>), run...</span></span></code></pre></div>
<p>On GitHub.com the link will work as expected since <code>upgrade.md</code> exists. But for a Jekyll built site, the browser expects the URL to be something like <code>/upgrade.html</code> (or whatever permalink style is configured).</p>
<table>
<thead>
<tr>
<th>HTML output</th>
<th style="text-align:center">-</th>
</tr>
</thead>
<tbody>
<tr>
<td><code><a href="upgrade.md">upgrade the package</a></code></td>
<td style="text-align:center">❌</td>
</tr>
</tbody>
</table>
<p>With <strong>jekyll-relative-links</strong> installed the same Markdown link of <code>[upgrade the package](upgrade.md)</code> will output as a valid relative link to the rendered file — custom permalinks and all!</p>
<table>
<thead>
<tr>
<th>HTML output</th>
<th style="text-align:center">-</th>
</tr>
</thead>
<tbody>
<tr>
<td><code><a href="/upgrade.html">upgrade the package</a></code></td>
<td style="text-align:center">✅</td>
</tr>
</tbody>
</table>
<aside class="c-notice"><strong class="c-notice_heading">⚠ Compatibility with posts</strong><div class="c-notice_content"><p>By default <strong>jekyll-relative-links</strong> is enabled for <em>pages only</em>. To allow relative links in posts and other collections, set <code>collections</code> to <code>true</code> in the <code>_config.yml</code> file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">relative_links</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">collections</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span></span></span></code></pre></div></div>
</aside>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://en.wikipedia.org/wiki/Permalink" rel="noopener">Wikipedia describes a permalink</a> or permanent link as a URL that is intended to remain unchanged for many years into the future, yielding a hyperlink that is less susceptible to link rot. <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
<li id="fn:2">
<p>Unlike the <code>_posts</code> directory, <code>_pages</code> is not initially recognized by Jekyll. By design, directories (or folders) starting with an <code>_</code> are ignored except for <code>_includes</code>, <code>_layouts</code>, <code>_posts</code>, and <code>_sass</code>. To force Jekyll to read from these “ignored” directories, add them to the <a href="https://jekyllrb.com/docs/configuration/options/" rel="noopener"><code>include</code> array</a> within <code>_config.yml</code>. <a href="#fnref:2" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
</ol>
</div><p><a href="https://mademistakes.com/mastering-jekyll/how-to-link/?utm_source=atom_feed">URLs and links in Jekyll</a> was originally published on Made Mistakes.</p>Jekyll’s site.url and baseurlhttps://mademistakes.com/mastering-jekyll/site-url-baseurl/2021-07-11T00:00:00-04:002021-07-19T12:35:43-04:00<p><img src="/images/jekyll-site-url-baseurl-illustration.jpg" alt="" loading="lazy" decoding="async" width="2660" height="1496"></p><p><strong>Jekyll’s</strong> site <code>url</code> and <code>baseurl</code> variables cause a lot of confusion for users. I see it all the time in the <a href="https://talk.jekyllrb.com/" rel="noopener"><strong>Jekyll Talk</strong> forum</a>, <a href="https://stackoverflow.com/questions/tagged/jekyll" rel="noopener"><strong>Stackoverflow</strong></a>, or as bug reports in <a href="/work/jekyll-themes/">my themes</a>.</p>
<blockquote>
<p>My Jekyll site works locally but when I push it up to <em>GitHub Pages</em> it is broken with no styling and looks like this. Help!"</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/jekyll-minima-stylesheet-404_hu472ebced326479fa05f4bae9c13b6694_60961_400x0_resize_q75_box.jpg 400w, /images/jekyll-minima-stylesheet-404_hu472ebced326479fa05f4bae9c13b6694_60961_800x0_resize_q75_box.jpg 800w, /images/jekyll-minima-stylesheet-404.jpg 932w" src="/images/jekyll-minima-stylesheet-404_hu472ebced326479fa05f4bae9c13b6694_60961_800x0_resize_q75_box.jpg"
alt="Jekyll Minima site with missing CSS" loading="lazy" decoding="async" width="932" height="500" style="background: #fefefe"/></p>
</blockquote>
<p>Improper use of Jekyll’s <code>baseurl</code> can break links to CSS, posts, and more as seen above.</p>
<h2 id="what-are-url-and-baseurl"><a href="#what-are-url-and-baseurl" title="Permalink to What are url and baseurl?">What are <code>url</code> and <code>baseurl</code>?</a></h2><p>So what exactly are the <code>url</code> and <code>baseurl</code> variables? To start, both are site-wide variables set in the <code>_config.yml</code> file and affect how Jekyll builds URLs. I like to describe them like this:</p>
<table>
<thead>
<tr>
<th>Site variable</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>url</code></td>
<td>A site’s full URL including protocol, domain, and port (if applicable).</td>
</tr>
<tr>
<td><code>baseurl</code></td>
<td>Name of sub-directory the site is served from e.g., <code>/blog</code>.</td>
</tr>
</tbody>
</table>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/site-url-baseurl-permalink-example_huca3eeba290fb825f741c9a9a4c48b942_259520_400x0_resize_q75_box.jpg 400w, /images/site-url-baseurl-permalink-example_huca3eeba290fb825f741c9a9a4c48b942_259520_800x0_resize_q75_box.jpg 800w, /images/site-url-baseurl-permalink-example.jpg 2702w" src="/images/site-url-baseurl-permalink-example_huca3eeba290fb825f741c9a9a4c48b942_259520_800x0_resize_q75_box.jpg"
alt="illustration describing site url and baseurl permalink structure" loading="lazy" decoding="async" width="2702" height="1206" style="background: #28252c"/></p>
<h2 id="how-to-use-url-and-baseurl"><a href="#how-to-use-url-and-baseurl" title="Permalink to How to use url and baseurl">How to use <code>url</code> and <code>baseurl</code></a></h2><p>A couple of important facts to keep in mind when using both:</p>
<ol>
<li>
<p>Leave off trailing forward slashes when setting <code>url</code></p>
<table>
<thead>
<tr>
<th>✅ Do this</th>
<th>❌ Don’t do this</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>website: https://mademistakes.com</code></td>
<td><code>website: https://mademistakes.com/</code></td>
</tr>
</tbody>
</table>
</li>
<li>
<p><code>baseurl</code> is <em>not needed</em> for most sites and can be omitted.</p>
</li>
<li>
<p><code>baseurl</code> is only necessary when hosting your site in a sub-directory. <a href="https://docs.github.com/en/pages/getting-started-with-github-pages/about-github-pages#types-of-github-pages-sites" rel="noopener">Project sites</a> hosted on <a href="https://pages.github.com/" rel="noopener"><strong>GitHub Pages</strong></a> are the common use-case of this variable.</p>
</li>
</ol>
<aside class="c-notice"><strong class="c-notice_heading">Remember to properly set links</strong><div class="c-notice_content"><p>These URL variables are not magic and need to be applied to links in your layouts, includes, or themes. This can be done by prefixing all links with <code>{{ site.url }}{{ site.baseurl }}</code> or by apply Jekyll’s <a href="#absolute_url-filter"><code>absolute_url</code></a> or <a href="#relative_url-filter"><code>relative_url</code></a> filters to them.</p></div>
</aside>
<h2 id="jekyll-url-troubleshooting"><a href="#jekyll-url-troubleshooting" title="Permalink to Jekyll URL troubleshooting">Jekyll URL troubleshooting</a></h2><p>If we go back to the example above with the broken CSS link and inspect the HTML’s source. You will often find that the link to the CSS file is incorrect for any number of reasons:</p>
<ol>
<li>Double forward slashes e.g., <code>//minima/assets/style.css</code></li>
<li>Missing base URL due to not using <code>relative_url</code> filter, <code>absolute_url</code> filter, or <code>{{ site.baseurl }}</code> in theme files.</li>
</ol>
<figure>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/firefox-inspector-broken-stylesheet-ref_hu87a836d515e0dd84f1c021ea1c7ab44a_89300_400x0_resize_q75_box.jpg 400w, /images/firefox-inspector-broken-stylesheet-ref_hu87a836d515e0dd84f1c021ea1c7ab44a_89300_800x0_resize_q75_box.jpg 800w, /images/firefox-inspector-broken-stylesheet-ref.jpg 970w" src="/images/firefox-inspector-broken-stylesheet-ref_hu87a836d515e0dd84f1c021ea1c7ab44a_89300_800x0_resize_q75_box.jpg"
alt="Firefox web inspector showing HTML source and broken stylesheet reference" loading="lazy" decoding="async" width="970" height="412" style="background: #fafbfb"/></p>
<figcaption>When the path to the stylesheet is wrong the browser can’t load it. No stylesheet == plain looking website with no styles and looks <em>broken</em>.</figcaption>
</figure>
<h2 id="jekyll-development-and-siteurl"><a href="#jekyll-development-and-siteurl" title="Permalink to Jekyll development and site.url">Jekyll development and <code>site.url</code></a></h2><p>One other question I see asked over and over again from Jekyll users is:</p>
<blockquote>
<p>Why are my links broken? They all start with <code>http://localhost:4000</code> or <code>http://127.0.0.1:4000</code>.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/github-pages-404-file-not-found_hu7aafce072bdd600b8eaecaacc5295f1b_35999_400x0_resize_q75_box.jpg 400w, /images/github-pages-404-file-not-found_hu7aafce072bdd600b8eaecaacc5295f1b_35999_800x0_resize_q75_box.jpg 800w, /images/github-pages-404-file-not-found.jpg 919w" src="/images/github-pages-404-file-not-found_hu7aafce072bdd600b8eaecaacc5295f1b_35999_800x0_resize_q75_box.jpg"
alt="Screenshot of GitHub Pages default 404 file not found page" loading="lazy" decoding="async" width="919" height="493" style="background: #f0f0f0"/></p>
</blockquote>
<p>This happens because in older versions of Jekyll (v3.3 through 4.1) it would reset <code>site.url</code> to <code>localhost:4000</code> when <a href="https://jekyllrb.com/docs/configuration/environments/" rel="noopener"><strong><code>JEKYLL_ENV=development</code></strong></a> (the default environment value), overriding whatever is set in the <code>_config.yml</code>. In newer versions of Jekyll (<a href="https://jekyllrb.com/docs/history/#v4-2-0" rel="noopener">v4.2 and up</a>) this <a href="https://github.com/jekyll/jekyll/pull/7253" rel="noopener">reset of <code>site.url</code> no longer happens</a>.</p>
<p>In older versions of Jekyll, <code>site.url</code> is also reset to <code>http://localhost:4000</code> when spinning up a development server with the <code>jekyll serve</code> command.</p>
<h3 id="siteurl-values"><a href="#siteurl-values" title="Permalink to site.url values"><code>site.url</code> values</a></h3><table>
<thead>
<tr>
<th>Jekyll version</th>
<th>Environment</th>
<th>CLI command</th>
<th><code>site.url</code> value</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>4.2</strong> (✨latest)</td>
<td><code>development</code></td>
<td><code>serve</code></td>
<td>value set in <code>_config.yml</code></td>
</tr>
<tr>
<td>3.3</td>
<td><code>development</code></td>
<td><code>serve</code></td>
<td>reset to <code>http://localhost:4000</code></td>
</tr>
<tr>
<td><strong>4.2</strong> (✨latest)</td>
<td><code>development</code></td>
<td><code>build</code></td>
<td>value set in <code>_config.yml</code></td>
</tr>
<tr>
<td>3.3</td>
<td><code>development</code></td>
<td><code>build</code></td>
<td>value set in <code>_config.yml</code></td>
</tr>
<tr>
<td><strong>4.2</strong> (✨latest)</td>
<td><code>production</code></td>
<td><code>serve</code></td>
<td>value set in <code>_config.yml</code></td>
</tr>
<tr>
<td>3.3</td>
<td><code>production</code></td>
<td><code>serve</code></td>
<td>value set in <code>_config.yml</code></td>
</tr>
<tr>
<td><strong>4.2</strong> (✨latest)</td>
<td><code>production</code></td>
<td><code>build</code></td>
<td>value set in <code>_config.yml</code></td>
</tr>
<tr>
<td>3.3</td>
<td><code>production</code></td>
<td><code>build</code></td>
<td>value set in <code>_config.yml</code></td>
</tr>
</tbody>
</table>
<p>Under normal circumstances these two conditions won’t cause any headaches, especially when hosting on <a href="https://pages.github.com/" rel="noopener"><strong>GitHub Pages</strong></a> as the environment and <code>url</code> are set automatically.</p>
<p>The issue surfaces when building locally, using an older versions of Jekyll, and then forgetting to set <code>JEKYLL_ENV</code> to <code>production</code>. Or pushing up files from a <code>_site</code> directory after running <code>jekyll serve</code> instead of <code>jekyll build</code>.</p>
<aside class="c-notice"><strong class="c-notice_heading">⚠ GitHub Pages <a href="https://pages.github.com/versions/" rel="noopener">dependencies and versions</a></strong><div class="c-notice_content"><p>Further confusing the issue, <strong>GitHub Pages</strong> does not use the latest version of Jekyll to build sites. It is currently locked at 3.9.0, which means it doesn’t have parity with a lot of the new hotness found in version 4.</p></div>
</aside>
<h2 id="examples"><a href="#examples" title="Permalink to Examples">Examples</a></h2><p>Below you will find examples of how to write different types of links that require <code>site.url</code> or <code>site.baseurl</code> values to work properly. Links marked as ✅ have valid URLs while those marked as ❌ are broken and will trigger a 404 file not found error.</p>
<p>Each of the examples assume:</p>
<ol>
<li>
<p>Jekyll v4.2 or greater is installed.</p>
</li>
<li>
<p>The site is hosted on GitHub Pages in a sub-directory named <code>blog</code>.</p>
</li>
<li>
<p>Posts follow <a href="https://jekyllrb.com/docs/posts/" rel="noopener">standard naming conventions</a> of <code>YYYY-MM-DD-filename.md</code> inside of a <code>_posts</code> directory.</p>
</li>
<li>
<p>Image assets are in a <code>images</code> directory inside of the root.</p>
</li>
<li>
<p>Have the following <code>_config.yml</code> settings:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">website</span><span class="p">:</span><span class="w"> </span><span class="l">https://mmistakes.github.io</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">basewebsite</span><span class="p">:</span><span class="w"> </span><span class="l">/blog</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">permalink</span><span class="p">:</span><span class="w"> </span><span class="l">date</span></span></span></code></pre></div>
</li>
</ol>
<h3 id="markdown-links"><a href="#markdown-links" title="Permalink to Markdown links">Markdown links</a></h3><p>How to link to pages, posts, and images in Markdown.</p>
<h4 id="page-in-root-directory"><a href="#page-in-root-directory" title="Permalink to Page in root directory">Page in root directory</a></h4>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">[<span class="nt">about page</span>](<span class="na">/about.html</span>)</span></span></code></pre></div>
<table>
<thead>
<tr>
<th>HTML output</th>
<th style="text-align:center">-</th>
</tr>
</thead>
<tbody>
<tr>
<td><code><a href="/about.html">about page</a></code><br />URL is missing the base URL of <code>/blog</code> from the path.</td>
<td style="text-align:center">❌</td>
</tr>
</tbody>
</table>
<h4 id="page-in-sub-directory"><a href="#page-in-sub-directory" title="Permalink to Page in sub-directory">Page in sub-directory</a></h4>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">[<span class="nt">about page</span>](<span class="na">/blog/about.html</span>)
</span></span><span class="line"><span class="cl">[<span class="nt">about page</span>](<span class="na">{{ site.baseurl }}/about.html</span>)
</span></span><span class="line"><span class="cl">[<span class="nt">about page</span>](<span class="na">{{ 'about.html' | relative_url }}</span>)</span></span></code></pre></div>
<table>
<thead>
<tr>
<th>HTML output</th>
<th style="text-align:center">-</th>
</tr>
</thead>
<tbody>
<tr>
<td><code><a href="/blog/about.html">about page</a></code></td>
<td style="text-align:center">✅</td>
</tr>
</tbody>
</table>
<h4 id="page-in-date-sub-directories"><a href="#page-in-date-sub-directories" title="Permalink to Page in date sub-directories">Page in date sub-directories</a></h4>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">[<span class="nt">Welcome to Jekyll post</span>](<span class="na">/blog/2021/06/29/welcome-to-jekyll.html</span>)
</span></span><span class="line"><span class="cl">[<span class="nt">Welcome to Jekyll post</span>](<span class="na">{% post_url 2021-06-29-welcome-to-jekyll %}</span>)
</span></span><span class="line"><span class="cl">[<span class="nt">Welcome to Jekyll post</span>](<span class="na">{% link _posts/2021-06-29-welcome-to-jekyll.md %}</span>)</span></span></code></pre></div>
<table>
<thead>
<tr>
<th>HTML output</th>
<th style="text-align:center">-</th>
</tr>
</thead>
<tbody>
<tr>
<td><code><a href="/blog/2021/06/29/welcome-to-jekyll.html">Welcome to Jekyll post</a></code></td>
<td style="text-align:center">✅</td>
</tr>
</tbody>
</table>
<h4 id="image-in-root-directory"><a href="#image-in-root-directory" title="Permalink to Image in root directory">Image in root directory</a></h4>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">[<span class="nt">cheese pizza</span>](<span class="na">pizza.jpg</span>)</span></span></code></pre></div>
<table>
<thead>
<tr>
<th>HTML output</th>
<th style="text-align:center">-</th>
</tr>
</thead>
<tbody>
<tr>
<td><code><img src="pizza.jpg" alt="cheese pizza"></code><br />URL is missing the base URL of <code>/blog</code> and sub-directory <code>/images</code> from the path.</td>
<td style="text-align:center">❌</td>
</tr>
</tbody>
</table>
<h4 id="image-in-sub-directory"><a href="#image-in-sub-directory" title="Permalink to Image in sub-directory">Image in sub-directory</a></h4>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">[<span class="nt">cheese pizza</span>](<span class="na">images/pizza.jpg</span>)</span></span></code></pre></div>
<table>
<thead>
<tr>
<th>HTML output</th>
<th style="text-align:center">-</th>
</tr>
</thead>
<tbody>
<tr>
<td><code><img src="images/pizza.jpg" alt="cheese pizza"></code><br />URL is document-relative when it should be root-relative and is missing the base URL of <code>/blog</code> from the path.</td>
<td style="text-align:center">❌</td>
</tr>
</tbody>
</table>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">[<span class="nt">cheese pizza</span>](<span class="na">/images/pizza.jpg</span>)</span></span></code></pre></div>
<table>
<thead>
<tr>
<th>HTML output</th>
<th style="text-align:center">-</th>
</tr>
</thead>
<tbody>
<tr>
<td><code><img src="/images/pizza.jpg" alt="cheese pizza"></code><br />URL is missing the base URL of <code>/blog</code> from the path.</td>
<td style="text-align:center">❌</td>
</tr>
</tbody>
</table>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">[<span class="nt">cheese pizza</span>](<span class="na">/blog/images/pizza.jpg</span>)
</span></span><span class="line"><span class="cl">[<span class="nt">cheese pizza</span>](<span class="na">{{ '/images/pizza.jpg' | relative_url }}</span>)
</span></span><span class="line"><span class="cl">[<span class="nt">cheese pizza</span>](<span class="na">{{ site.baseurl }}/images/pizza.jpg</span>)</span></span></code></pre></div>
<table>
<thead>
<tr>
<th>HTML output</th>
<th style="text-align:center">-</th>
</tr>
</thead>
<tbody>
<tr>
<td><code><img src="/blog/images/pizza.jpg" alt="cheese pizza"></code></td>
<td style="text-align:center">✅</td>
</tr>
</tbody>
</table>
<h3 id="root-relative-url-links"><a href="#root-relative-url-links" title="Permalink to Root-relative URL links">Root-relative URL links</a></h3><p>How to link resources with root-relative URLs, like stylesheets, favicons, and JavaScript.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">"stylesheet"</span> <span class="na">href</span><span class="o">=</span><span class="s">"/assets/css/style.css"</span><span class="p">></span></span></span></code></pre></div>
<table>
<thead>
<tr>
<th>HTML output</th>
<th style="text-align:center">-</th>
</tr>
</thead>
<tbody>
<tr>
<td><code><link rel="stylesheet" href="/assets/css/style.css"></code><br />URL is missing the base URL of <code>/blog</code> from the path.</td>
<td style="text-align:center">❌</td>
</tr>
</tbody>
</table>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">"stylesheet"</span> <span class="na">href</span><span class="o">=</span><span class="s">"/blog/assets/css/style.css"</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">"stylesheet"</span> <span class="na">href</span><span class="o">=</span><span class="s">"{{ site.baseurl }}/assets/css/style.css"</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">"stylesheet"</span> <span class="na">href</span><span class="o">=</span><span class="s">"{{ '/assets/css/style.css' | relative_url }}"</span><span class="p">></span></span></span></code></pre></div>
<table>
<thead>
<tr>
<th>HTML output</th>
<th style="text-align:center">-</th>
</tr>
</thead>
<tbody>
<tr>
<td><code><link rel="stylesheet" href="/blog/assets/css/style.css"></code></td>
<td style="text-align:center">✅</td>
</tr>
</tbody>
</table>
<h3 id="absolute-url-links"><a href="#absolute-url-links" title="Permalink to Absolute URL links">Absolute URL links</a></h3><p>How to link to resources that require the full URL, like a canonical URL in the page’s <code><head></code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">"canonical"</span> <span class="na">href</span><span class="o">=</span><span class="s">"{{ page.url }}"</span><span class="p">></span></span></span></code></pre></div>
<table>
<thead>
<tr>
<th>HTML output</th>
<th style="text-align:center">-</th>
</tr>
</thead>
<tbody>
<tr>
<td><code><link rel="canonical" href="/2021/06/29/welcome-to-jekyll.html"></code><br />URL is missing the site URL and base URL of <code>/blog</code> from the path.</td>
<td style="text-align:center">❌</td>
</tr>
</tbody>
</table>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">"canonical"</span> <span class="na">href</span><span class="o">=</span><span class="s">"{{ page.url | absolute_url }}"</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">"canonical"</span> <span class="na">href</span><span class="o">=</span><span class="s">"{{ page.url | prepend: site.baseurl | prepend: site.url }}"</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">"canonical"</span> <span class="na">href</span><span class="o">=</span><span class="s">"{{ site.url }}{{ site.baseurl }}{{ page.url }}"</span><span class="p">></span></span></span></code></pre></div>
<table>
<thead>
<tr>
<th>HTML output</th>
<th style="text-align:center">-</th>
</tr>
</thead>
<tbody>
<tr>
<td><code><link rel="canonical" href="https://mmistakes.github.io/blog/2021/06/29/welcome-to-jekyll.html"></code></td>
<td style="text-align:center">✅</td>
</tr>
</tbody>
</table>
<p>As you can see from above examples, Jekyll is flexible and allows for multiple ways of creating links. Personally I tend to lean on the <code>relative_url</code> and <code>absolute_url</code> filters as they require less typing and have some “smarts” to them.</p>
<p>The best option is the one that works for you!</p><p><a href="https://mademistakes.com/mastering-jekyll/site-url-baseurl/?utm_source=atom_feed">Jekyll’s site.url and baseurl</a> was originally published on Made Mistakes.</p>Not so quick with the Gatsby site optimizationshttps://mademistakes.com/notes/gatsby-site-optimizations-2021/2021-06-22T16:14:38-04:002023-12-08T09:17:53-05:00<p>This is the post where I talk about neglecting the site and pledge to blog more… <em>wait, wasn’t I suppose to do this on January 1st?</em></p>
<p>Jokes aside, the last year and half I haven’t felt like writing. Between my “real” job, open source projects, COVID-19, buying and selling a house during a global pandemic, and moving the twins to a new school — where does someone find the time?</p>
<p>With life stabilized, I had time to dust the cobwebs off this site.</p>
<h2 id="build-process"><a href="#build-process" title="Permalink to Build process">Build process</a></h2><p>In the time that I <a href="/notes/twenty-nineteen/#web-development">ported over from Jekyll to Gatsby</a>, GitHub released Actions to build, test, and deploy code. Migrating my build process over to <a href="https://github.com/features/actions" rel="noopener">GitHub Actions</a> seemed obvious as I was hitting the limits of Travis CI’s free tier.</p>
<h3 id="github-actions-migration"><a href="#github-actions-migration" title="Permalink to GitHub Actions migration">GitHub Actions migration</a></h3><p>The process of updating involved creating a new YAML config file and adding environment variables. The cherry on top was leveraging an <a href="https://github.com/actions/cache" rel="noopener">action to cache</a> Gatsby’s <code>.cache</code> and <code>public</code> folders between builds.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Caching Gatsby</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">gatsby-cache-build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/cache@v2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd"> public
</span></span></span><span class="line"><span class="cl"><span class="sd"> .cache</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">${{ runner.os }}-gatsby-build-${{ github.run_id }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">restore-keys</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd"> ${{ runner.os }}-gatsby-build-</span><span class="w"> </span></span></span></code></pre></div>
<p>Building the site from a cold start and no cache takes about 35 minutes to chug through 1,426 pages and resize/optimize 13,194 images. Each cached builds after takes ~1 minute to finish — that’s what I call an improvement!</p>
<div class="c-browser-frame">
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/deploy-to-netlify-github-action_hu55bdff52c924c6a13e8f6b764c62bccc_70767_400x0_resize_q75_box.jpg 400w, /images/deploy-to-netlify-github-action_hu55bdff52c924c6a13e8f6b764c62bccc_70767_800x0_resize_q75_box.jpg 800w, /images/deploy-to-netlify-github-action.jpg 1330w" src="/images/deploy-to-netlify-github-action_hu55bdff52c924c6a13e8f6b764c62bccc_70767_800x0_resize_q75_box.jpg"
alt="Deploy to Netlify GitHub Action log." loading="lazy" decoding="async" width="1330" height="730" style="background: #242a2f"/></p>
</div>
<h3 id="upgrade-gatsby-to-3x"><a href="#upgrade-gatsby-to-3x" title="Permalink to Upgrade Gatsby to 3.x">Upgrade Gatsby to 3.x</a></h3><p><a href="https://www.gatsbyjs.com/blog/gatsby-v3/" rel="noopener">Gatsby v3 added all sorts of improvements</a> to the local development experience, which naturally I wanted to take part in. Unfortunately I had to wait for a few community plugins to be updated as the old Gatsby v2 versions broke my builds.</p>
<h4 id="plugin-gatsby-remark-source-namehttpsgithubcomelbomangatsby-remark-source-name"><a href="#plugin-gatsby-remark-source-namehttpsgithubcomelbomangatsby-remark-source-name" title="Permalink to Plugin: gatsby-remark-source-name">Plugin: <a href="https://github.com/elboman/gatsby-remark-source-name" rel="noopener">gatsby-remark-source-name</a></a></h4><p>Coming from Jekyll I’m used to organizing Markdown files into different collections like <code>posts</code>, <code>pages</code>, and <code>comments</code>. With Gatsby I place each in their own folder with a matching <code>name</code> in <code>gatsby-config.js</code> as shown below:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// excerpt from gatsby-config.js
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">plugins</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">resolve</span><span class="o">:</span> <span class="s1">'gatsby-source-filesystem'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">options</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">name</span><span class="o">:</span> <span class="s1">'posts'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">path</span><span class="o">:</span> <span class="sb">`</span><span class="si">${</span><span class="nx">__dirname</span><span class="si">}</span><span class="sb">/src/posts`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">resolve</span><span class="o">:</span> <span class="s1">'gatsby-source-filesystem'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">options</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">name</span><span class="o">:</span> <span class="s1">'pages'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">path</span><span class="o">:</span> <span class="sb">`</span><span class="si">${</span><span class="nx">__dirname</span><span class="si">}</span><span class="sb">/src/pages`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">resolve</span><span class="o">:</span> <span class="s1">'gatsby-source-filesystem'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">options</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">name</span><span class="o">:</span> <span class="s1">'comments'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">path</span><span class="o">:</span> <span class="sb">`</span><span class="si">${</span><span class="nx">__dirname</span><span class="si">}</span><span class="sb">/src/comments`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span></span></span></code></pre></div>
<p>The <strong>gatsby-remark-source-name</strong> plugin then adds these as a <code>sourceName</code> field to Remark nodes. Allowing for an easy way to filter on collection type in my GraphQL queries:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-graphql" data-lang="graphql"><span class="line"><span class="cl"><span class="c"># query for all "posts" Markdown files</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="py">return</span><span class="w"> </span><span class="py">graphql</span><span class="p">(</span><span class="err">`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="py">posts</span><span class="p">:</span><span class="w"> </span><span class="nc">allMarkdownRemark</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="py">filter</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nc">fields</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nc">sourceName</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nc">eq</span><span class="p">:</span><span class="w"> </span><span class="s">"posts"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nc">edges</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="py">node</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="py">html</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="py">frontmatter</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c"># Assumes you're using title in your frontmatter</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="py">title</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="err">`</span><span class="p">)</span></span></span></code></pre></div>
<p>This broke with Gatsby v3 and threw the following error:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">TypeError: Cannot destructure property <span class="s1">'createNodeField'</span> of <span class="s1">'boundActionCreators'</span> as it is undefined.</span></span></code></pre></div>
<p>Turns out a <a href="https://github.com/elboman/gatsby-remark-source-name/pull/1#issuecomment-835423884" title="add sourceName nodes to Markdown files" rel="noopener">plugin isn’t needed</a> and you can achieve the same result by adding the following to <code>gatsby-node.js</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">exports</span><span class="p">.</span><span class="nx">onCreateNode</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">node</span><span class="p">,</span> <span class="nx">getNode</span><span class="p">,</span> <span class="nx">actions</span> <span class="p">})</span> <span class="p">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="p">{</span> <span class="nx">createNodeField</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">actions</span>
</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">internal</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="s1">'MarkdownRemark'</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">fileNode</span> <span class="o">=</span> <span class="nx">getNode</span><span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">parent</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="nx">createNodeField</span><span class="p">({</span>
</span></span><span class="line"><span class="cl"> <span class="nx">node</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">name</span><span class="o">:</span> <span class="s1">'sourceName'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">value</span><span class="o">:</span> <span class="nx">fileNode</span><span class="p">.</span><span class="nx">sourceInstanceName</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">})</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
<h4 id="plugin-gatsby-source-github-apihttpsgithubcomlddgatsby-source-github-api"><a href="#plugin-gatsby-source-github-apihttpsgithubcomlddgatsby-source-github-api" title="Permalink to Plugin: gatsby-source-github-api">Plugin: <a href="https://github.com/ldd/gatsby-source-github-api" rel="noopener">gatsby-source-github-api</a></a></h4><p>Not sure why <strong>gatsby-source-github-api</strong> had problems with Gatsby 3.x, but this was the error message I encountered:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">ERROR <span class="c1">#11321 PLUGIN</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="s2">"gatsby-source-github-api"</span> threw an error <span class="k">while</span> running the sourceNodes lifecycle:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Cannot destructure property <span class="s1">'createNode'</span> of <span class="s1">'boundActionCreators'</span> as it is undefined.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="m">8</span> <span class="p">|</span> <span class="o">)</span> <span class="o">=</span>> <span class="o">{</span>
</span></span><span class="line"><span class="cl"> <span class="m">9</span> <span class="p">|</span> console.log<span class="o">(</span>boundActionCreators<span class="o">)</span>
</span></span><span class="line"><span class="cl">> <span class="m">10</span> <span class="p">|</span> const <span class="o">{</span> createNode <span class="o">}</span> <span class="o">=</span> boundActionCreators<span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="p">|</span> ^
</span></span><span class="line"><span class="cl"> <span class="m">11</span> <span class="p">|</span> <span class="k">return</span> new Promise<span class="o">((</span>resolve, reject<span class="o">)</span> <span class="o">=</span>> <span class="o">{</span>
</span></span><span class="line"><span class="cl"> <span class="m">12</span> <span class="p">|</span> // we need a token to use this plugin
</span></span><span class="line"><span class="cl"> <span class="m">13</span> <span class="p">|</span> <span class="k">if</span> <span class="o">(</span><span class="nv">token</span> <span class="o">===</span> undefined<span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">File: node_modules<span class="se">\g</span>atsby-source-github-api<span class="se">\g</span>atsby-node.js:10:11</span></span></code></pre></div>
<p>A quick <a href="https://github.com/ldd/gatsby-source-github-api/pull/29" title="Pull request to add support for Gatsby v3" rel="noopener">update</a> of the npm package and all was well with the world.</p>
<h4 id="migrating-gatsby-from-v2-to-v3"><a href="#migrating-gatsby-from-v2-to-v3" title="Permalink to Migrating Gatsby from v2 to v3">Migrating Gatsby from v2 to v3</a></h4><p>For everything that wasn’t related to outdated community plugins, I followed the <a href="https://www.gatsbyjs.com/docs/reference/release-notes/migrating-from-v2-to-v3/" title="Migrating from Gatsby v2 to v3" rel="noopener">official migration guide</a>. A couple of find/replace commands across my project and I was done.</p>
<p>The other big change came from <a href="https://www.gatsbyjs.com/docs/reference/release-notes/image-migration-guide" rel="noopener">migrating <strong>gatsby-image</strong> to <strong>gatsby-plugin-image</strong></a>. This involved updating dependencies, fixing GraphQL queries, and replacing components.</p>
<p>Running <code>npx gatsby-codemods gatsby-plugin-image</code> did a lot of this work for me. Gatsby’s CLI tool is pretty good at letting you know if you’re using the deprecated <strong>gatsby-image</strong> component. It even let me know about instances where I should add <code>alt</code> attributes, which is good for accessibility.</p>
<h2 id="passing-core-web-vitals"><a href="#passing-core-web-vitals" title="Permalink to Passing Core Web Vitals">Passing Core Web Vitals</a></h2><div class="c-browser-frame">
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/mm-lighthouse-scores-062021_hu9bc504f80480ac5a2f39db19b735e995_125972_400x0_resize_q75_box.jpg 400w, /images/mm-lighthouse-scores-062021_hu9bc504f80480ac5a2f39db19b735e995_125972_800x0_resize_q75_box.jpg 800w, /images/mm-lighthouse-scores-062021.jpg 1034w" src="/images/mm-lighthouse-scores-062021_hu9bc504f80480ac5a2f39db19b735e995_125972_800x0_resize_q75_box.jpg"
alt="Perfect 100 Lighthouse scores for mademistakes.com" loading="lazy" decoding="async" width="1034" height="848" style="background: #212121"/></p>
</div>
<p>Only took me a year of tinkering to get 100’s in <strong>Google Lighthouse</strong> for performance, accessibility, best practices, and SEO. Shipping less JavaScript by installing <a href="https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-plugin-preact" rel="noopener"><strong>gatsby-plugin-preact</strong></a> certainly helped improve performance scores too.</p>
<p>But on some of my heavier pages with tons of DOM elements, 3rd party embeds from Twitter, and large animated GIFs — perf scores still take a hit.</p>
<p>Gatsby supports both <a href="https://www.gatsbyjs.com/docs/reference/built-in-components/gatsby-plugin-image#formats" rel="noopener">WebP and AVIF image formats</a> now, so I’ll need to experiment with those to see if I can shave even more time off. Just as long as my build times don’t increase too much…</p>
<h3 id="accessibility-improvements"><a href="#accessibility-improvements" title="Permalink to Accessibility improvements">Accessibility improvements</a></h3><ul>
<li>Adjusted the <code>:focus</code> and <code>:hover</code> styling of grid and list entries to render an outline around each when selected.</li>
<li>Bumped up the padding around footer links so they’re easier to tap.</li>
</ul>
<h2 id="dark-mode-updates"><a href="#dark-mode-updates" title="Permalink to Dark mode updates">Dark mode updates</a></h2><p>Flipping between light and dark themes on the site worked before, but it had room to improve. For starters it wasn’t checking if a user had color theme preference (light or dark) set in their operating system.</p>
<p>To remedy this I installed <a href="https://github.com/wKovacs64/gatsby-plugin-use-dark-mode" rel="noopener"><strong>gatsby-plugin-use-dark-mode</strong></a> to add a custom React Hook for use in my “dark mode toggle” component.</p>
<p>As an added benefit it also <a href="https://github.com/donavon/use-dark-mode#that-flash" rel="noopener">injects a small bit of JavaScript</a> that helps with the <em>dreaded flash of default theme</em> plaguing sites with dark modes.</p>
<h2 id="table-of-contents-updates"><a href="#table-of-contents-updates" title="Permalink to Table of contents updates">Table of contents updates</a></h2><p>I’m still using a feature of Gatsby’s Markdown transformer to <a href="https://www.gatsbyjs.com/plugins/gatsby-transformer-remark/#configuring-the-tableofcontents" rel="noopener">automatically create table of contents</a> from headings. But instead of dropping it into a collapsible <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details" rel="noopener"><code><details></code> element</a> I styled the unordered list as a true sidebar.</p>
<p>Could just be me, but putting it off to the side has a nicer flow than before.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/mm-2021-gatsby-post-template-toc_hu1f84d2b8e62f1ed96fd480beb3777140_233151_400x0_resize_q75_box.jpg 400w, /images/mm-2021-gatsby-post-template-toc_hu1f84d2b8e62f1ed96fd480beb3777140_233151_800x0_resize_q75_box.jpg 800w, /images/mm-2021-gatsby-post-template-toc.jpg 1268w" src="/images/mm-2021-gatsby-post-template-toc_hu1f84d2b8e62f1ed96fd480beb3777140_233151_800x0_resize_q75_box.jpg"
alt="Post template with optional table of contents pushed to the left of main content" loading="lazy" decoding="async" width="1268" height="927" style="background: #f9faf9"/></p>
<h2 id="visual-refinements"><a href="#visual-refinements" title="Permalink to Visual refinements">Visual refinements</a></h2><p>And because I can’t leave anything alone I started tinkering with the design of a few things.</p>
<h3 id="home-page"><a href="#home-page" title="Permalink to Home page">Home page</a></h3><p>Replaced the glitched image of me on the home page with a faux Polaroid frame and fixed it’s alignment with the rest of the page.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/mm-2021-home-page-intro_hu680c11e843652996caf13927459242b8_130843_400x0_resize_q75_box.jpg 400w, /images/mm-2021-home-page-intro_hu680c11e843652996caf13927459242b8_130843_800x0_resize_q75_box.jpg 800w, /images/mm-2021-home-page-intro.jpg 1327w" src="/images/mm-2021-home-page-intro_hu680c11e843652996caf13927459242b8_130843_800x0_resize_q75_box.jpg"
alt="Home template with intro and Polaroid photo" loading="lazy" decoding="async" width="1327" height="616" style="background: #f6f4f3"/></p>
<h3 id="archive-pages"><a href="#archive-pages" title="Permalink to Archive pages">Archive pages</a></h3><p>Changed the styling of the related tags component from a multi-column to an inline list to save space. I also reused this component on archive pages to help surface related topics that might have been buried before.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/mm-2021-related-topics-component_hucbaea0fed7c546fac4774cecdd53ee6f_38602_400x0_resize_q75_box.jpg 400w, /images/mm-2021-related-topics-component_hucbaea0fed7c546fac4774cecdd53ee6f_38602_800x0_resize_q75_box.jpg 800w, /images/mm-2021-related-topics-component.jpg 1262w" src="/images/mm-2021-related-topics-component_hucbaea0fed7c546fac4774cecdd53ee6f_38602_800x0_resize_q75_box.jpg"
alt="Related topics inline list component" loading="lazy" decoding="async" width="1262" height="236" style="background: #fefefe"/></p>
<p>Added the featured posts component (used on the home page) to all archive pages… if applicable.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/mm-2021-featured-posts_huc1ba94a9afa331e53b2ac9925bbe9821_144394_400x0_resize_q75_box.jpg 400w, /images/mm-2021-featured-posts_huc1ba94a9afa331e53b2ac9925bbe9821_144394_800x0_resize_q75_box.jpg 800w, /images/mm-2021-featured-posts.jpg 1266w" src="/images/mm-2021-featured-posts_huc1ba94a9afa331e53b2ac9925bbe9821_144394_800x0_resize_q75_box.jpg"
alt="Featured posts component" loading="lazy" decoding="async" width="1266" height="580" style="background: #fbfcfb"/></p>
<h3 id="works-page"><a href="#works-page" title="Permalink to Works page">Works page</a></h3><p>Pulled in more GitHub repositories under the <strong>Open source contributions</strong> section, added fork counts, and switched to a staggered card layout to add interest.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/mm-2021-github-repository-cards_hub34752b3802a1a98fd11ef30d1752ca0_184289_400x0_resize_q75_box.jpg 400w, /images/mm-2021-github-repository-cards_hub34752b3802a1a98fd11ef30d1752ca0_184289_800x0_resize_q75_box.jpg 800w, /images/mm-2021-github-repository-cards.jpg 1276w" src="/images/mm-2021-github-repository-cards_hub34752b3802a1a98fd11ef30d1752ca0_184289_800x0_resize_q75_box.jpg"
alt="GitHub repositories arranged in a grid of cards" loading="lazy" decoding="async" width="1276" height="853" style="background: #fcfdfc"/></p>
<p>Speaking of open source, I switched this repository to private on GitHub. Way too many lazy developers were reusing my content with minimal changes. I have nothing against people forking my work and making it their own. But replacing my name and social media links with your own and calling it a day is weak.</p>
<p>I may make it public again if I have time to submodule out the content, but that might be too much pain for what it’s worth.</p>
<h2 id="experimenting-with-ads"><a href="#experimenting-with-ads" title="Permalink to Experimenting with ads">Experimenting with ads</a></h2><p>To help offset the small costs of keeping this site up I’m experimenting with Google AdSense again. I was ready to ditch them once I saw how bad they degraded page speed performance.</p>
<p>After messing around with an <a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API" rel="noopener">intersection observer</a>, I was able to keep things in check by waiting to load the Adsense scripts. I’m sure this JavaScript I added to Gatsby’s <code>html.js</code> file could be improved on. But for now it’s doing the trick and I’m pennies richer for it.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="o"><</span><span class="nx">script</span>
</span></span><span class="line"><span class="cl"> <span class="nx">dangerouslySetInnerHTML</span><span class="o">=</span><span class="p">{{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">__html</span><span class="o">:</span> <span class="sb">`
</span></span></span><span class="line"><span class="cl"><span class="sb"> (function(window, document) {
</span></span></span><span class="line"><span class="cl"><span class="sb"> function loadAds() {
</span></span></span><span class="line"><span class="cl"><span class="sb"> // Load Google AdSense
</span></span></span><span class="line"><span class="cl"><span class="sb"> var ad = document.createElement('script');
</span></span></span><span class="line"><span class="cl"><span class="sb"> ad.type = 'text/javascript';
</span></span></span><span class="line"><span class="cl"><span class="sb"> ad.async = true;
</span></span></span><span class="line"><span class="cl"><span class="sb"> ad.dataset.adClient = 'ca-pub-xxxxxxxxxxxxxxxx';
</span></span></span><span class="line"><span class="cl"><span class="sb"> ad.src = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js';
</span></span></span><span class="line"><span class="cl"><span class="sb"> var sc = document.getElementsByTagName('script')[0];
</span></span></span><span class="line"><span class="cl"><span class="sb"> sc.parentNode.insertBefore(ad, sc);
</span></span></span><span class="line"><span class="cl"><span class="sb"> }
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb"> var lazyLoad = false;
</span></span></span><span class="line"><span class="cl"><span class="sb"> function onLazyLoad() {
</span></span></span><span class="line"><span class="cl"><span class="sb"> if (lazyLoad === false) {
</span></span></span><span class="line"><span class="cl"><span class="sb"> lazyLoad = true;
</span></span></span><span class="line"><span class="cl"><span class="sb"> loadAds();
</span></span></span><span class="line"><span class="cl"><span class="sb"> console.log("advertisements loaded");
</span></span></span><span class="line"><span class="cl"><span class="sb"> }
</span></span></span><span class="line"><span class="cl"><span class="sb"> }
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb"> if(!!window.IntersectionObserver){
</span></span></span><span class="line"><span class="cl"><span class="sb"> let observer = new IntersectionObserver((entries, observer) => {
</span></span></span><span class="line"><span class="cl"><span class="sb"> entries.forEach(entry => {
</span></span></span><span class="line"><span class="cl"><span class="sb"> if(entry.isIntersecting){
</span></span></span><span class="line"><span class="cl"><span class="sb"> onLazyLoad();
</span></span></span><span class="line"><span class="cl"><span class="sb"> observer.unobserve(entry.target);
</span></span></span><span class="line"><span class="cl"><span class="sb"> }
</span></span></span><span class="line"><span class="cl"><span class="sb"> });
</span></span></span><span class="line"><span class="cl"><span class="sb"> }, { rootMargin: "0px 0px 500px 0px" });
</span></span></span><span class="line"><span class="cl"><span class="sb"> document.querySelectorAll('.adsbygoogle').forEach(ad => { observer.observe(ad) });
</span></span></span><span class="line"><span class="cl"><span class="sb"> }
</span></span></span><span class="line"><span class="cl"><span class="sb"> })(window, document);
</span></span></span><span class="line"><span class="cl"><span class="sb"> `</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">}}</span>
</span></span><span class="line"><span class="cl"><span class="err">/></span></span></span></code></pre></div><p><a href="https://mademistakes.com/notes/gatsby-site-optimizations-2021/?utm_source=atom_feed">Not so quick with the Gatsby site optimizations</a> was originally published on Made Mistakes.</p>2 Little Rosebudshttps://mademistakes.com/work/2-little-rosebuds/2020-01-21T00:00:00-05:002020-01-22T10:12:52-05:00<p><img src="/images/2-little-rosebuds-feature.jpg" alt="" loading="lazy" decoding="async" width="1580" height="888"></p><p><a href="https://2littlerosebuds.com" rel="noopener"><strong>2 Little Rosebuds</strong></a> is a blog written by my wife Wendy Rose. The site started as a place for her to post about our twin girls and morphed into an outlet for her growing addiction to subscription boxes.</p>
<p>My responsibilities on the site include:</p>
<ul>
<li>Wordpress installation and maintenance.</li>
<li>Creative, theme design, and development.</li>
<li>Content management.</li>
</ul>
<h2 id="logo-design"><a href="#logo-design" title="Permalink to Logo design">Logo design</a></h2><p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/two-little-rosebuds-logo_huc8a16ffb2f442d3db8c7e9cc728cac62_26021_400x0_resize_box_3.png 400w, /images/two-little-rosebuds-logo_huc8a16ffb2f442d3db8c7e9cc728cac62_26021_800x0_resize_box_3.png 800w, /images/two-little-rosebuds-logo.png 1276w" src="/images/two-little-rosebuds-logo_huc8a16ffb2f442d3db8c7e9cc728cac62_26021_800x0_resize_box_3.png"
alt="2 Little Rosebuds logotype" loading="lazy" decoding="async" width="1276" height="456" /></p>
<p>The <strong>2 Little Rosebuds</strong> word mark has gone through several versions over the years. For this iteration I wanted something feminine — with subtle hints at veganism and “the twins,” to relate back to the blog’s identity and subject matter.</p>
<h2 id="style-tiles"><a href="#style-tiles" title="Permalink to Style tiles">Style tiles</a></h2><p>For all my projects I like to start with a quick sketch to help capture the overall tone and feel I’m going for. Creating a set of style tiles in Adobe Photoshop or Illustrator is my preferred way of doing that. Colors, basic typography, image treatments, and any other design elements I’m toying with all end up here.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/2lrb-style-tiles_hu9f6d32d3564bf3cc989fd528772c2e47_61776_400x0_resize_box_3.png 400w, /images/2lrb-style-tiles.png 780w"src="/images/2lrb-style-tiles.png"alt="2 Little Rosebuds identity style tiles" loading="lazy" decoding="async" width="780" height="1876" /></p>
<h2 id="wordpress-layouts"><a href="#wordpress-layouts" title="Permalink to Wordpress layouts">Wordpress layouts</a></h2><p>With over 3,500 posts in the last eight years, <strong>2 Little Rosebuds</strong> has become an authority on all things “subscription box.” My main goals were to keep the experience familiar and to simplify the underlying code and design wherever possible.</p>
<p>This meant keeping the established color palette, but with minor adjustments to maintain adequate contrast for improved accessibility.</p>
<p>Reducing the amount of typefaces used to improve page loading speeds. And combining the masthead, navigation bar, and search form into a single organism to give space back to what’s important — post content.</p>
<figure>
<div class="c-browser-frame">
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/2lrb-post-layout_hude7e5a704579c060cc960fb4ba370ea2_482183_400x0_resize_q75_box.jpg 400w, /images/2lrb-post-layout_hude7e5a704579c060cc960fb4ba370ea2_482183_800x0_resize_q75_box.jpg 800w, /images/2lrb-post-layout.jpg 1327w" src="/images/2lrb-post-layout_hude7e5a704579c060cc960fb4ba370ea2_482183_800x0_resize_q75_box.jpg"
alt="Screenshot of post layout." loading="lazy" decoding="async" width="1327" height="2782" style="background: #f9f7f6"/></p>
</div>
<figcaption>Single post layout.</figcaption>
</figure>
<p>Post content is given prominence by using a centered single column layout to make reading comfortable. Just Wendy’s words and photographs — no side bar clutter or distracting advertisements.</p>
<figure>
<div class="c-browser-frame">
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/2lrb-category-archive-layout_hu2bfccd7ea59da094147f2e298374ea02_522434_400x0_resize_q75_box.jpg 400w, /images/2lrb-category-archive-layout_hu2bfccd7ea59da094147f2e298374ea02_522434_800x0_resize_q75_box.jpg 800w, /images/2lrb-category-archive-layout.jpg 1388w" src="/images/2lrb-category-archive-layout_hu2bfccd7ea59da094147f2e298374ea02_522434_800x0_resize_q75_box.jpg"
alt="Screenshot of category archive layout." loading="lazy" decoding="async" width="1388" height="1446" style="background: #f9f3f3"/></p>
</div>
<figcaption>Category archive layout.</figcaption>
</figure>
<p>Layouts used for the directory got a refresher to better showcase the 650 subscription boxes listed within. Search, sort controls, box type refinements, and review scores are placed in familiar locations to help discover subscriptions while browsing.</p>
<figure>
<div class="c-browser-frame">
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/2lrb-subscription-box-directory_hu99f53f99b6ffce59f82a980fc8bcdc0b_470059_400x0_resize_q75_box.jpg 400w, /images/2lrb-subscription-box-directory_hu99f53f99b6ffce59f82a980fc8bcdc0b_470059_800x0_resize_q75_box.jpg 800w, /images/2lrb-subscription-box-directory.jpg 1388w" src="/images/2lrb-subscription-box-directory_hu99f53f99b6ffce59f82a980fc8bcdc0b_470059_800x0_resize_q75_box.jpg"
alt="Screenshot of subscription box directory." loading="lazy" decoding="async" width="1388" height="1163" style="background: #fbf8f8"/></p>
</div>
<figcaption>Subscription box directory layout.</figcaption>
</figure>
<figure>
<div class="c-browser-frame">
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/2lrb-subscription-box-layout_huf1e5f4162114aa38c006ba0a17da5263_427624_400x0_resize_q75_box.jpg 400w, /images/2lrb-subscription-box-layout_huf1e5f4162114aa38c006ba0a17da5263_427624_800x0_resize_q75_box.jpg 800w, /images/2lrb-subscription-box-layout.jpg 1388w" src="/images/2lrb-subscription-box-layout_huf1e5f4162114aa38c006ba0a17da5263_427624_800x0_resize_q75_box.jpg"
alt="Screenshot of subscription box page." loading="lazy" decoding="async" width="1388" height="1698" style="background: #f6f6f6"/></p>
</div>
<figcaption>Subscription box detail page layout.</figcaption>
</figure><p><a href="https://mademistakes.com/work/2-little-rosebuds/?utm_source=atom_feed">2 Little Rosebuds</a> was originally published on Made Mistakes.</p>Twenty Nineteenhttps://mademistakes.com/notes/twenty-nineteen/2020-01-01T00:00:00-05:002024-02-21T08:42:53-05:00<p><img src="/images/twenty-19.jpg" alt="" loading="lazy" decoding="async" width="1280" height="720"></p><p>First time doing a <em>year in review</em> post thingy. The statistics below are just for fun as there’s nothing to compare them against yet. Should be interesting to see how they deviate next year as I would have never guessed some would be as high as they were.</p>
<h2 id="2019-by-the-numbers"><a href="#2019-by-the-numbers" title="Permalink to 2019 by the numbers">2019 by the numbers</a></h2><ul>
<li><strong>1</strong> blog post</li>
<li><strong>4</strong> side projects</li>
<li><strong>365</strong> days spent growing a beard</li>
<li><strong>33</strong> video games played</li>
<li><strong>64</strong> movies watched</li>
<li><strong>56</strong> days closing all rings on Apple Watch</li>
<li><strong>6,522</strong> steps/day average</li>
</ul>
<h3 id="web-development"><a href="#web-development" title="Permalink to Web development">Web development</a></h3><p>Starting way back in 2017 I experimented with migrating my <a href="https://jekyllrb.com/" rel="noopener"><strong>Jekyll</strong></a> built personal site over to <a href="https://www.gatsbyjs.com/" rel="noopener"><strong>Gatsby</strong></a>… things didn’t work out so well. It took until 2019 for Gatsby and its ecosystem to mature enough for me to take it seriously.</p>
<p>Up until then it couldn’t handle building my site of ~1,000 posts, paginated category/tag archives, and 13,000 images. Plagued with <code>FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory</code> messages I waited as Gatsby received performance optimizations that fixed my build woes.</p>
<p>With the right cocktail of plugins I finally launched a Gatsby powered site on the last day of 2019, December 31st. Expect a post mortem in a future article as there was a lot I struggled with:</p>
<ul>
<li>Adapting Liquid templates to vanilla JavaScript and React.</li>
<li>Finding Markdown parity with Kramdown features.</li>
<li>Deploying my own instance of <a href="/mastering-jekyll/static-comments/"><strong>Staticman</strong></a> and getting comments working with Gatsby.</li>
<li>Replacing custom Jekyll plugin tags.</li>
<li>Working through <strong>Lighthouse</strong> scores that were often worse than the pre-Gatsby site.</li>
<li>Build timeouts on <strong>Netlify</strong>.</li>
</ul>
<h3 id="open-source-contributions"><a href="#open-source-contributions" title="Permalink to Open source contributions">Open source contributions</a></h3><p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/git-commits-2019_hu9531053aca1a50ce6ba6b3a096a199c7_6237_400x0_resize_box_3.png 400w, /images/git-commits-2019_hu9531053aca1a50ce6ba6b3a096a199c7_6237_800x0_resize_box_3.png 800w, /images/git-commits-2019.png 1144w" src="/images/git-commits-2019_hu9531053aca1a50ce6ba6b3a096a199c7_6237_800x0_resize_box_3.png"
alt="2019 GitHub contributions" loading="lazy" decoding="async" width="1144" height="230" /></p>
<p>In 2019 I pushed <strong>1,056</strong> commits to GitHub, most of which were to my three most popular Jekyll themes: <a href="/work/jekyll-themes/minimal-mistakes/">Minimal Mistakes</a>, <a href="/work/jekyll-themes/so-simple/">So Simple</a>, and <a href="/work/jekyll-themes/basically-basic/">Basically Basic</a>.</p>
<p>It boggles my mind that a repository that started out as a stripped down theme of my personal site has been: downloaded 96,901 times, forked 10,100 times, and starred 5,993 times. Thanks to everyone who has supported its continued development with kind words and donations!</p>
<h3 id="a-yeard-and-a-half"><a href="#a-yeard-and-a-half" title="Permalink to A yeard and a half">A yeard and a half</a></h3><p>I’ve had a love/hate relationship with beard growing for as long as I can remember. For whatever stupid reason I decided I wanted to grow a yeard in 2018… during the middle of the summer.</p>
<figure data-columns="3">
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/michael-rose-beard-3mos_huc46881bb92bacac701d67ce57b4d955b_332366_400x0_resize_q75_box.jpg 400w, /images/michael-rose-beard-3mos_huc46881bb92bacac701d67ce57b4d955b_332366_800x0_resize_q75_box.jpg 800w, /images/michael-rose-beard-3mos.jpg 960w" src="/images/michael-rose-beard-3mos_huc46881bb92bacac701d67ce57b4d955b_332366_800x0_resize_q75_box.jpg"
alt="Beard growth after 3 months." loading="lazy" decoding="async" width="960" height="1280" style="background: #1e2422"/></p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/michael-rose-beard-6mos_hu4f6b6c468301184d6abddba67cb266cf_206683_400x0_resize_q75_box.jpg 400w, /images/michael-rose-beard-6mos_hu4f6b6c468301184d6abddba67cb266cf_206683_800x0_resize_q75_box.jpg 800w, /images/michael-rose-beard-6mos.jpg 962w" src="/images/michael-rose-beard-6mos_hu4f6b6c468301184d6abddba67cb266cf_206683_800x0_resize_q75_box.jpg"
alt="Beard growth after 6 months." loading="lazy" decoding="async" width="962" height="1280" style="background: #181716"/></p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/michael-rose-beard-12mos_hu45386d04772984dad09223ed4225a8d1_393353_400x0_resize_q75_box.jpg 400w, /images/michael-rose-beard-12mos_hu45386d04772984dad09223ed4225a8d1_393353_800x0_resize_q75_box.jpg 800w, /images/michael-rose-beard-12mos.jpg 960w" src="/images/michael-rose-beard-12mos_hu45386d04772984dad09223ed4225a8d1_393353_800x0_resize_q75_box.jpg"
alt="Beard growth after a year." loading="lazy" decoding="async" width="960" height="1280" style="background: #393637"/></p>
<figcaption>Beard progression at 3 months, 6 months, and 12 months.</figcaption>
</figure>
<p>At the close of 2019 I’ve gone eighteen months without shaving and plan on trying for whatever you call a 2x yeard. We’ll see though as eating soup, snagging hairs in zippers, and daily beard maintenance does get old on occassion.</p>
<h3 id="video-games-played"><a href="#video-games-played" title="Permalink to Video games played">Video games played</a></h3><p>If it wasn’t for the Nintendo Switch and being a portable console, I would have never played this many games. Being able to quickly resume a game before dinner or when the twins are in bed makes a difference.</p>
<blockquote class="twitter-tweet" data-dnt="true"><p lang="en" dir="ltr">This game is ridic.<a href="https://twitter.com/hashtag/UntitledGooseGame?src=hash&ref_src=twsrc%5Etfw">#UntitledGooseGame</a> <a href="https://twitter.com/hashtag/NintendoSwitch?src=hash&ref_src=twsrc%5Etfw">#NintendoSwitch</a> <a href="https://t.co/0YHhFVmrpU">pic.twitter.com/0YHhFVmrpU</a></p>— 𝔐𝔦𝔠𝔥𝔞𝔢𝔩 ℜ𝔬𝔰𝔢 (@mmistakes) <a href="https://twitter.com/mmistakes/status/1175410210359238657?ref_src=twsrc%5Etfw">September 21, 2019</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>I’m slowly making it through my backlog of Playstation must plays, but sneaking those in has been difficult. As most have mature ratings, they’re not something I can play with my kids. And the fact that it’s not a portable console means I need to wrestle the television from my wife, who sure as shit doesn’t want to watch me game for an hour.</p>
<table>
<thead>
<tr>
<th>Game</th>
<th>Rating</th>
<th>Note</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Sayonara Wild Hearts</strong> (Switch)</td>
<td>★★★★</td>
<td>Not quite as rhythm heavy as I was expecting, but solid.</td>
</tr>
<tr>
<td><strong>Untitled Goose Game</strong> (Switch)</td>
<td>★★★★★</td>
<td>A game I never knew I wanted till I did.</td>
</tr>
<tr>
<td><strong>Beyond Two Souls</strong> (PS4)</td>
<td>★★★</td>
<td>About 2 hours in, I’m a sucker for David Cage games.</td>
</tr>
<tr>
<td><strong>Blasphemous</strong> (Switch)</td>
<td>★★★</td>
<td>Started the demo, will revist later.</td>
</tr>
<tr>
<td><strong>River City Girls</strong> (Switch)</td>
<td>★★★★</td>
<td>Music, art, brawling is all great. Has me pumped for <strong>Streets of Rage 4</strong>.</td>
</tr>
<tr>
<td><strong>Luigi’s Mansion 3</strong> (Switch)</td>
<td>★★★</td>
<td>Controls take some getting used to. CO-OP is fun once you unlock Gooigi. Challenging boss battles.</td>
</tr>
<tr>
<td><strong>Detroit: Become Human</strong> (PS4)</td>
<td>★★★★</td>
<td>Would like to replay to see all missed story paths… if I ever have the time.</td>
</tr>
<tr>
<td><strong>Super Mario Maker 2</strong> (Switch)</td>
<td>★★★★★</td>
<td>Consumer of Mario levels more so than a creator. Suffered through online lag to hit Rank A in multiplayer.</td>
</tr>
<tr>
<td><strong>Uncharted: The Lost Legacy</strong> (PS4)</td>
<td>★★★★</td>
<td>Completed the story.</td>
</tr>
<tr>
<td><strong>The Legend of Zelda: A Link to the Past</strong> (Switch SNES)</td>
<td>★★★★★</td>
<td>Playing through for the first time on an actual console. At the last dungeon, ready to fight Ganon.</td>
</tr>
<tr>
<td><strong>Super Mario World</strong> (Switch SNES)</td>
<td>★★★★★</td>
<td>Mario Maker 2 got me revisiting the classics.</td>
</tr>
<tr>
<td><strong>Ninja Gaiden</strong> (Switch NES)</td>
<td>★★★★★</td>
<td>How I ever beat this as a kid is beyond me.</td>
</tr>
<tr>
<td><strong>Zelda II: The Adventure of Linke</strong> Switch (NES)</td>
<td>★★★★</td>
<td>That last palace is a doozy…</td>
</tr>
<tr>
<td><strong>Uncharted 4: A Thief’s End</strong> (PS4)</td>
<td>★★★★★</td>
<td>Completed the story, missed a bunch of collectibles.</td>
</tr>
<tr>
<td><strong>Uncharted The Nathan Drake Collection</strong> (PS4)</td>
<td>★★★</td>
<td>Rushed my way through 1, 2, and 3 so I could start 4.</td>
</tr>
<tr>
<td><strong>New Super Mario Bros. U Deluxe</strong> (Switch)</td>
<td>★★★★</td>
<td>I’m cool with Nintendo re-releasing Wii U games I never played.</td>
</tr>
<tr>
<td><strong>Cuphead</strong> (Switch)</td>
<td>★★★★</td>
<td>20% complete. This game isn’t fooling around.</td>
</tr>
<tr>
<td><strong>Sonic Mania</strong> (Xbox One)</td>
<td>★★★★</td>
<td>Fits in perfectly with the Sega Genesis era of games. Challenge ramps up at the end.</td>
</tr>
<tr>
<td><strong>Super Kirby Clash</strong> (Switch)</td>
<td>★★</td>
<td>Micro-transaction hell.</td>
</tr>
<tr>
<td><strong>Super Phantom Cat: Remake</strong> (Switch)</td>
<td>★★★</td>
<td>Purchased on the cheap, OK platformer.</td>
</tr>
<tr>
<td><strong>Yoshi’s Crafted World</strong> (Switch)</td>
<td>★★★★</td>
<td>Great art direction. CO-OP’d with the twins.</td>
</tr>
<tr>
<td><strong>Super Mario Party</strong> (Switch)</td>
<td>★★★</td>
<td>Great amount of mini-games but really wish it would let you use Pro Controllers.</td>
</tr>
<tr>
<td><strong>Super Mario Odyssey</strong> (Switch)</td>
<td>★★★★★</td>
<td>Completed the story and bought most costumes. Collected 596 moons.</td>
</tr>
<tr>
<td><strong>Tetris 99</strong> (Switch)</td>
<td>★★★★</td>
<td>Love me some Tetris. Won 2 of 388 games.</td>
</tr>
<tr>
<td><strong>Thumper</strong> (Switch)</td>
<td>★★★★</td>
<td>So much dread.</td>
</tr>
<tr>
<td><strong>Gris</strong> (Switch)</td>
<td>★★★★</td>
<td>Walking simulator with a watercolor aesthetic.</td>
</tr>
<tr>
<td><strong>Hollow Knight</strong> (Switch)</td>
<td>★★★★</td>
<td>Sank over 20 hours into it and no idea if I’m near the end or not.</td>
</tr>
<tr>
<td><strong>What Remains of Edith Finch</strong> (PS4)</td>
<td>★★★★</td>
<td>Surprisingly good Playstation Plus free game.</td>
</tr>
<tr>
<td><strong>Hellblade: Senua’s Sacrifice</strong> (PS4)</td>
<td>★★★★★</td>
<td>Loved everything about this game. Can’t wait for the sequel.</td>
</tr>
<tr>
<td><strong>Bulletstorm: Full Clip Edition</strong> (PS4)</td>
<td>★★★</td>
<td>Picked up for free via Playstation Plus, about 75% the way through. Potty mouth shooter fare.</td>
</tr>
<tr>
<td><strong>The Last Guardian</strong></td>
<td>★★</td>
<td>Really wanted to like this one, but the floaty ass controls ruins it for me. Made it about midway through the story… I think.</td>
</tr>
<tr>
<td><strong>Shadow of the Tomb Raider</strong> (PS4)</td>
<td>★★★★</td>
<td>Enjoyed the tropical scenery more than the snow in Rise.</td>
</tr>
<tr>
<td><strong>The Order: 1886</strong> (PS4)</td>
<td>★★★★</td>
<td>Purchased on sale, not bad.</td>
</tr>
<tr>
<td><strong>Super Smash Bros. Ultimate</strong> (Switch)</td>
<td>★★★</td>
<td>Awful at fighting games. Unlocked all the characters, stopped playing shortly after.</td>
</tr>
</tbody>
</table>
<h3 id="movies-watched"><a href="#movies-watched" title="Permalink to Movies watched">Movies watched</a></h3><p>According to <a href="https://letterboxd.com/mmistakes/" rel="noopener"><strong>Letterboxd</strong></a> below are all the flicks I watched last year. And here I thought I binged more television series than movies — guess not.</p>
<table>
<thead>
<tr>
<th>Movie</th>
<th>Rating</th>
<th>Note</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Star Wars: The Rise of Skywalker</strong> (2019)</td>
<td>★★★</td>
<td>Should have been split into two parts.</td>
</tr>
<tr>
<td><strong>Mission: Impossible - Fallout</strong> (2018)</td>
<td>★★★</td>
<td>By the Numbers.</td>
</tr>
<tr>
<td><strong>Marriage Story</strong> (2019)</td>
<td>★★★</td>
<td>Meh.</td>
</tr>
<tr>
<td><strong>Frozen 2</strong> (2019)</td>
<td>★★★</td>
<td>Too much singing.</td>
</tr>
<tr>
<td><strong>The Grinch</strong> (2018)</td>
<td>★★★</td>
<td>Didn’t bore me as much as the Jim Carrey version.</td>
</tr>
<tr>
<td><strong>Klaus</strong> (2019)</td>
<td>★★★</td>
<td>For the kids… and it’s the Christmas season right?</td>
</tr>
<tr>
<td><strong>The Irishman</strong> (2019)</td>
<td>★★★</td>
<td>The digital de-aging didn’t bother me.</td>
</tr>
<tr>
<td><strong>The Game Changers</strong> (2018)</td>
<td>★★★</td>
<td>Tough vegans.</td>
</tr>
<tr>
<td><strong>The Lobster</strong> (2015)</td>
<td>★★★</td>
<td>I have questions.</td>
</tr>
<tr>
<td><strong>Carrie</strong> (1976)</td>
<td>★★★</td>
<td>So that’s where all the references came from.</td>
</tr>
<tr>
<td><strong>Tell Me Who I Am</strong> (2019)</td>
<td>★★★★</td>
<td>I’m a sucker for documentaries of this nature.</td>
</tr>
<tr>
<td><strong>El Camino: A Breaking Bad Movie</strong> (2019)</td>
<td>★★★</td>
<td>Unnecessary but good.</td>
</tr>
<tr>
<td><strong>Skin</strong> (2018)</td>
<td>★★★★</td>
<td>If it has a hint of a true story to it, I’ll bite.</td>
</tr>
<tr>
<td><strong>Between Two Ferns: The Movie</strong> (2019)</td>
<td>★★★★</td>
<td>Yup, what I expected.</td>
</tr>
<tr>
<td><strong>In the Shadow of the Moon</strong> (2019)</td>
<td>★★★</td>
<td>Sort of like <strong>Looper</strong> I guess.</td>
</tr>
<tr>
<td><strong>Pulp Fiction</strong> (1994)</td>
<td>★★★★★</td>
<td>Rewatched.</td>
</tr>
<tr>
<td><strong>Don’t Break Down: A Film About Jawbreaker</strong> (2017)</td>
<td>★★★★</td>
<td>Probably need to enjoy their music to get something out of this.</td>
</tr>
<tr>
<td><strong>Screwball</strong> (2018)</td>
<td>★★★</td>
<td>Roids.</td>
</tr>
<tr>
<td><strong>Ralph Breaks the Internet</strong> (2018)</td>
<td>★★★</td>
<td>A few memes a lot of zzzZzzs.</td>
</tr>
<tr>
<td><strong>Jack of all Trades</strong> (2018)</td>
<td>★★★</td>
<td>Hard toilet paper.</td>
</tr>
<tr>
<td><strong>Pokémon Detective Pikachu</strong> (2019)</td>
<td>★★</td>
<td>I call bullshit.</td>
</tr>
<tr>
<td><strong>Bob Lazar: Area 51 and Flying Saucers</strong> (2018)</td>
<td>★★</td>
<td>Zzzzzz.</td>
</tr>
<tr>
<td><strong>Being Frank: The Chris Sievey Story</strong> (2018)</td>
<td>★★★</td>
<td>The true story behind that one Michael Fassbender flick.</td>
</tr>
<tr>
<td><strong>Toy Story 4</strong> (2019)</td>
<td>★★★</td>
<td>Spoony.</td>
</tr>
<tr>
<td><strong>Us</strong> (2019)</td>
<td>★★★</td>
<td>I have questions.</td>
</tr>
<tr>
<td><strong>Avengers: Endgame</strong> (2019)</td>
<td>★★★</td>
<td>Ten years of plot threads tied up in 3 hours… exactly how you’d expect.</td>
</tr>
<tr>
<td><strong>The Transformers: The Movie</strong> (1986)</td>
<td>★★★★★</td>
<td>Rewatched with commentary. Not much value added from the director who explained exactly what we saw on screen. More insight from the writer would have been better.</td>
</tr>
<tr>
<td><strong>Extremely Wicked, Shockingly Evil and Vile</strong> (2019)</td>
<td>★★★</td>
<td>Found the tapes documentary more interesting.</td>
</tr>
<tr>
<td><strong>The Billboard Boys</strong> (2016)</td>
<td>★★★</td>
<td><em>…took a shower, ready to go another two years.</em></td>
</tr>
<tr>
<td><strong>Mids 90s</strong> (2018)</td>
<td>★★★</td>
<td>Didn’t disappoint with the non-ending.</td>
</tr>
<tr>
<td><strong>Zodiac</strong> (2007)</td>
<td>★★★</td>
<td>Rewatched.</td>
</tr>
<tr>
<td><strong>Glass</strong> (2019)</td>
<td>★★★</td>
<td>Of course it was a purple iMac.</td>
</tr>
<tr>
<td><strong>Christopher Robin</strong> (2018)</td>
<td>★★★</td>
<td>Meh.</td>
</tr>
<tr>
<td><strong>This is 40</strong> (2012)</td>
<td>★★★★</td>
<td>Relatable.</td>
</tr>
<tr>
<td><strong>The Endless</strong> (2017)</td>
<td>★★★</td>
<td>Groundhog day for hipsters.</td>
</tr>
<tr>
<td><strong>The Stanford Prison Experiment</strong> (2015)</td>
<td>★★★</td>
<td>So many bad fake mustaches.</td>
</tr>
<tr>
<td><strong>The Fear of 13</strong> (2015)</td>
<td>★★★</td>
<td>Prison.</td>
</tr>
<tr>
<td><strong>Kong: Skull Island</strong> (2017)</td>
<td>★★★</td>
<td>Dong.</td>
</tr>
<tr>
<td><strong>Aquaman</strong> (2018)</td>
<td>★★★★</td>
<td><em>We could have just peed on it.</em></td>
</tr>
<tr>
<td><strong>Creed II</strong> (2018)</td>
<td>★★★</td>
<td>Nostalgic for <strong>Rocky IV</strong>.</td>
</tr>
<tr>
<td><strong>The Hateful Eight</strong> (2015)</td>
<td>★★★</td>
<td>Mmm, jelly beans.</td>
</tr>
<tr>
<td><strong>Triple Frontier</strong> (2019)</td>
<td>★★★</td>
<td>Kept waiting for the other shoe to drop, then <strong>Metallica</strong> did.</td>
</tr>
<tr>
<td><strong>The Matrix Revolutions</strong> (2003)</td>
<td>★★★</td>
<td>Rewatched.</td>
</tr>
<tr>
<td><strong>The Matrix Reloaded</strong> (2003)</td>
<td>★★★</td>
<td>Rewatched.</td>
</tr>
<tr>
<td><strong>Eighth Grade</strong> (2018)</td>
<td>★★★</td>
<td>Just wait until high school.</td>
</tr>
<tr>
<td><strong>The Matrix</strong> (1999)</td>
<td>★★★★</td>
<td>Rewatched.</td>
</tr>
<tr>
<td><strong>The Lost Arcade</strong> (2015)</td>
<td>★★★</td>
<td>Fighting game snobbery.</td>
</tr>
<tr>
<td><strong>Free Solo</strong> (2018)</td>
<td>★★★★</td>
<td>“So delighted.”</td>
</tr>
<tr>
<td><strong>Mandy</strong> (2018)</td>
<td>★★★</td>
<td>There’s no arguing this thing oozed style. Not sure what the fuck I just dreamed though.</td>
</tr>
<tr>
<td><strong>Lords of Chaos</strong> (2018)</td>
<td>★★★</td>
<td>Strangely enough, black metal and Sigur Rós compliment each other well.</td>
</tr>
<tr>
<td><strong>Paddleton</strong> (2019)</td>
<td>★★★</td>
<td>Slow burn.</td>
</tr>
<tr>
<td><strong>Shirkers</strong> (2018)</td>
<td>★★</td>
<td>This score really annoyed the piss outta me.</td>
</tr>
<tr>
<td><strong>Isle of Dogs</strong> (2018)</td>
<td>★★★</td>
<td>Yup, it’s a W.A. film.</td>
</tr>
<tr>
<td><strong>Alt-Right: Age of Rage</strong> (2018)</td>
<td>★★★</td>
<td></td>
</tr>
<tr>
<td><strong>The Edge of Seventeen</strong> (2016)</td>
<td>★★★</td>
<td>I feel like I’ve seen this movie before…</td>
</tr>
<tr>
<td><strong>Abducted in Plain Sight</strong> (2017)</td>
<td>★★★★</td>
<td>Wow</td>
</tr>
<tr>
<td><strong>Root Cause</strong> (2019)</td>
<td>★★★</td>
<td>No comment.</td>
</tr>
<tr>
<td><strong>The Family I Had</strong> (2017)</td>
<td>★★★</td>
<td></td>
</tr>
<tr>
<td><strong>Toy Story 2</strong> (1999)</td>
<td>★★★</td>
<td>Rewatched.</td>
</tr>
<tr>
<td><strong>Toy Story</strong> (1995)</td>
<td>★★★</td>
<td>Rewatched.</td>
</tr>
<tr>
<td><strong>Fyre</strong> (2019)</td>
<td>★★★</td>
<td>Living the dream.</td>
</tr>
<tr>
<td><strong>Unbreakable</strong> (2000)</td>
<td>★★★★</td>
<td>Rewatched.</td>
</tr>
<tr>
<td><strong>A Fat Wreck</strong> (2016)</td>
<td>★★★★</td>
<td>Nostalgic for pop punk.</td>
</tr>
<tr>
<td><strong>Solo: A Star Wars Story</strong> (2018)</td>
<td>★★★★</td>
<td>Can’t believe all the negative press.</td>
</tr>
<tr>
<td><strong>Black Mirror: Bandersnatch</strong> (2018)</td>
<td>★★★</td>
<td>Would have been fine with this not being <em>interactive</em> at all.</td>
</tr>
</tbody>
</table>
<h3 id="health-and-activity"><a href="#health-and-activity" title="Permalink to Health and activity">Health and activity</a></h3><p>The last couple of years I’ve been wanting to ditch FitBit devices for Apple Watch. My hestitation being battery life and needing to charge it daily. As someone who wore a Fitbit for 5 days straight without charging — this was a deal breaker.</p>
<p>When Apple released a Watch this fall with an always-on screen, I decided to try it out. On paper it does pretty much everything the Fitbits I’ve owned for years do. But some reason it’s motivated me to be active in ways they couldn’t.</p>
<figure data-columns="3">
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/activity-app-01-2020_huab3b1ff89d880e2c2ad29bdd41a9dae8_370872_400x0_resize_q75_box.jpg 400w, /images/activity-app-01-2020_huab3b1ff89d880e2c2ad29bdd41a9dae8_370872_800x0_resize_q75_box.jpg 800w, /images/activity-app-01-2020.jpg 1242w" src="/images/activity-app-01-2020_huab3b1ff89d880e2c2ad29bdd41a9dae8_370872_800x0_resize_q75_box.jpg"
alt="Closed rings for December 2019 in the Activity iOS app." loading="lazy" decoding="async" width="1242" height="2138" style="background: #070605"/></p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/health-app-activity-2019_hu15518c2ddd1824688627d518f7fc259c_214794_400x0_resize_q75_box.jpg 400w, /images/health-app-activity-2019_hu15518c2ddd1824688627d518f7fc259c_214794_800x0_resize_q75_box.jpg 800w, /images/health-app-activity-2019.jpg 1242w" src="/images/health-app-activity-2019_hu15518c2ddd1824688627d518f7fc259c_214794_800x0_resize_q75_box.jpg"
alt="Activity in the Health app for 2019." loading="lazy" decoding="async" width="1242" height="2138" style="background: #f9fafa"/></p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/attain-app-january-2020_hu6e84813c830c2b143ddb28b407af5283_202751_400x0_resize_q75_box.jpg 400w, /images/attain-app-january-2020_hu6e84813c830c2b143ddb28b407af5283_202751_800x0_resize_q75_box.jpg 800w, /images/attain-app-january-2020.jpg 1242w" src="/images/attain-app-january-2020_hu6e84813c830c2b143ddb28b407af5283_202751_800x0_resize_q75_box.jpg"
alt="Attain app dashbaord in January of 2019." loading="lazy" decoding="async" width="1242" height="2138" style="background: #d00892"/></p>
<figcaption>iOS health app screenshots for 2019.</figcaption>
</figure>
<p>I’ve gone from hitting move and workout goals 3 days a week to all 7. Earning gift card rewards via <a href="https://www.attainbyaetna.com/" rel="noopener">Aetna’s <strong>Attain</strong> app</a> is a great motivator as well.</p>
<p>I still don’t like that I need to charge it everyday since I use it for sleep tracking. But a well timed charge during my morning shower is usually enough to top it off for the day.</p>
<h2 id="heading-into-2020"><a href="#heading-into-2020" title="Permalink to Heading into 2020">Heading into 2020</a></h2><p>I don’t consciously set goals or make resolutions really. If I can do more of the same as 2019 I’ll be happy. Now that I’m more active, I’d like to amp up my consumption of fruits and vegetables this year. I eat a lot of beige processed food that I’m sure is up to no good in my body.</p>
<p>We’ll see I guess…</p><p><a href="https://mademistakes.com/notes/twenty-nineteen/?utm_source=atom_feed">Twenty Nineteen</a> was originally published on Made Mistakes.</p>Faster Netlify buildshttps://mademistakes.com/notes/faster-netlify-builds/2019-02-26T00:00:00-05:002021-04-29T13:35:10-04:00<p>Slowly but surely, I’ve been chipping away at my site’s <a href="/notes/autumn-refresh/#build-and-deploy">build time on <strong>Netlify</strong></a>.</p>
<p>There’s little left for me to optimize until Jekyll drops some nice updates in <a href="https://github.com/jekyll/jekyll/projects/2" rel="noopener">version 4.0</a>. I’ve <a href="https://github.com/mmistakes/made-mistakes-jekyll/issues/629" rel="noopener">cached the rendering of Liquid includes</a> across <code>_layouts</code> via the <a href="https://github.com/benbalter/jekyll-include-cache" rel="noopener"><code>{% include_cached %}</code></a> tag, limited use of <code>{% for %}</code> loops over large collections like <code>{% site.posts %}</code>, and <a href="/articles/using-jekyll-2017/#optimization">stripped Jekyll’s duties down</a> to solely a HTML generator.</p>
<p>The “little” that remains, is about a gigabyte of images I pipe through <strong>Gulp</strong>. Thousands of high resolution images are processed by <a href="https://github.com/lovell/sharp" rel="noopener"><strong>Sharp</strong></a> into various sizes. I’ve been able to knock this down from 18 minutes, to six on a fresh build…</p>
<div class="c-browser-frame">
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/netlify-deploy-log_hu167463557533a0e413130161c491ae6f_41632_400x0_resize_box_3.png 400w, /images/netlify-deploy-log_hu167463557533a0e413130161c491ae6f_41632_800x0_resize_box_3.png 800w, /images/netlify-deploy-log.png 1190w" src="/images/netlify-deploy-log_hu167463557533a0e413130161c491ae6f_41632_800x0_resize_box_3.png"
alt="Screenshot of Netlify’s deploy log for Made Mistakes." loading="lazy" decoding="async" width="1190" height="724" /></p>
</div>
<p>When building the site locally I store the image artifacts in a temporary folder, and generate new ones if the source changes. When Netlify generates the site it processes these images each build — regardless if they have changed or not.</p>
<p>By stashing the processed images in a <em>secret</em> Netlify cache folder<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> and using <a href="https://github.com/mmistakes/made-mistakes-jekyll/tree/master/gulp" rel="noopener">Gulp to move files</a> around, I cut the build time in half. Which is fantastic since I’m now averaging 5–8 minutes for dependencies to install, Jekyll to run, and Netlify to deploy the site.</p>
<p><strong>Excerpt from my Gulp build task</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// 'gulp copy:images:cached' -- copies cached images from Netlify's `/opt/build/cache/` folder to `/dist/`
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="nx">gulp</span><span class="p">.</span><span class="nx">task</span><span class="p">(</span><span class="s2">"copy:images:cached"</span><span class="p">,</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="nx">gulp</span>
</span></span><span class="line"><span class="cl"> <span class="p">.</span><span class="nx">src</span><span class="p">(</span><span class="nx">paths</span><span class="p">.</span><span class="nx">imageFilesCachePath</span> <span class="o">+</span> <span class="s2">"/**/*"</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">newer</span><span class="p">(</span><span class="nx">paths</span><span class="p">.</span><span class="nx">imageFilesSite</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"> <span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">gulp</span><span class="p">.</span><span class="nx">dest</span><span class="p">(</span><span class="nx">paths</span><span class="p">.</span><span class="nx">imageFilesSite</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Netlify has an undocumented <code>/opt/build/cache/</code> folder that is cached and persists between builds. <a href="https://www.contentful.com/blog/2018/05/17/faster-static-site-builds-part-one-process-only-what-you-need/#caching-for-the-win" rel="noopener">Faster static site builds Part 1</a> <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
</ol>
</div><p><a href="https://mademistakes.com/notes/faster-netlify-builds/?utm_source=atom_feed">Faster Netlify builds</a> was originally published on Made Mistakes.</p>Autumn refreshhttps://mademistakes.com/notes/autumn-refresh/2018-11-12T00:00:00-05:002022-12-02T10:15:45-05:00<p><img src="/images/autumn-refresh-feature.jpg" alt="" loading="lazy" decoding="async" width="1920" height="1059"></p><p>Lately I’ve been toying with the idea of migrating from <a href="https://jekyllrb.com/" rel="noopener"><strong>Jekyll</strong></a> to <a href="https://www.gatsbyjs.com/" rel="noopener"><strong>GatsbyJS</strong></a> (more on that in a future post). Initial tests look promising, but there are <a href="https://github.com/mmistakes/gatsby-test/issues/1" rel="noopener">some issues</a> I’m still working through.</p>
<p>In the meantime I’ve taken visual cues from <a href="https://reactjs.org/docs/react-component.html" rel="noopener">React components</a> I built in a repo where I was <a href="https://github.com/mmistakes/gatsby-test" rel="noopener">playing around with Gatsby</a>, and adapted them here as standard HTML/CSS.</p>
<h2 id="design-changes"><a href="#design-changes" title="Permalink to Design changes">Design changes</a></h2><p>In <a href="https://github.com/mmistakes/made-mistakes-jekyll/releases/tag/12.0.0" rel="noopener">Made Mistakes v12</a> text and image where large and readible, but didn’t exactly fill the canvas elegantly. I wanted to change that with this design refresh.</p>
<figure>
<div class="c-browser-frame">
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/mm-desktop-design-old_hu1688796b51330ee85caba5f2f1d2c4c7_118451_400x0_resize_q75_box.jpg 400w, /images/mm-desktop-design-old_hu1688796b51330ee85caba5f2f1d2c4c7_118451_800x0_resize_q75_box.jpg 800w, /images/mm-desktop-design-old.jpg 900w" src="/images/mm-desktop-design-old_hu1688796b51330ee85caba5f2f1d2c4c7_118451_800x0_resize_q75_box.jpg"
alt="Screenshot of mademistakes.com’s design before the refresh." loading="lazy" decoding="async" width="900" height="1031" style="background: #e0e0e4"/></p>
</div>
<figcaption>Site design before the refresh.</figcaption>
</figure>
<p>Since majority of the visitors to my site use modern browsers, I had a good excuse to play with <code>display: grid</code>. I’ve removed the <a href="http://oddbird.net/susy/" rel="noopener"><strong>Susy</strong> mixins</a> and most of the <code>float</code> based columns, which cut the amount of CSS I had to write considerably.</p>
<figure>
<div class="c-browser-frame">
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/mm-desktop-design-new_huf2fd44b00cb32a85012fbc62d4505d9a_69946_400x0_resize_q75_box.jpg 400w, /images/mm-desktop-design-new_huf2fd44b00cb32a85012fbc62d4505d9a_69946_800x0_resize_q75_box.jpg 800w, /images/mm-desktop-design-new.jpg 900w" src="/images/mm-desktop-design-new_huf2fd44b00cb32a85012fbc62d4505d9a_69946_800x0_resize_q75_box.jpg"
alt="Screenshot of mademistakes.com’s design after the refresh." loading="lazy" decoding="async" width="900" height="613" style="background: #ebecee"/></p>
</div>
<figcaption>Site design after the refresh.</figcaption>
</figure>
<p>To my eye this new layout breaks up the page better. Content comes into view earlier on the page, and there’s plenty of room for ancillary information on the right. With a little <code>position: sticky</code> sprinkled on the <code>aside</code> column, internal skip links stay fixed in view — when space allows.</p>
<aside class="c-notice"><strong class="c-notice_heading">Pro tip: Use the Firefox DevTools when working with CSS grid layouts</strong><div class="c-notice_content"><p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/firefox-grid-inspector-screenshot_huf55acca98289f6008606dd3560835a23_127603_400x0_resize_q75_box.jpg 400w, /images/firefox-grid-inspector-screenshot_huf55acca98289f6008606dd3560835a23_127603_800x0_resize_q75_box.jpg 800w, /images/firefox-grid-inspector-screenshot.jpg 900w" src="/images/firefox-grid-inspector-screenshot_huf55acca98289f6008606dd3560835a23_127603_800x0_resize_q75_box.jpg"
alt="Firefox Grid Inspector screenshot" loading="lazy" decoding="async" width="900" height="554" style="background: #f4f4f5"/></p>
<p>Out of all the major browsers, Firefox’s <a href="https://developer.mozilla.org/en-US/docs/Tools/Page_Inspector/How_to/Examine_grid_layouts" rel="noopener"><strong>Grid Inspector</strong></a> is arguably the best for examining, modifying, or debugging grid layouts.</p></div>
</aside>
<h2 id="accessibility-improvements"><a href="#accessibility-improvements" title="Permalink to Accessibility improvements">Accessibility improvements</a></h2><p>This refresh prompted me to test how accessible the site is and fix any glaring issues.</p>
<h3 id="buttons-have-an-accessible-name"><a href="#buttons-have-an-accessible-name" title="Permalink to Buttons have an accessible name">Buttons have an accessible name</a></h3><blockquote>
<p>When a button doesn’t have an accessible name, screen readers announce it as “button”, making it unusable for users who rely on screen readers.</p>
</blockquote>
<p>Digging in, I discovered <a href="http://bigfootjs.com/" rel="noopener"><strong>bigfoot.js</strong></a><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> didn’t name <code><button></code> elements that it created. Thankfully the button markup is configurable so I was able replace a set of presentational-only <code><svg></code> elements with unique names instead.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">bigfoot</span> <span class="o">=</span> <span class="nx">$</span><span class="p">.</span><span class="nx">bigfoot</span><span class="p">({</span>
</span></span><span class="line"><span class="cl"> <span class="nx">actionOriginalFN</span><span class="o">:</span> <span class="s1">'delete'</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">buttonMarkup</span><span class="o">:</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="s1">'<div class="bigfoot-footnote__container">'</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl"> <span class="s1">' <button href="#" class="bigfoot-footnote__button" rel="footnote"'</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl"> <span class="s1">' id="{{SUP:data-footnote-backlink-ref}}"'</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl"> <span class="s1">' data-footnote-number="{{FOOTNOTENUM}}"'</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl"> <span class="s1">' data-footnote-identifier="{{FOOTNOTEID}}"'</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl"> <span class="s1">' alt="See Footnote {{FOOTNOTENUM}}"'</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl"> <span class="s1">' data-bigfoot-footnote="{{FOOTNOTECONTENT}}">'</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl"> <span class="s1">' <span class="visually-hidden">{{FOOTNOTENUM}}</span>'</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl"> <span class="s1">' </button>'</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl"> <span class="s1">'</div>'</span>
</span></span><span class="line"><span class="cl"> <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div>
<h3 id="color-contrast-is-satisfactory"><a href="#color-contrast-is-satisfactory" title="Permalink to Color contrast is satisfactory">Color contrast is satisfactory</a></h3><p>The color of elements like captions and footer text were too light, so I darkened them. I did the same for links by giving them a contrast ratio of <code>4.77</code>.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/mm-link-color-contrast-raio_hu5c6d12e07d144a820a1331e7ab347317_38472_400x0_resize_box_3.png 400w, /images/mm-link-color-contrast-raio_hu5c6d12e07d144a820a1331e7ab347317_38472_800x0_resize_box_3.png 800w, /images/mm-link-color-contrast-raio.png 800w" src="/images/mm-link-color-contrast-raio_hu5c6d12e07d144a820a1331e7ab347317_38472_800x0_resize_box_3.png"
alt="link color contrast ratio screenshot" loading="lazy" decoding="async" width="800" height="530" /></p>
<p>The Color Picker in Chrome’s DevTools will show you the contrast ratio of text elements to help make your site more accessible to users with low-vision impairments or color-vision deficiencies.</p>
<h2 id="performance-improvements"><a href="#performance-improvements" title="Permalink to Performance improvements">Performance improvements</a></h2><p>Optimizations with the biggest impact (minifying, concatenating, inlining critical CSS) I was already doing, but there was still room for improvement.</p>
<h3 id="defer-offscreen-images"><a href="#defer-offscreen-images" title="Permalink to Defer offscreen images">Defer offscreen images</a></h3><blockquote>
<p>Consider lazy-loading offscreen and hidden images after all critical resources have finished loading to lower time to interactive.</p>
</blockquote>
<p>Large feature images were already lazy-loaded and served responsively using <code>srcset</code> and a handful of sized images. Images found in the body <code>{{ content }}</code> of my Markdown files were not.</p>
<p>Taking a cue from <a href="https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-remark-images" rel="noopener"><strong>gatsby-remark-images’</strong></a> playbook, I wrote a small plugin to convert Markdown image syntax into an <code><img></code> element with synatactically sugar for lazy-loading. To my surprise this actually worked.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># Description: Jekyll plugin to replace Markdown image syntax with lazy-load HTML markup</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">Jekyll</span><span class="o">::</span><span class="no">Hooks</span><span class="o">.</span><span class="n">register</span> <span class="ss">:posts</span><span class="p">,</span> <span class="ss">:pre_render</span> <span class="k">do</span> <span class="o">|</span><span class="n">post</span><span class="p">,</span> <span class="n">payload</span><span class="o">|</span>
</span></span><span class="line"><span class="cl"> <span class="n">docExt</span> <span class="o">=</span> <span class="n">post</span><span class="o">.</span><span class="n">extname</span><span class="o">.</span><span class="n">tr</span><span class="p">(</span><span class="s1">'.'</span><span class="p">,</span> <span class="s1">''</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="c1"># only process Markdown files</span>
</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="n">payload</span><span class="o">[</span><span class="s1">'site'</span><span class="o">][</span><span class="s1">'markdown_ext'</span><span class="o">].</span><span class="n">include?</span> <span class="n">docExt</span>
</span></span><span class="line"><span class="cl"> <span class="n">newContent</span> <span class="o">=</span> <span class="n">post</span><span class="o">.</span><span class="n">content</span><span class="o">.</span><span class="n">gsub</span><span class="p">(</span><span class="sr">/(?:!\[(.*?)\]\((.*?)\))/</span><span class="p">,</span> <span class="s1">'<noscript><img src="\2"></noscript><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="\2" alt="\1" class="lazyload fade-in">'</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="n">post</span><span class="o">.</span><span class="n">content</span> <span class="o">=</span> <span class="n">newContent</span>
</span></span><span class="line"><span class="cl"> <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div>
<h3 id="avoids-an-excessive-dom-size"><a href="#avoids-an-excessive-dom-size" title="Permalink to Avoids an excessive DOM size">Avoids an excessive DOM size</a></h3><blockquote>
<p>Browser engineers recommend pages contain fewer than ~1,500 DOM nodes. The sweet spot is a tree depth < 32 elements and fewer than 60 children/parent element. A large DOM can increase memory usage, cause longer style calculations, and produce costly layout reflows.</p>
</blockquote>
<p>Trimming <code><div></code> fat where I could helped cut page weight down. Some hefty pages remain (I’m looking at your <a href="/paperfaces/">PaperFaces gallery</a>, but most fall under the 1,500 DOM node threshold. In the future I hope to fix this issue with a <a href="https://awesome-lewin-0d1356.netlify.com/grid-example/" rel="noopener">gallery pagination component</a> when switching to Gatsby.</p>
<h2 id="lighthouse-audits-comparison"><a href="#lighthouse-audits-comparison" title="Permalink to Lighthouse audits comparison">Lighthouse audits comparison</a></h2><figure>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/mm-lighthouse-audit-before_hud86f0c58c77f3aecbe9d2a3cf67122fc_23498_400x0_resize_box_3.png 400w, /images/mm-lighthouse-audit-before.png 716w"src="/images/mm-lighthouse-audit-before.png"alt="mademistakes.com’s audit results before applying optimizations." loading="lazy" decoding="async" width="716" height="318" /></p>
<figcaption><strong>Lighthouse</strong> audit results before optimizations.</figcaption>
</figure>
<figure>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/mm-lighthouse-audit-after_hu74d1b547d948fac1a7a08ca16c6a6206_21278_400x0_resize_box_3.png 400w, /images/mm-lighthouse-audit-after.png 716w"src="/images/mm-lighthouse-audit-after.png"alt="mademistakes.com’s audit results after applying optimizations." loading="lazy" decoding="async" width="716" height="316" /></p>
<figcaption><strong>Lighthouse</strong> audit results after optimizations.</figcaption>
</figure>
<p>Not sure why metrics like <em>Speed Index</em> increased. But <em>Time to Interactive</em>, <em>First Meaningful Paint</em>, and <em>First CPI Idle</em> all show improvements, so I guess that’s why the site went from a 79 in performance, to an 89.</p>
<h2 id="build-and-deploy"><a href="#build-and-deploy" title="Permalink to Build and deploy">Build and deploy</a></h2><p>And if all of this wasn’t enough, I also made the switch away from <a href="https://www.travis-ci.com/" rel="noopener"><strong>Travis CI</strong></a>, to <a href="https://www.netlify.com/" rel="noopener"><strong>Netlify</strong></a>. Instead of a 48 line <code>.travis.yml</code> file I now have a three line <code>netlify.toml</code>.</p>
<p>The Netlify builds are about 2-4 minutes faster than those with Travis CI. And look to be even quicker if I leverage undocumented <a href="https://www.contentful.com/blog/2018/05/17/faster-static-site-builds-part-one-process-only-what-you-need/#caching-for-the-win" rel="noopener">Netlify cache folders</a> that persists between builds. Using these to store the thousands of images I pipe through resizing tasks could shave another 6-8 minutes off my build.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>A jQuery plugin used to make footnotes less visually distracting. <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
</ol>
</div><p><a href="https://mademistakes.com/notes/autumn-refresh/?utm_source=atom_feed">Autumn refresh</a> was originally published on Made Mistakes.</p>Water tensionhttps://mademistakes.com/work/procreate-paintings/marie-m-portrait/2018-06-04T00:00:00-04:002018-06-04T00:00:00-04:00<p><img src="/images/marie-m-procreate.jpg" alt="" loading="lazy" decoding="async" width="1600" height="900"></p><p>Portrait of Marie M. inspired by a <a href="https://sktchy.com/FVAa3D" rel="noopener">photograph on Sktchy</a>. Digitally painted with Procreate on a 12.9″ iPad Pro.</p>
<p>Approximately 6 hours of work spread across a couple of weeks.</p>
<blockquote class="twitter-tweet" data-dnt="true"><p lang="en" dir="ltr">~6 hours of work spread across a couple of weeks, because who has time? <a href="https://t.co/faECZVYPSP">pic.twitter.com/faECZVYPSP</a></p>— 𝔐𝔦𝔠𝔥𝔞𝔢𝔩 ℜ𝔬𝔰𝔢 (@mmistakes) <a href="https://twitter.com/mmistakes/status/1003311926024294402?ref_src=twsrc%5Etfw">June 3, 2018</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p><strong>Tools used:</strong></p>
<ul>
<li><a href="https://www.apple.com/apple-pencil/" rel="noopener">Apple Pencil</a></li>
<li><a href="https://www.apple.com/ipad-pro/" rel="noopener">iPad Pro 12.9″ (2nd generation)</a></li>
<li><a href="https://procreate.art/" rel="noopener">Procreate</a></li>
</ul><p><a href="https://mademistakes.com/work/procreate-paintings/marie-m-portrait/?utm_source=atom_feed">Water tension</a> was originally published on Made Mistakes.</p>The two hour beardhttps://mademistakes.com/work/procreate-paintings/ale-b-portrait/2018-04-30T00:00:00-04:002018-04-30T16:22:39-04:00<p><img src="/images/ale-b-procreate.jpg" alt="" loading="lazy" decoding="async" width="1600" height="2133"></p><p>Portrait of Ale B. inspired by a <a href="https://sktchy.com/pGkOfH" rel="noopener">photograph on Sktchy</a>. Digitally painted with Procreate on a 12.9" iPad Pro.</p>
<p>Two hours and 30 minutes of beard scribbling over the last couple of weeks. Because who has time to relax draw in a single sitting?</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/ale-b-progress-1-lg_huc39177002097091ce35ef731fb4a1506_539176_400x0_resize_q75_box.jpg 400w, /images/ale-b-progress-1-lg_huc39177002097091ce35ef731fb4a1506_539176_800x0_resize_q75_box.jpg 800w, /images/ale-b-progress-1-lg.jpg 1600w" src="/images/ale-b-progress-1-lg_huc39177002097091ce35ef731fb4a1506_539176_800x0_resize_q75_box.jpg"
alt="work in progress screenshot" loading="lazy" decoding="async" width="1600" height="1103" style="background: #e4e0dc"/></p>
<blockquote class="twitter-tweet" data-dnt="true"><p lang="en" dir="ltr">Time-lapse export video via <a href="https://twitter.com/hashtag/Procreate?src=hash&ref_src=twsrc%5Etfw">#Procreate</a> <a href="https://twitter.com/hashtag/WIP?src=hash&ref_src=twsrc%5Etfw">#WIP</a> <a href="https://twitter.com/hashtag/Sktchy?src=hash&ref_src=twsrc%5Etfw">#Sktchy</a> <a href="https://t.co/uqMtzknWYp">pic.twitter.com/uqMtzknWYp</a></p>— 𝔐𝔦𝔠𝔥𝔞𝔢𝔩 ℜ𝔬𝔰𝔢 (@mmistakes) <a href="https://twitter.com/mmistakes/status/990746870380195840?ref_src=twsrc%5Etfw">April 30, 2018</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p><strong>Tools used:</strong></p>
<ul>
<li><a href="https://www.apple.com/apple-pencil/" rel="noopener">Apple Pencil</a></li>
<li><a href="https://www.apple.com/ipad-pro/" rel="noopener">iPad Pro 12.9" (2nd generation)</a></li>
<li><a href="https://procreate.art/" rel="noopener">Procreate</a></li>
</ul><p><a href="https://mademistakes.com/work/procreate-paintings/ale-b-portrait/?utm_source=atom_feed">The two hour beard</a> was originally published on Made Mistakes.</p>Turn awayhttps://mademistakes.com/work/procreate-paintings/tatha-s-portrait/2018-04-14T00:00:00-04:002018-04-14T14:27:36-04:00<p><img src="/images/tatha-s-procreate.jpg" alt="" loading="lazy" decoding="async" width="1600" height="2133"></p><p>Portrait of Tatha S. inspired by a <a href="https://sktchy.com/Eoee2D" rel="noopener">photograph on Sktchy</a>. Digitally painted with Procreate on a 12.9″ iPad Pro.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/tatha-s-progress-1-lg_hu41bdc3a0d913413f84e2bbbd7bf692a7_446979_400x0_resize_q75_box.jpg 400w, /images/tatha-s-progress-1-lg_hu41bdc3a0d913413f84e2bbbd7bf692a7_446979_800x0_resize_q75_box.jpg 800w, /images/tatha-s-progress-1-lg.jpg 1280w" src="/images/tatha-s-progress-1-lg_hu41bdc3a0d913413f84e2bbbd7bf692a7_446979_800x0_resize_q75_box.jpg"
alt="work in progress screenshot" loading="lazy" decoding="async" width="1280" height="1707" style="background: #d9d8d5"/></p>
<p>Using Apple Pencil to lay lines.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/tatha-s-progress-2-lg_hub72a0f0ea363ed6b3aa4b2f625d42681_228497_400x0_resize_q75_box.jpg 400w, /images/tatha-s-progress-2-lg_hub72a0f0ea363ed6b3aa4b2f625d42681_228497_800x0_resize_q75_box.jpg 800w, /images/tatha-s-progress-2-lg.jpg 1280w" src="/images/tatha-s-progress-2-lg_hub72a0f0ea363ed6b3aa4b2f625d42681_228497_800x0_resize_q75_box.jpg"
alt="work in progress screenshot" loading="lazy" decoding="async" width="1280" height="960" style="background: #b5b5b5"/></p>
<blockquote class="twitter-tweet" data-dnt="true"><p lang="en" dir="ltr">Procreate time-lapse export <a href="https://t.co/Xw5ozxENYI">pic.twitter.com/Xw5ozxENYI</a></p>— 𝔐𝔦𝔠𝔥𝔞𝔢𝔩 ℜ𝔬𝔰𝔢 (@mmistakes) <a href="https://twitter.com/mmistakes/status/984246043511607297?ref_src=twsrc%5Etfw">April 12, 2018</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p><strong>Tools used:</strong></p>
<ul>
<li><a href="https://www.apple.com/apple-pencil/" rel="noopener">Apple Pencil</a></li>
<li><a href="https://www.apple.com/ipad-pro/" rel="noopener">iPad Pro 12.9″ (2nd generation)</a></li>
<li><a href="https://procreate.art/" rel="noopener">Procreate</a></li>
</ul><p><a href="https://mademistakes.com/work/procreate-paintings/tatha-s-portrait/?utm_source=atom_feed">Turn away</a> was originally published on Made Mistakes.</p>Corpse paint your tearshttps://mademistakes.com/work/procreate-paintings/maya-z-portrait/2018-04-03T00:00:00-04:002018-04-03T00:00:00-04:00<p><img src="/images/maya-z-procreate.jpg" alt="" loading="lazy" decoding="async" width="1600" height="2133"></p><p>Portrait of Maya Z. inspired by a <a href="https://sktchy.com/efNbvC" rel="noopener">photograph on Sktchy</a>. Digitally painted with Procreate on a 12.9″ iPad Pro.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/maya-z-progress-1-lg_hu7f3f7c0efa2b8d7e86ae1e95909a6f0c_260752_400x0_resize_q75_box.jpg 400w, /images/maya-z-progress-1-lg_hu7f3f7c0efa2b8d7e86ae1e95909a6f0c_260752_800x0_resize_q75_box.jpg 800w, /images/maya-z-progress-1-lg.jpg 1280w" src="/images/maya-z-progress-1-lg_hu7f3f7c0efa2b8d7e86ae1e95909a6f0c_260752_800x0_resize_q75_box.jpg"
alt="work in progress screenshot" loading="lazy" decoding="async" width="1280" height="960" style="background: #484848"/></p>
<p>Slowly building up tones.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/maya-z-progress-2-lg_hua3e1b8e37b96e2240e61761f9b9501a2_229523_400x0_resize_q75_box.jpg 400w, /images/maya-z-progress-2-lg_hua3e1b8e37b96e2240e61761f9b9501a2_229523_800x0_resize_q75_box.jpg 800w, /images/maya-z-progress-2-lg.jpg 1280w" src="/images/maya-z-progress-2-lg_hua3e1b8e37b96e2240e61761f9b9501a2_229523_800x0_resize_q75_box.jpg"
alt="work in progress screenshot" loading="lazy" decoding="async" width="1280" height="960" style="background: #292929"/></p>
<blockquote class="twitter-tweet" data-dnt="true"><p lang="en" dir="ltr"><a href="https://twitter.com/hashtag/Procreate?src=hash&ref_src=twsrc%5Etfw">#Procreate</a> time-lapse export <a href="https://t.co/dSNE7fKHWb">pic.twitter.com/dSNE7fKHWb</a></p>— 𝔐𝔦𝔠𝔥𝔞𝔢𝔩 ℜ𝔬𝔰𝔢 (@mmistakes) <a href="https://twitter.com/mmistakes/status/980117629473083394?ref_src=twsrc%5Etfw">March 31, 2018</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p><strong>Tools used:</strong></p>
<ul>
<li><a href="https://www.apple.com/apple-pencil/" rel="noopener">Apple Pencil</a></li>
<li><a href="https://www.apple.com/ipad-pro/" rel="noopener">iPad Pro 12.9″ (2nd generation)</a></li>
<li><a href="https://procreate.art/" rel="noopener">Procreate</a></li>
</ul><p><a href="https://mademistakes.com/work/procreate-paintings/maya-z-portrait/?utm_source=atom_feed">Corpse paint your tears</a> was originally published on Made Mistakes.</p>