HugoNotes on Made MistakesShort, bite sized thoughts, inspirations, mistakes, and other minutia you'd find in a blog.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/notes/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>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>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>Configuring publishing sources for GitHub Pageshttps://mademistakes.com/notes/github-pages-publishing-source/2016-11-04T00:00:00-04:002019-02-15T15:56:02-05:00<p>You know what sucks? Maintaining two separate branches of an open source project, just to host its source code and documentation together with GitHub Pages.</p>
<p>For example, my Jekyll theme’s are setup on GitHub with the following branches:</p>
<ul>
<li><code>master</code> holds the theme files for cloning and installing</li>
<li><code>gh-pages</code> is a dupe of master with example posts and theme documentation</li>
</ul>
<p>Anytime I push updates to <code>master</code> I switch to the <code>gh-pages</code> branch, cherry pick commits, and deal with any conflicts. Wouldn’t it be nice if there was a better way to keep everything together in a single branch?</p>
<p>Well there is. Under <strong>Settings</strong> there is a drop-down menu under <strong>GitHub Pages/Source</strong> that allows you to choose where to build from<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/github-pages-options_hu0a22793e69bf55ea6da771320452a287_130804_400x0_resize_q75_box.jpg 400w, /images/github-pages-options_hu0a22793e69bf55ea6da771320452a287_130804_800x0_resize_q75_box.jpg 800w, /images/github-pages-options.jpg 904w" src="/images/github-pages-options_hu0a22793e69bf55ea6da771320452a287_130804_800x0_resize_q75_box.jpg"
alt="GitHub Pages Sources" loading="lazy" decoding="async" width="904" height="760" style="background: #eeefee"/></p>
<p>Flip the source to <strong>master branch /docs folder</strong>, move contents of <code>gh-pages</code> into <strong>/docs</strong> on <code>master</code>, and boom!</p>
<p>Source code and documentation in a single branch. No more switching branches and “cherry picking” for me.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>You can <a href="https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site" rel="noopener">configure GitHub Pages</a> to publish your site’s source files from <code>master</code>, <code>gh-pages</code>, or a <code>/docs</code> folder on your <code>master</code> branch for Project Pages and other Pages sites that meet certain criteria. <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
</ol>
</div><p><a href="https://mademistakes.com/notes/github-pages-publishing-source/?utm_source=atom_feed">Configuring publishing sources for GitHub Pages</a> was originally published on Made Mistakes.</p>Oil pastels are not pencilshttps://mademistakes.com/notes/oil-pastels/2016-10-27T00:00:00-04:002019-02-15T15:55:53-05:00<p><img src="/images/oil-pastel-sunset-finished.jpg" alt="" loading="lazy" decoding="async" width="1600" height="1103"></p><p>Not to overstate the obvious here, but oil pastels do not behave like colored pencils.</p>
<p>Their size and shape should have been enough to clue me into that fact, but I proceeded to use them like a pencil anyways. Punished severely for this ignorance I was.</p>
<p>Made a slight recovery when I began using the tool like I would with oil paint. <em>Globing on specks of color, and letting them mix with each other naturally.</em> They don’t quite have the mixing quality of paint, but with a finger or blending stumps pigment can be pulled about.</p>
<p>Attempting to create form with precise strokes is totally not the way to approach oil pastels.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/oil-pastels-process-1_hue58f2e100ab3fe1bf7d884bb0908afc5_628244_400x0_resize_q75_box.jpg 400w, /images/oil-pastels-process-1_hue58f2e100ab3fe1bf7d884bb0908afc5_628244_800x0_resize_q75_box.jpg 800w, /images/oil-pastels-process-1.jpg 1600w" src="/images/oil-pastels-process-1_hue58f2e100ab3fe1bf7d884bb0908afc5_628244_800x0_resize_q75_box.jpg"
alt="Preliminary sketch and taped off paper" loading="lazy" decoding="async" width="1600" height="1200" style="background: #c8c9c8"/></p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/oil-pastels-process-2_hue0cc516a6c98f23bd71fd9518a49e6d0_659673_400x0_resize_q75_box.jpg 400w, /images/oil-pastels-process-2_hue0cc516a6c98f23bd71fd9518a49e6d0_659673_800x0_resize_q75_box.jpg 800w, /images/oil-pastels-process-2.jpg 1600w" src="/images/oil-pastels-process-2_hue0cc516a6c98f23bd71fd9518a49e6d0_659673_800x0_resize_q75_box.jpg"
alt="Sketching out the sky" loading="lazy" decoding="async" width="1600" height="1200" style="background: #dddede"/></p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/oil-pastels-process-3_hub0a9ccfb636c00c4dfd5a014f783e99d_751218_400x0_resize_q75_box.jpg 400w, /images/oil-pastels-process-3_hub0a9ccfb636c00c4dfd5a014f783e99d_751218_800x0_resize_q75_box.jpg 800w, /images/oil-pastels-process-3.jpg 1600w" src="/images/oil-pastels-process-3_hub0a9ccfb636c00c4dfd5a014f783e99d_751218_800x0_resize_q75_box.jpg"
alt="The finished piece before removing the mask" loading="lazy" decoding="async" width="1600" height="1200" style="background: #324242"/></p>
<h2 id="tools-used"><a href="#tools-used" title="Permalink to Tools Used">Tools Used</a></h2><ul>
<li><a href="https://www.amazon.com/Pentel-Arts-Pastels-Count-PHN-25/dp/B01HGYIAT0/ref=as_li_ss_tl?ie=UTF8&qid=1477597424&sr=8-6&keywords=pentel+oil+pastels&linkCode=ll1&tag=2rosebuds-20&linkId=51263f8c62514e85fd039ffcb86934e6" rel="noopener">Pentel Arts Oil Pastels, 25 Count</a></li>
<li><a href="https://www.amazon.com/Jack-Richeson-Blending-Stomp-8-Inch/dp/B001BYRK1Q/ref=as_li_ss_tl?ie=UTF8&qid=1477597624&sr=8-6&keywords=blending+stumps&linkCode=ll1&tag=2rosebuds-20&linkId=aaaff9e80069ada137c46e2ced713eb5" rel="noopener">Jack Richeson Blending Stomp, 3/8″</a></li>
</ul><p><a href="https://mademistakes.com/notes/oil-pastels/?utm_source=atom_feed">Oil pastels are not pencils</a> was originally published on Made Mistakes.</p>CSS blur effecthttps://mademistakes.com/notes/css-blur-effect/2016-10-25T00:00:00-04:002016-11-04T16:13:05-04:00<p>Playing with the new <strong>Depth Effect</strong> (aka <a href="https://www.instagram.com/explore/tags/fauxkeh/" rel="noopener"><em>fauxkeh</em></a>) on my iPhone 7 Plus got me thinking about Gaussian blurs, and how to improve their use on this site.</p>
<p>In this <a href="https://github.com/mmistakes/made-mistakes-jekyll/tree/11.0.0" rel="noopener">last redesign</a>, I applied a blurred effect to images in a few key locations<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. To keep page speed in check I shrunk images down to <code>~20px</code> wide, optimized them with <a href="https://github.com/imagemin/imagemin" rel="noopener"><strong>imagemin</strong></a>, and then scaled them up with <code>background-size: cover</code> to fill their parent containers.</p>
<p>The browser does the rest, enlarging and smoothing out these tiny images — creating a blurred effect of sorts. For the most part they look good. On devices that display <code>@2x</code> or higher, blotchy patterns and artifacts begin to show their ugly faces.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/mm-upscaled-image-blur-ipad_hu3a005774cd037018b1ebddb7eb2c656d_46291_400x0_resize_q75_box.jpg 400w, /images/mm-upscaled-image-blur-ipad_hu3a005774cd037018b1ebddb7eb2c656d_46291_800x0_resize_q75_box.jpg 800w, /images/mm-upscaled-image-blur-ipad.jpg 1600w" src="/images/mm-upscaled-image-blur-ipad_hu3a005774cd037018b1ebddb7eb2c656d_46291_800x0_resize_q75_box.jpg"
alt="ugly upscaled images on a high resolution display" loading="lazy" decoding="async" width="1600" height="422" style="background: #2e2d2d"/></p>
<p>This is where <a href="https://css-tricks.com/almanac/properties/f/filter/" rel="noopener">CSS filter functions</a> like <code>blur</code> come into play. By adding a single line to my stylesheet I was able to smoothen out these images just enough to overcome the ugly.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="cl">.teaser__bg {
</span></span><span class="line"><span class="cl"> position: absolute;
</span></span><span class="line"><span class="cl"> top: 0;
</span></span><span class="line"><span class="cl"> right: 0;
</span></span><span class="line"><span class="cl"> bottom: 0;
</span></span><span class="line"><span class="cl"> left: 0;
</span></span><span class="line"><span class="cl"><span class="gi">+ filter: blur(15px);
</span></span></span><span class="line"><span class="cl"><span class="gi"></span> background-repeat: no-repeat;
</span></span><span class="line"><span class="cl"> background-size: cover;
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></div>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/mm-image-filter-blur-ipad-1124_hu8e4a88257c1f5b3ac5de23b35befed32_43664_400x0_resize_q75_box.jpg 400w, /images/mm-image-filter-blur-ipad-1124_hu8e4a88257c1f5b3ac5de23b35befed32_43664_800x0_resize_q75_box.jpg 800w, /images/mm-image-filter-blur-ipad-1124.jpg 1124w" src="/images/mm-image-filter-blur-ipad-1124_hu8e4a88257c1f5b3ac5de23b35befed32_43664_800x0_resize_q75_box.jpg"
alt="blur filtered background images" loading="lazy" decoding="async" width="1124" height="367" style="background: #2e2d2d"/></p>
<p>The best part, browser <a href="https://caniuse.com/#search=filter" rel="noopener">support for CSS filter effects</a> is quite good.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><strong>Cover images</strong> with a striped pattern overlay. <strong>Background images</strong> in the post pagination links found at the bottom of each page. <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
</ol>
</div><p><a href="https://mademistakes.com/notes/css-blur-effect/?utm_source=atom_feed">CSS blur effect</a> was originally published on Made Mistakes.</p>Using SSI to detect cookieshttps://mademistakes.com/notes/using-ssi/2016-10-24T00:00:00-04:002019-11-05T14:50:51-05:00<p>In my never ending quest to micro-optimize the hell out of my site, I ran into a snag when trying to use SSI directives.</p>
<p><a href="https://github.com/mmistakes/made-mistakes-jekyll/tree/10.3.0" rel="noopener">Version 10.2</a> of this site was my half-baked attempt at <a href="/articles/using-jekyll-2016/#critical-path-css">eliminating render-blocking CSS</a> to speed up page loads. By manually inlining critical CSS via a <a href="http://jekyllrb.com/docs/templates/#includes" rel="noopener">Jekyll include</a> and using <a href="https://github.com/filamentgroup/loadCSS" rel="noopener"><strong>loadCSS</strong></a> to asynchronously load the rest — I did pretty good.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/mm-home-pagespeed-021116_hue25c830546fd8638fd0c1563e554a0f3_77452_400x0_resize_q75_box.jpg 400w, /images/mm-home-pagespeed-021116_hue25c830546fd8638fd0c1563e554a0f3_77452_800x0_resize_q75_box.jpg 800w, /images/mm-home-pagespeed-021116.jpg 1152w" src="/images/mm-home-pagespeed-021116_hue25c830546fd8638fd0c1563e554a0f3_77452_800x0_resize_q75_box.jpg"
alt="Made Mistakes version 10 analyzed with Google’s PageSpeed Insights tool" loading="lazy" decoding="async" width="1152" height="653" style="background: #fafafa"/></p>
<p>This workflow wasn’t ideal for a variety of reasons:</p>
<ol>
<li>Manual process.</li>
<li>Need to maintain separate “critical” stylesheets for inlining.</li>
<li>Included a bunch of declarations that aren’t critical to rendering “above the fold” content — causing some file size bloat.</li>
</ol>
<p>So with the help of <a href="https://github.com/addyosmani/critical" rel="noopener"><strong>Critical</strong></a> (and friends) I attempted to automated the process. Getting it working within the constraints of a Jekyll site with thousands of posts wasn’t easy, but I got close with <a href="https://github.com/mmistakes/made-mistakes-jekyll/tree/master/gulp/tasks" rel="noopener">a set of Gulp tasks</a>. A tale for another day unfortunately…</p>
<p>Sorry a little off topic there, back to SSI directives.</p>
<p>I learned that to speed up things for repeat visitors, loading a cached version of the CSS instead of waiting for <strong>loadCSS</strong> to do its thing was preferred. By using Filament Group’s aptly named <a href="https://github.com/filamentgroup/enhance" rel="noopener"><strong>Enhance.js</strong></a> project as a boilerplate, this could be achieved by dropping a cookie and using SSI directives.</p>
<p>Structuring our HTML looks a little like like 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="c"><!--#if expr="$HTTP_COOKIE=/fullcss\=true/" --></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/stylesheets/style.css"</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="c"><!--#else--></span>
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">style</span><span class="p">></span>
</span></span><span class="line"><span class="cl"> <span class="c">/* critical path CSS styles go here... */</span>
</span></span><span class="line"><span class="cl"> <span class="p"></</span><span class="nt">style</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="c"><!--#endif--></span>
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">noscript</span><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/stylesheets/style.css"</span><span class="p">></</span><span class="nt">noscript</span><span class="p">></span></span></span></code></pre></div>
<p>The <code>#if</code> and <code>#else</code> conditionals are SSI directives used by Apache to do some neat things. In this context they determine if a cookie named <code>fullcss</code> has been set. If it has, cached CSS files will load using a standard <code><link></code> element. If it hasn’t, the inline CSS will be rendered by the browser instead.</p>
<p>For first time visitors:</p>
<ol>
<li>Critical CSS inlined within the <code><style></code> element will load almost instantly.</li>
<li><strong>loadCSS</strong> script will asynchronously load the remaining page CSS as not to block rendering.</li>
<li>A cookie will be set to trigger the loading of cached CSS on future page loads.</li>
</ol>
<p>After setting all this up and testing my pages against <a href="https://www.webpagetest.org/" rel="noopener"><strong>WebPagetest</strong></a>, <a href="https://developers.google.com/speed/pagespeed/insights/" rel="noopener"><strong>PageSpeed Insights</strong></a>, and <a href="https://gtmetrix.com/" rel="noopener"><strong>GTmetrix</strong></a> I saw an obvious drop in scrores. Apparently the SSI directives weren’t working as intended, causing <code>style.css</code> to render block each page load. Hmmmm…</p>
<p>Oh right, maybe it’s Cloudflare’s <strong>Auto Minifying</strong> setting mucking around! Sure enough, as soon I disabled their HTML minifier, lines like <code><!--#if expr="$HTTP_COOKIE=/fullcss\=true/" --></code> remained untouched. Unfortunately <code>style.css</code> was still render blocking the page.</p>
<figure>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/cloudflare-auto-minify_hu187f673b65dadaac0260a957ca307cc2_26904_400x0_resize_q75_box.jpg 400w, /images/cloudflare-auto-minify_hu187f673b65dadaac0260a957ca307cc2_26904_800x0_resize_q75_box.jpg 800w, /images/cloudflare-auto-minify.jpg 999w" src="/images/cloudflare-auto-minify_hu187f673b65dadaac0260a957ca307cc2_26904_800x0_resize_q75_box.jpg"
alt="Screenshot of Cloudflare’s Auto Minify settings." loading="lazy" decoding="async" width="999" height="263" style="background: #f8f8f8"/></p>
<figcaption>Auto Minify removes unnecessary characters from your source code (like extraneous whitespace and comments).</figcaption>
</figure>
<p>Dug a little deeper and discovered you have to configure your server to <a href="http://httpd.apache.org/docs/current/howto/ssi.html#configuring" rel="noopener">permit SSI</a> before they’ll be recognized. Oops! Dropped these two lines in my <code>.htaccess</code> file and everything magically worked.</p>
<div class="highlight"><pre class="chroma" tabindex="0"><code class="language-" data-lang="">Options +Includes
AddHandler server-parsed .shtml .html .htm</code></pre></div>
<figure>
<p>![Screenshot of Google PageSpeed Insights score results.(../../images/pagespeed-insights-99-100.jpg)</p>
<figcaption>So close to 100. If it wasn’t for the Google Analytics and AdSense scripts…</figcaption>
</figure><p><a href="https://mademistakes.com/notes/using-ssi/?utm_source=atom_feed">Using SSI to detect cookies</a> was originally published on Made Mistakes.</p>Inktober failurehttps://mademistakes.com/notes/inktober-2016-failure/2016-10-17T00:00:00-04:002019-12-28T19:28:12-05:00<p><img src="/images/inktober-2016-wash-portrait-process-1.jpg" alt="" loading="lazy" decoding="async" width="1600" height="1000"></p><p>Similar to previous years, I started out with the best of intentions for <a href="http://mrjakeparker.com/inktober" rel="noopener"><strong>Inktober</strong></a> and then quickly lose steam…</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/inktober-2016-wash-portrait_hu62e276c1e1671ef5846e8f3edd9cff59_902604_400x0_resize_q75_box.jpg 400w, /images/inktober-2016-wash-portrait_hu62e276c1e1671ef5846e8f3edd9cff59_902604_800x0_resize_q75_box.jpg 800w, /images/inktober-2016-wash-portrait.jpg 1600w" src="/images/inktober-2016-wash-portrait_hu62e276c1e1671ef5846e8f3edd9cff59_902604_800x0_resize_q75_box.jpg"
alt="Girl in sunglasses ink wash portrait" loading="lazy" decoding="async" width="1600" height="1958" style="background: #2a2a25"/></p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/inktober-2016-wash-portrait-process-2_huec448689d1dea0c7129454420967799a_318483_400x0_resize_q75_box.jpg 400w, /images/inktober-2016-wash-portrait-process-2_huec448689d1dea0c7129454420967799a_318483_800x0_resize_q75_box.jpg 800w, /images/inktober-2016-wash-portrait-process-2.jpg 1600w" src="/images/inktober-2016-wash-portrait-process-2_huec448689d1dea0c7129454420967799a_318483_800x0_resize_q75_box.jpg"
alt="Detail of nose and mouth ink wash portrait" loading="lazy" decoding="async" width="1600" height="1000" style="background: #272721"/></p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/inktober-2016-wash-portrait-process-3_hu87d0299ced6908ca77692889fc327a29_434106_400x0_resize_q75_box.jpg 400w, /images/inktober-2016-wash-portrait-process-3_hu87d0299ced6908ca77692889fc327a29_434106_800x0_resize_q75_box.jpg 800w, /images/inktober-2016-wash-portrait-process-3.jpg 1600w" src="/images/inktober-2016-wash-portrait-process-3_hu87d0299ced6908ca77692889fc327a29_434106_800x0_resize_q75_box.jpg"
alt="Ink wash and blue masking tape detail" loading="lazy" decoding="async" width="1600" height="1000" style="background: #363633"/></p>
<p>After taking a few days to complete a single ink wash — I was spent. Working digitally has spoiled me to the point that sitting at a desk instead of the sofa, cleaning brushes, and dealing with messes is just too much work to bother with.</p>
<p>I did manage to record a time-lapse video during hour 2 of the painting. Unfortunately I mistakenly put the iPhone 7 Plus in <strong>SLO-MO</strong> mode by mistake for the first half. Really surprised there was enough free space to record an hour of 1080p video…</p>
<p>1 hour time-lapse condensed down into ~20 seconds.</p>
<lite-youtube videoid="gOBj8HdfA2Y" playlabel="YouTube Video">
<a href="https://youtube.com/watch?v=gOBj8HdfA2Y" class="lty-playbtn" title="YouTube Video">
<span class="visually-hidden">YouTube Video</span>
</a>
</lite-youtube>
<p>October is half over and I suppose there’s still time for me to knock out another portrait or two. If past years are any indication there’s not a good chance of that happening. 😐</p>
<p><strong>Inktober pieces from the past:</strong></p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/inktober-past-1_hudf9c487227f626ced9d8aa80267f79d3_983629_400x0_resize_q75_box.jpg 400w, /images/inktober-past-1_hudf9c487227f626ced9d8aa80267f79d3_983629_800x0_resize_q75_box.jpg 800w, /images/inktober-past-1.jpg 1600w" src="/images/inktober-past-1_hudf9c487227f626ced9d8aa80267f79d3_983629_800x0_resize_q75_box.jpg"
alt="Blind contour drawing of a figure in a robe" loading="lazy" decoding="async" width="1600" height="1163" style="background: #d8d6d2"/></p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/inktober-past-2_hu9ccc3135c5e580639fadf6e95e11803a_728142_400x0_resize_q75_box.jpg 400w, /images/inktober-past-2_hu9ccc3135c5e580639fadf6e95e11803a_728142_800x0_resize_q75_box.jpg 800w, /images/inktober-past-2.jpg 1600w" src="/images/inktober-past-2_hu9ccc3135c5e580639fadf6e95e11803a_728142_800x0_resize_q75_box.jpg"
alt="Grid paper ink sketches of females" loading="lazy" decoding="async" width="1600" height="1163" style="background: #dedadc"/></p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/inktober-past-3_hu41ea2418551636167eda4545d684a63b_625880_400x0_resize_q75_box.jpg 400w, /images/inktober-past-3_hu41ea2418551636167eda4545d684a63b_625880_800x0_resize_q75_box.jpg 800w, /images/inktober-past-3.jpg 1600w" src="/images/inktober-past-3_hu41ea2418551636167eda4545d684a63b_625880_800x0_resize_q75_box.jpg"
alt="Blind contour ink sketch of a nude woman" loading="lazy" decoding="async" width="1600" height="1163" style="background: #dcdcdc"/></p><p><a href="https://mademistakes.com/notes/inktober-2016-failure/?utm_source=atom_feed">Inktober failure</a> was originally published on Made Mistakes.</p>How to glitch imageshttps://mademistakes.com/notes/glitching-images/2016-08-26T00:00:00-04:002018-01-19T09:24:59-05:00<p>To create profile images that look like they’ve been run through a television set with poor reception, I use <a href="https://www.rgb.nu/decim8" rel="noopener"><strong>DECIM8</strong></a>. It’s available as both an iOS and Android app.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/glitched-examples_huc4d500496fb7c333769af2f463effb25_255214_400x0_resize_q75_box.jpg 400w, /images/glitched-examples_huc4d500496fb7c333769af2f463effb25_255214_800x0_resize_q75_box.jpg 800w, /images/glitched-examples.jpg 1100w" src="/images/glitched-examples_huc4d500496fb7c333769af2f463effb25_255214_800x0_resize_q75_box.jpg"
alt="glitched image examples" loading="lazy" decoding="async" width="1100" height="460" style="background: #09090a"/></p>
<p>There are also free <em>glitch generators</em> available online that you run in-browser. Simply upload the image you want to glitch and play around with the settings to achieve the desired result.</p>
<ul>
<li><a href="http://www.airtightinteractive.com/demos/js/imageglitcher/" rel="noopener">ImageGlitcher</a></li>
<li><a href="https://snorpey.github.io/jpg-glitch/" rel="noopener">Glitch Images</a></li>
<li><a href="http://www.errozero.co.uk/glitchatron/" rel="noopener">GLITCHATRON</a></li>
</ul><p><a href="https://mademistakes.com/notes/glitching-images/?utm_source=atom_feed">How to glitch images</a> was originally published on Made Mistakes.</p>How to make time-lapse videos on iPadhttps://mademistakes.com/notes/time-lapse-videos/2016-08-26T00:00:00-04:002021-07-26T11:13:21-04:00<p><img src="/images/time-lapse-iphone-setup.jpg" alt="" loading="lazy" decoding="async" width="1600" height="1067"></p><p>Most of my iPad time-lapse videos were made using <a href="https://www.airsquirrels.com/reflector" rel="noopener"><strong>Reflector</strong></a><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> to record the screen. With editing done later to adjust timing, include music, and add overlays in <strong>Adobe AfterEffects</strong>.</p>
<h2 id="screen-recording"><a href="#screen-recording" title="Permalink to Screen recording">Screen recording</a></h2><p>Apple has built screen recording directly into iPadOS and iOS, allowing you to <a href="https://support.apple.com/en-us/HT207935" rel="noopener">capture the screen</a> directly on device without needing any other software or hardware.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/ipados-screen-recording_hu9af291e36820e396e2d7ccc3b2428c34_2082594_400x0_resize_q75_box.jpg 400w, /images/ipados-screen-recording_hu9af291e36820e396e2d7ccc3b2428c34_2082594_800x0_resize_q75_box.jpg 800w, /images/ipados-screen-recording.jpg 2732w" src="/images/ipados-screen-recording_hu9af291e36820e396e2d7ccc3b2428c34_2082594_800x0_resize_q75_box.jpg"
alt="Screen recording settings screen on iPadOS" loading="lazy" decoding="async" width="2732" height="1566" style="background: #343333"/></p>
<p>You can also <a href="https://support.apple.com/guide/quicktime-player/record-a-movie-qtp356b55534/10.5/mac/11.0#apd86177808b0da4" rel="noopener">capture the iPad’s screen on a Mac</a> using the <strong>QuickTime Player</strong> app by selecting the <strong>New Movie Recording</strong> option from the File menu.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/quicktime-player-new-recording-menu_hucd4ef092208b19f7a0042cab3471464b_100946_400x0_resize_box_3.png 400w, /images/quicktime-player-new-recording-menu.png 756w"src="/images/quicktime-player-new-recording-menu.png"alt="New recording menu option in QuickTime Player on macOS" loading="lazy" decoding="async" width="756" height="92" /></p>
<p>Then changing from the built-in FaceTime camera to an iPad or iPhone connected to your Mac via Lightning cable.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/quicktime-player-camera-setting_hu75bb3c26671bdee51ae83a4769406be6_291551_400x0_resize_box_3.png 400w, /images/quicktime-player-camera-setting_hu75bb3c26671bdee51ae83a4769406be6_291551_800x0_resize_box_3.png 800w, /images/quicktime-player-camera-setting.png 873w" src="/images/quicktime-player-camera-setting_hu75bb3c26671bdee51ae83a4769406be6_291551_800x0_resize_box_3.png"
alt="Selecting iPad Pro as camera source in QuickTime Player on macOS" loading="lazy" decoding="async" width="873" height="296" /></p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/quicktime-movie-ipad-recording_hucf657da75127733b8808ca50816ad7f7_257317_400x0_resize_q75_box.jpg 400w, /images/quicktime-movie-ipad-recording_hucf657da75127733b8808ca50816ad7f7_257317_800x0_resize_q75_box.jpg 800w, /images/quicktime-movie-ipad-recording.jpg 1920w" src="/images/quicktime-movie-ipad-recording_hucf657da75127733b8808ca50816ad7f7_257317_800x0_resize_q75_box.jpg"
alt="Recording iPad’s screen on macOS with QuickTime Player for macOS" loading="lazy" decoding="async" width="1920" height="1080" style="background: #e4e0e1"/></p>
<h2 id="slideshow-time-lapse"><a href="#slideshow-time-lapse" title="Permalink to Slideshow time-lapse">Slideshow time-lapse</a></h2><p>My earlier attempts were more slideshow<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> than time-lapse. I’d manually take a screenshot<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> of the screen every few seconds, bring import into the Photos app (previouly iPhoto), then create selected a <strong>Create Slideshow</strong> from the File menu to convert hundreds of images into a QuickTime movie.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/create-slideshow-photos-macos_hu5d4d4af13a53f9dee6df2227e73445ae_270228_400x0_resize_q75_box.jpg 400w, /images/create-slideshow-photos-macos_hu5d4d4af13a53f9dee6df2227e73445ae_270228_800x0_resize_q75_box.jpg 800w, /images/create-slideshow-photos-macos.jpg 1479w" src="/images/create-slideshow-photos-macos_hu5d4d4af13a53f9dee6df2227e73445ae_270228_800x0_resize_q75_box.jpg"
alt="Create a new slideshow in the Photos app on macOS" loading="lazy" decoding="async" width="1479" height="1026" style="background: #e2e0df"/></p>
<p>These movies weren’t the greatest since you couldn’t see any of the physical marks happening. But still neat to see a drawings progression in a low tech kind of way.</p>
<p>Apps like <a href="https://procreate.art/" rel="noopener"><strong>Procreate</strong></a> have taken this a step further by integrating time-lapse recording into their software. Each stroke, transform, and edit are recorded and able to view and export at any time.</p>
<p><img sizes="(min-width: 1440px) 690px, (min-width: 1024px) 790px, 94vw"
srcset="/images/procreate-time-lapse-recording-settings_hu31069268dfd4e393e117f0df24e886b7_140800_400x0_resize_q75_box.jpg 400w, /images/procreate-time-lapse-recording-settings_hu31069268dfd4e393e117f0df24e886b7_140800_800x0_resize_q75_box.jpg 800w, /images/procreate-time-lapse-recording-settings.jpg 1680w" src="/images/procreate-time-lapse-recording-settings_hu31069268dfd4e393e117f0df24e886b7_140800_800x0_resize_q75_box.jpg"
alt="View of Procreate’s time-lapse recording action menu on top of a black and white painting of a woman in the bath." loading="lazy" decoding="async" width="1680" height="1259" style="background: #2b2b2b"/></p>
<h2 id="traditional-time-lapse"><a href="#traditional-time-lapse" title="Permalink to Traditional time-lapse">Traditional time-lapse</a></h2><p>My preferred method of creating iPad time-lapses would be to record off device at select intervals. Meaning, having a setup where I’m able to capture myself, the work area, and the iPad all in view at once. There’s so much more value there since you can see fingers or styli interacting with the iPad.</p>
<p>I’ve experimented with <em>traditional</em> time-lapse photography using the following tools to record a drawing session:</p>
<ul>
<li><a href="https://amzn.to/357H76a" rel="noopener">Reflector arm stand</a></li>
<li><a href="https://amzn.to/3vcYMUG" rel="noopener">Clamps</a></li>
<li>iPhone</li>
<li><a href="http://www.lapseit.com/" rel="noopener"><strong>Lapse It</strong></a> app</li>
</ul>
<lite-youtube videoid="K2dgaV9_rCI" playlabel="YouTube Video">
<a href="https://youtube.com/watch?v=K2dgaV9_rCI" class="lty-playbtn" title="YouTube Video">
<span class="visually-hidden">YouTube Video</span>
</a>
</lite-youtube>
<p>I’ve had mixed results with the <a href="https://www.youtube.com/watch?v=JqVzqVG0e5g&index=8&list=PLaLqP2ipMLc6UugVLyTwWTiFtmmZzj7ao" rel="noopener">final videos</a> as removing glare from the iPad’s screen is a real challenge. If my work environment was more controlled and I had a studio lighting setup, the results would be more impressive I’m sure.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://www.youtube.com/watch?v=SU3kYxJmWuQ" rel="noopener">https://www.youtube.com/watch?v=SU3kYxJmWuQ</a> <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
<li id="fn:2">
<p><a href="https://www.youtube.com/watch?v=NqcGVymOiPo" rel="noopener">https://www.youtube.com/watch?v=NqcGVymOiPo</a> <a href="#fnref:2" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
<li id="fn:3">
<p>To take a screenshot, press iPad’s power and volume up buttons at the same time. For older models with a home button, press the iPad’s power and home buttons at the same time. If done correctly a thumbnail image will appear on screen after a sound of the camera’s shutter closing plays. <a href="#fnref:3" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
</ol>
</div><p><a href="https://mademistakes.com/notes/time-lapse-videos/?utm_source=atom_feed">How to make time-lapse videos on iPad</a> was originally published on Made Mistakes.</p>