{
	"feed_url": "https://www.lukebennett.dev//feed.json",
	"home_page_url": "https://www.lukebennett.dev/",
	"icon": "https://www.lukebennett.dev//apple-icon.png",
	"items": [
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>Recently, I’ve been working on a client project built as a single-page application (SPA). Unlike server-rendered apps (where I’ve spent the majority of my time over the past few years), all the data transformations in this app happen client-side. This makes performance something we need to consider much more carefully.</p><p>The app fetches data from an API that uses <a href=\"https://opis.io/json-schema/2.x/pointers.html\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">JSON pointers</a> to reduce the size of the payload. The data is then validated (using a runtime schema validator), transformed into a more usable shape, and plotted on a directed graph to form a tree structure.</p><p>Early on, we realised that minimising data passes and avoiding unnecessary memory allocations (like creating intermediate arrays) would be important for maintaining performance, particularly for users working with larger datasets.</p><p>In the past, I rarely used loops like <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">for</code> or <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">for...of</code> after learning them. The conventional wisdom in the JavaScript community seemed to be to favour immutable data structures and functional programming styles — chaining methods like <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">filter</code> and <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">map</code>. However, I’d noticed performance-conscious projects often favoured loops, and I’d read about how they can be faster and more memory-efficient. This inspired me to experiment with them more on this project.</p><p>I quickly realised that (in most cases) loops allowed me to do more work in a single pass without sacrificing readability or maintainability. While chaining array methods is often concise, I’ve found loops strike a nice balance between clarity and efficiency — especially when TypeScript introduces friction with array methods, as I’ll demonstrate in the examples below.</p><p>This isn’t to say there’s anything wrong with array methods — in most cases, they’re perfectly fine, and their method names often make the code’s intent clear, especially for simpler tasks. But for more complex transformations or when performance really matters, I’ve been gravitating towards loops lately.</p><p>Let’s compare three ways to write a <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">makeDisplayName</code> function: one that chains array methods, one using <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">reduce</code>, and another with a <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">for...of</code> loop. The function takes multiple arguments representing the parts of a name, filters out invalid values, trims the remaining strings, and concatenates them with a space:</p><div><pre class=\"shiki poimandres\" style=\"background-color:#1b1e28;color:#a6accd\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#E4F0FBD0\">makeDisplayName</span><span style=\"color:#A6ACCD\">(</span><span style=\"color:#D0679D\">null</span><span style=\"color:#A6ACCD\">, </span><span style=\"color:#A6ACCD\">'</span><span style=\"color:#5DE4C7\">Maynard</span><span style=\"color:#A6ACCD\">'</span><span style=\"color:#A6ACCD\">, </span><span style=\"color:#D0679D\">undefined</span><span style=\"color:#A6ACCD\">, </span><span style=\"color:#A6ACCD\">'</span><span style=\"color:#5DE4C7\">James</span><span style=\"color:#A6ACCD\">'</span><span style=\"color:#A6ACCD\">, </span><span style=\"color:#D0679D\">false</span><span style=\"color:#91B4D5\"> &#x26;&#x26;</span><span style=\"color:#A6ACCD\"> '</span><span style=\"color:#5DE4C7\">Bob</span><span style=\"color:#A6ACCD\">'</span><span style=\"color:#A6ACCD\">, </span><span style=\"color:#A6ACCD\">'</span><span style=\"color:#5DE4C7\">Keenan</span><span style=\"color:#A6ACCD\">'</span><span style=\"color:#A6ACCD\">, </span><span style=\"color:#A6ACCD\">''</span><span style=\"color:#A6ACCD\">);</span></span>\n<span class=\"line\"><span style=\"color:#767C9DB0;font-style:italic\">// Returns 'Maynard James Keenan'</span></span></code></pre></div><p>This function is unlikely to have performance issues in practice, but it’s complex enough to showcase the trade-offs you might encounter when choosing between different approaches.</p><h2 class=\"text-balance\" id=\"chained-array-methods\">Chained array methods</h2><div><pre class=\"shiki poimandres\" style=\"background-color:#1b1e28;color:#a6accd\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#91B4D5\">function</span><span style=\"color:#ADD7FF\"> makeDisplayName</span><span style=\"color:#A6ACCD\">(</span><span style=\"color:#91B4D5\">...</span><span style=\"color:#E4F0FB\">parts</span><span style=\"color:#91B4D5\">:</span><span style=\"color:#A6ACCDC0\"> Array</span><span style=\"color:#A6ACCD\">&#x3C;</span><span style=\"color:#A6ACCDC0\">string</span><span style=\"color:#91B4D5\"> |</span><span style=\"color:#A6ACCDC0\"> undefined</span><span style=\"color:#91B4D5\"> |</span><span style=\"color:#A6ACCDC0\"> null</span><span style=\"color:#91B4D5\"> |</span><span style=\"color:#A6ACCDC0\"> false</span><span style=\"color:#A6ACCD\">>)</span><span style=\"color:#91B4D5\">:</span><span style=\"color:#A6ACCDC0\"> string</span><span style=\"color:#A6ACCD\"> {</span></span>\n<span class=\"line\"><span style=\"color:#767C9DB0;font-style:italic\">\t// Filter, trim, and join valid parts</span></span>\n<span class=\"line\"><span style=\"color:#5DE4C7C0\">\treturn</span><span style=\"color:#A6ACCD\"> (</span></span>\n<span class=\"line\"><span style=\"color:#E4F0FB\">\t\tparts</span></span>\n<span class=\"line\"><span style=\"color:#767C9DB0;font-style:italic\">\t\t\t// Filter out invalid parts (non-strings or falsy values)</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">\t\t\t.</span><span style=\"color:#E4F0FBD0\">filter</span><span style=\"color:#A6ACCD\">(</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">\t\t\t\t(</span><span style=\"color:#E4F0FB\">part</span><span style=\"color:#A6ACCD\">)</span><span style=\"color:#91B4D5\">:</span><span style=\"color:#E4F0FB\"> part</span><span style=\"color:#91B4D5\"> is</span><span style=\"color:#A6ACCDC0\"> string</span><span style=\"color:#91B4D5\"> =></span><span style=\"color:#E4F0FBD0\"> Boolean</span><span style=\"color:#A6ACCD\">(</span><span style=\"color:#E4F0FB\">part</span><span style=\"color:#A6ACCD\">) </span><span style=\"color:#91B4D5\">&#x26;&#x26;</span><span style=\"color:#91B4D5\"> typeof</span><span style=\"color:#E4F0FB\"> part</span><span style=\"color:#91B4D5\"> ===</span><span style=\"color:#A6ACCD\"> '</span><span style=\"color:#5DE4C7\">string</span><span style=\"color:#A6ACCD\">'</span><span style=\"color:#A6ACCD\">,</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">\t\t\t)</span></span>\n<span class=\"line\"><span style=\"color:#767C9DB0;font-style:italic\">\t\t\t// Trim each part</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">\t\t\t.</span><span style=\"color:#E4F0FBD0\">map</span><span style=\"color:#A6ACCD\">((</span><span style=\"color:#E4F0FB\">part</span><span style=\"color:#A6ACCD\">) </span><span style=\"color:#91B4D5\">=></span><span style=\"color:#E4F0FB\"> part</span><span style=\"color:#A6ACCD\">.</span><span style=\"color:#E4F0FBD0\">trim</span><span style=\"color:#A6ACCD\">())</span></span>\n<span class=\"line\"><span style=\"color:#767C9DB0;font-style:italic\">\t\t\t// Join all parts with a space</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">\t\t\t.</span><span style=\"color:#E4F0FBD0\">join</span><span style=\"color:#A6ACCD\">(</span><span style=\"color:#A6ACCD\">'</span><span style=\"color:#A6ACCD\"> '</span><span style=\"color:#A6ACCD\">)</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">\t);</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">}</span></span></code></pre></div><p>This function is fine for the most part: it’s concise and uses familiar methods. However, it requires three passes over the array (<code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">filter</code>, <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">map</code>, and <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">join</code>) and creates two intermediate arrays. While this isn’t an issue for this use case, it’s worth considering for larger datasets. A minor annoyance is dealing with TypeScript — the <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">filter</code> needs a type predicate, and the truthy check requires wrapping with <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">Boolean()</code> or using <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">!!</code>, which adds some verbosity.</p><h2 class=\"text-balance\" id=\"reduce\">Reduce</h2><div><pre class=\"shiki poimandres\" style=\"background-color:#1b1e28;color:#a6accd\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#91B4D5\">function</span><span style=\"color:#ADD7FF\"> makeDisplayName</span><span style=\"color:#A6ACCD\">(</span><span style=\"color:#91B4D5\">...</span><span style=\"color:#E4F0FB\">parts</span><span style=\"color:#91B4D5\">:</span><span style=\"color:#A6ACCDC0\"> Array</span><span style=\"color:#A6ACCD\">&#x3C;</span><span style=\"color:#A6ACCDC0\">string</span><span style=\"color:#91B4D5\"> |</span><span style=\"color:#A6ACCDC0\"> undefined</span><span style=\"color:#91B4D5\"> |</span><span style=\"color:#A6ACCDC0\"> null</span><span style=\"color:#91B4D5\"> |</span><span style=\"color:#A6ACCDC0\"> false</span><span style=\"color:#A6ACCD\">>)</span><span style=\"color:#91B4D5\">:</span><span style=\"color:#A6ACCDC0\"> string</span><span style=\"color:#A6ACCD\"> {</span></span>\n<span class=\"line\"><span style=\"color:#767C9DB0;font-style:italic\">\t// Accumulate valid parts into a single string</span></span>\n<span class=\"line\"><span style=\"color:#5DE4C7C0\">\treturn</span><span style=\"color:#E4F0FB\"> parts</span><span style=\"color:#A6ACCD\">.</span><span style=\"color:#E4F0FBD0\">reduce</span><span style=\"color:#A6ACCD\">&#x3C;</span><span style=\"color:#A6ACCDC0\">string</span><span style=\"color:#A6ACCD\">>(</span></span>\n<span class=\"line\"><span style=\"color:#767C9DB0;font-style:italic\">\t\t// Callback</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">\t\t(</span><span style=\"color:#E4F0FB\">name</span><span style=\"color:#A6ACCD\">, </span><span style=\"color:#E4F0FB\">part</span><span style=\"color:#A6ACCD\">) </span><span style=\"color:#91B4D5\">=></span><span style=\"color:#A6ACCD\"> {</span></span>\n<span class=\"line\"><span style=\"color:#767C9DB0;font-style:italic\">\t\t\t// If the part is a non-empty string</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">\t\t\tif (</span><span style=\"color:#E4F0FB\">part</span><span style=\"color:#91B4D5\"> &#x26;&#x26;</span><span style=\"color:#91B4D5\"> typeof</span><span style=\"color:#E4F0FB\"> part</span><span style=\"color:#91B4D5\"> ===</span><span style=\"color:#A6ACCD\"> '</span><span style=\"color:#5DE4C7\">string</span><span style=\"color:#A6ACCD\">'</span><span style=\"color:#A6ACCD\">) {</span></span>\n<span class=\"line\"><span style=\"color:#767C9DB0;font-style:italic\">\t\t\t\t// Append the trimmed part to the name, with a space</span></span>\n<span class=\"line\"><span style=\"color:#5DE4C7C0\">\t\t\t\treturn</span><span style=\"color:#E4F0FB\"> name</span><span style=\"color:#91B4D5\"> ?</span><span style=\"color:#A6ACCD\"> `${</span><span style=\"color:#E4F0FB\">name</span><span style=\"color:#A6ACCD\">}</span><span style=\"color:#A6ACCD\"> ${</span><span style=\"color:#E4F0FB\">part</span><span style=\"color:#A6ACCD\">.</span><span style=\"color:#E4F0FBD0\">trim</span><span style=\"color:#A6ACCD\">()</span><span style=\"color:#A6ACCD\">}`</span><span style=\"color:#91B4D5\"> :</span><span style=\"color:#E4F0FB\"> part</span><span style=\"color:#A6ACCD\">.</span><span style=\"color:#E4F0FBD0\">trim</span><span style=\"color:#A6ACCD\">();</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">\t\t\t}</span></span>\n<span class=\"line\"><span style=\"color:#767C9DB0;font-style:italic\">\t\t\t// Return the current name if the part is invalid</span></span>\n<span class=\"line\"><span style=\"color:#5DE4C7C0\">\t\t\treturn</span><span style=\"color:#E4F0FB\"> name</span><span style=\"color:#A6ACCD\">;</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">\t\t},</span></span>\n<span class=\"line\"><span style=\"color:#767C9DB0;font-style:italic\">\t\t// Initial value</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">\t\t''</span><span style=\"color:#A6ACCD\">,</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">\t);</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">}</span></span></code></pre></div><p>The first thing I noticed about this is we have to explicitly provide a value to the generic parameter of <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">reduce</code> for this to work. Trying to write a function like this can be pretty painful unless you know about this quirk. You also need to remember to return <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">name</code> if the part doesn&#x27;t pass the check in the <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">if</code> statement. Finally, we need to pass an empty string as the initial value so TypeScript can infer the type for <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">name</code>. While it’s a single-pass solution, it’s arguably harder to read than the <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">filter</code> and <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">map</code> approach.</p><h2 class=\"text-balance\" id=\"for-of-loop\"><code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">for...of</code> loop</h2><div><pre class=\"shiki poimandres\" style=\"background-color:#1b1e28;color:#a6accd\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#91B4D5\">function</span><span style=\"color:#ADD7FF\"> makeDisplayName</span><span style=\"color:#A6ACCD\">(</span><span style=\"color:#91B4D5\">...</span><span style=\"color:#E4F0FB\">parts</span><span style=\"color:#91B4D5\">:</span><span style=\"color:#A6ACCDC0\"> Array</span><span style=\"color:#A6ACCD\">&#x3C;</span><span style=\"color:#A6ACCDC0\">string</span><span style=\"color:#91B4D5\"> |</span><span style=\"color:#A6ACCDC0\"> undefined</span><span style=\"color:#91B4D5\"> |</span><span style=\"color:#A6ACCDC0\"> null</span><span style=\"color:#91B4D5\"> |</span><span style=\"color:#A6ACCDC0\"> false</span><span style=\"color:#A6ACCD\">>)</span><span style=\"color:#91B4D5\">:</span><span style=\"color:#A6ACCDC0\"> string</span><span style=\"color:#A6ACCD\"> {</span></span>\n<span class=\"line\"><span style=\"color:#767C9DB0;font-style:italic\">\t// Start with an empty string</span></span>\n<span class=\"line\"><span style=\"color:#91B4D5\">\tlet</span><span style=\"color:#E4F0FB\"> name</span><span style=\"color:#91B4D5\"> =</span><span style=\"color:#A6ACCD\"> ''</span><span style=\"color:#A6ACCD\">;</span></span>\n<span class=\"line\"><span style=\"color:#767C9DB0;font-style:italic\">\t// Loop through the parts</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">\tfor (</span><span style=\"color:#91B4D5\">const</span><span style=\"color:#E4F0FB\"> part</span><span style=\"color:#91B4D5\"> of</span><span style=\"color:#E4F0FB\"> parts</span><span style=\"color:#A6ACCD\">) {</span></span>\n<span class=\"line\"><span style=\"color:#767C9DB0;font-style:italic\">\t\t// If the part is a non-empty string</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">\t\tif (</span><span style=\"color:#E4F0FB\">part</span><span style=\"color:#91B4D5\"> &#x26;&#x26;</span><span style=\"color:#91B4D5\"> typeof</span><span style=\"color:#E4F0FB\"> part</span><span style=\"color:#91B4D5\"> ===</span><span style=\"color:#A6ACCD\"> '</span><span style=\"color:#5DE4C7\">string</span><span style=\"color:#A6ACCD\">'</span><span style=\"color:#A6ACCD\">) {</span></span>\n<span class=\"line\"><span style=\"color:#767C9DB0;font-style:italic\">\t\t\t// Add the trimmed part to the name, with a space</span></span>\n<span class=\"line\"><span style=\"color:#E4F0FB\">\t\t\tname</span><span style=\"color:#91B4D5\"> =</span><span style=\"color:#E4F0FB\"> name</span><span style=\"color:#91B4D5\"> ?</span><span style=\"color:#A6ACCD\"> `${</span><span style=\"color:#E4F0FB\">name</span><span style=\"color:#A6ACCD\">}</span><span style=\"color:#A6ACCD\"> ${</span><span style=\"color:#E4F0FB\">part</span><span style=\"color:#A6ACCD\">.</span><span style=\"color:#E4F0FBD0\">trim</span><span style=\"color:#A6ACCD\">()</span><span style=\"color:#A6ACCD\">}`</span><span style=\"color:#91B4D5\"> :</span><span style=\"color:#E4F0FB\"> part</span><span style=\"color:#A6ACCD\">.</span><span style=\"color:#E4F0FBD0\">trim</span><span style=\"color:#A6ACCD\">();</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">\t\t}</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">\t}</span></span>\n<span class=\"line\"><span style=\"color:#767C9DB0;font-style:italic\">\t// Return the final name</span></span>\n<span class=\"line\"><span style=\"color:#5DE4C7C0\">\treturn</span><span style=\"color:#E4F0FB\"> name</span><span style=\"color:#A6ACCD\">;</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">}</span></span></code></pre></div><p>In terms of code style, the <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">for...of</code> loop is the most imperative, but as you can see, it’s still about the same amount of code. We have basically the same logic as the <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">reduce</code> version, and none of the TypeScript shenanigans to worry about.</p><p>This is just one example, but it shows how different approaches trade off clarity, efficiency, and ease of use. Hopefully, it’s fairly representative of what you can expect.</p><p>Let’s look at how you can use a <a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Map</a> to take these benefits even further.</p><h2 class=\"text-balance\" id=\"using-maps-in-loops\">Using Maps in loops</h2><p>Another JavaScript feature I’ve found myself using more than ever on this project is the <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">Map</code> object. Maps are a special type of object that stores key-value pairs, and I often use its <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">get</code> and <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">has</code> methods to retrieve or check data inside a loop.</p><p>Imagine you’ve got an array of employees, and you want to print each employee’s name along with their manager’s name. Storing the manager object inside each employee isn’t ideal since managers are also employees and can manage multiple people, leading to lots of duplicates. A better approach might be to store the manager’s ID in each employee’s data.</p><p>Now there’s another challenge: you need to use the manager’s ID to find their data. At first, you might be tempted to use the <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">find</code> array method to search through the list of employees for the manager’s ID. But this can get very inefficient. For every employee in the array, you’d potentially have to search through the entire array again to find their manager.</p><p>Let’s break it down with some quick math. Say there are 100 employees in the array. On average, <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">find</code> would need to search through half the array (50 employees) to find the manager. That’s <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">100 * 50 = 5000</code> searches in total.</p><p>Instead, you can set up a <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">Map</code> where the key is the employee’s ID and the value is the corresponding employee object. Lookups are much faster because the <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">Map</code> knows exactly where each employee is stored in memory. This reduces the total operations to just <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">100</code> (to build the <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">Map</code>) plus <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">100</code> for the lookups, for a total of <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">200</code> operations compared to <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">5000</code> with <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">find</code>.</p><p>Here’s what that might look like:</p><div><pre class=\"shiki poimandres\" style=\"background-color:#1b1e28;color:#a6accd\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#767C9DB0;font-style:italic\">// Initial shape</span></span>\n<span class=\"line\"><span style=\"color:#91B4D5\">type</span><span style=\"color:#A6ACCD\"> Employee</span><span style=\"color:#91B4D5\"> =</span><span style=\"color:#A6ACCD\"> {</span></span>\n<span class=\"line\"><span style=\"color:#ADD7FF\">\tid</span><span style=\"color:#91B4D5\">:</span><span style=\"color:#A6ACCDC0\"> string</span><span style=\"color:#A6ACCD\">;</span></span>\n<span class=\"line\"><span style=\"color:#ADD7FF\">\tname</span><span style=\"color:#91B4D5\">:</span><span style=\"color:#A6ACCDC0\"> string</span><span style=\"color:#A6ACCD\">;</span></span>\n<span class=\"line\"><span style=\"color:#ADD7FF\">\tmanagerId</span><span style=\"color:#91B4D5\">?:</span><span style=\"color:#A6ACCDC0\"> string</span><span style=\"color:#A6ACCD\">;</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#767C9DB0;font-style:italic\">// Transformed shape</span></span>\n<span class=\"line\"><span style=\"color:#91B4D5\">type</span><span style=\"color:#A6ACCD\"> EmployeeManagementPair</span><span style=\"color:#91B4D5\"> =</span><span style=\"color:#A6ACCD\"> {</span></span>\n<span class=\"line\"><span style=\"color:#ADD7FF\">\temployeeName</span><span style=\"color:#91B4D5\">:</span><span style=\"color:#A6ACCDC0\"> string</span><span style=\"color:#A6ACCD\">;</span></span>\n<span class=\"line\"><span style=\"color:#ADD7FF\">\tmanagerName</span><span style=\"color:#91B4D5\">:</span><span style=\"color:#A6ACCDC0\"> string</span><span style=\"color:#A6ACCD\">;</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#91B4D5\">function</span><span style=\"color:#ADD7FF\"> mapEmployeesToManagers</span><span style=\"color:#A6ACCD\">(</span><span style=\"color:#E4F0FB\">employees</span><span style=\"color:#91B4D5\">:</span><span style=\"color:#A6ACCDC0\"> Array</span><span style=\"color:#A6ACCD\">&#x3C;</span><span style=\"color:#A6ACCDC0\">Employee</span><span style=\"color:#A6ACCD\">>) {</span></span>\n<span class=\"line\"><span style=\"color:#767C9DB0;font-style:italic\">\t// Precompute a Map for quick lookups</span></span>\n<span class=\"line\"><span style=\"color:#91B4D5\">\tconst</span><span style=\"color:#E4F0FB\"> employeesById</span><span style=\"color:#91B4D5\"> =</span><span style=\"color:#5DE4C7\"> new</span><span style=\"color:#E4F0FBD0\"> Map</span><span style=\"color:#A6ACCD\">(</span></span>\n<span class=\"line\"><span style=\"color:#E4F0FB\">\t\temployees</span><span style=\"color:#A6ACCD\">.</span><span style=\"color:#E4F0FBD0\">map</span><span style=\"color:#A6ACCD\">((</span><span style=\"color:#E4F0FB\">employee</span><span style=\"color:#A6ACCD\">) </span><span style=\"color:#91B4D5\">=></span><span style=\"color:#A6ACCD\"> [</span><span style=\"color:#E4F0FB\">employee</span><span style=\"color:#A6ACCD\">.</span><span style=\"color:#E4F0FB\">id</span><span style=\"color:#A6ACCD\">, </span><span style=\"color:#E4F0FB\">employee</span><span style=\"color:#A6ACCD\">]),</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">\t);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#91B4D5\">\tconst</span><span style=\"color:#E4F0FB\"> data</span><span style=\"color:#91B4D5\">:</span><span style=\"color:#A6ACCDC0\"> Array</span><span style=\"color:#A6ACCD\">&#x3C;</span><span style=\"color:#A6ACCDC0\">EmployeeManagementPair</span><span style=\"color:#A6ACCD\">> </span><span style=\"color:#91B4D5\">=</span><span style=\"color:#A6ACCD\"> [];</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">\tfor (</span><span style=\"color:#91B4D5\">const</span><span style=\"color:#E4F0FB\"> employee</span><span style=\"color:#91B4D5\"> of</span><span style=\"color:#E4F0FB\"> employees</span><span style=\"color:#A6ACCD\">) {</span></span>\n<span class=\"line\"><span style=\"color:#91B4D5\">\t\tconst</span><span style=\"color:#E4F0FB\"> manager</span><span style=\"color:#91B4D5\"> =</span><span style=\"color:#E4F0FB\"> employee</span><span style=\"color:#A6ACCD\">.</span><span style=\"color:#E4F0FB\">managerId</span></span>\n<span class=\"line\"><span style=\"color:#91B4D5\">\t\t\t?</span><span style=\"color:#E4F0FB\"> employeesById</span><span style=\"color:#A6ACCD\">.</span><span style=\"color:#E4F0FBD0\">get</span><span style=\"color:#A6ACCD\">(</span><span style=\"color:#E4F0FB\">employee</span><span style=\"color:#A6ACCD\">.</span><span style=\"color:#E4F0FB\">managerId</span><span style=\"color:#A6ACCD\">)</span></span>\n<span class=\"line\"><span style=\"color:#91B4D5\">\t\t\t:</span><span style=\"color:#D0679D\"> undefined</span><span style=\"color:#A6ACCD\">;</span></span>\n<span class=\"line\"><span style=\"color:#E4F0FB\">\t\tdata</span><span style=\"color:#A6ACCD\">.</span><span style=\"color:#E4F0FBD0\">push</span><span style=\"color:#A6ACCD\">({</span></span>\n<span class=\"line\"><span style=\"color:#ADD7FF\">\t\t\temployeeName</span><span style=\"color:#A6ACCD\">:</span><span style=\"color:#E4F0FB\"> employee</span><span style=\"color:#A6ACCD\">.</span><span style=\"color:#E4F0FB\">name</span><span style=\"color:#A6ACCD\">,</span></span>\n<span class=\"line\"><span style=\"color:#ADD7FF\">\t\t\tmanagerName</span><span style=\"color:#A6ACCD\">:</span><span style=\"color:#E4F0FB\"> manager</span><span style=\"color:#A6ACCD\">?.</span><span style=\"color:#E4F0FB\">name</span><span style=\"color:#91B4D5\"> ||</span><span style=\"color:#A6ACCD\"> '</span><span style=\"color:#5DE4C7\">None</span><span style=\"color:#A6ACCD\">'</span><span style=\"color:#A6ACCD\">,</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">\t\t});</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">\t}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#5DE4C7C0\">\treturn</span><span style=\"color:#E4F0FB\"> data</span><span style=\"color:#A6ACCD\">;</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">}</span></span></code></pre></div><h2 class=\"text-balance\" id=\"closing-thoughts\">Closing thoughts</h2><p>I’m glad I decided to experiment with using loops more often. They’ve become an invaluable addition to my toolbelt. They are generally faster than equivalent code using array methods — often easier to read — and as long as the mutations are properly encapsulated within a function, concerns about mutability are largely unfounded. Going forward, I’ll continue to use whatever I deem the best tool for the job, but now that I’ve spent more time with them, I expect to reach for a loop much more often.</p>",
			"date_published": "2025-01-01",
			"id": "https://www.lukebennett.dev//posts/using-loops-and-maps-for-efficient-data-transformations",
			"title": "Using Loops and Maps for Efficient Data Transformations",
			"url": "https://www.lukebennett.dev//posts/using-loops-and-maps-for-efficient-data-transformations"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>Back in April, <a href=\"https://www.youtube.com/watch?v=K5uS5DIKQbU\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">I gave a talk at Syd JS</a> about lessons learned while using Tailwind to build a design system. While I’ve been a Tailwind fan for years (since version <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">0.5.2</code>!), this was my first time applying it to a design system since joining Thinkmill. The process challenged me to rethink a few things.</p><p>Fun fact: I’m pretty sure the first time I heard about Thinkmill was through Tailwind super-fan (and now friend) <a href=\"https://simonswiss.com/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Simon Swiss</a>. Without Tailwind, I don’t think I’d be working at Thinkmill today!</p><p>The talk itself was just 25 minutes — too short to dive into the deeper, more in-the-weeds details that I hadn’t seen covered elsewhere. To fill in those gaps, I wrote a companion piece which <a href=\"https://www.thinkmill.com.au/blog/building-a-multi-brand-design-system-with-tailwind-tips-tricks-and-tradeoffs\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">you can find on the Thinkmill blog</a>. It’s a hefty read, clocking in at over 3,200 words, so grab a coffee (or your beverage of choice) and get comfy cause it might be a while!</p><p>If you find the talk or the article useful, I’d love to hear your thoughts! You can reach out on <a href=\"https://bsky.app/profile/lukebennett.dev\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Bluesky</a> or <a href=\"mailto:hello@lukebennett.com.au\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">via email</a>. And if your team needs help with a design system, <a href=\"https://www.thinkmill.com.au/contact\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">I can’t recommend Thinkmill enough</a>.</p>",
			"date_published": "2024-12-31",
			"id": "https://www.lukebennett.dev//posts/building-a-multi-brand-design-system-with-tailwind-tips-tricks-and-tradeoffs",
			"title": "Building a Multi-Brand Design System with Tailwind: Tips, Tricks, and Tradeoffs",
			"url": "https://www.lukebennett.dev//posts/building-a-multi-brand-design-system-with-tailwind-tips-tricks-and-tradeoffs"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>TIL</p>",
			"date_published": "2024-04-13",
			"external_url": "https://www.stefanjudis.com/today-i-learned/how-to-use-language-dependent-quotes-in-css/",
			"id": "https://www.lukebennett.dev//links/how-to-display-language-specific-quotes-in-css",
			"title": "How to display language-specific quotes in CSS",
			"url": "https://www.lukebennett.dev//links/how-to-display-language-specific-quotes-in-css"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<h2 class=\"text-balance\" id=\"links\">Links</h2><h3 class=\"text-balance\" id=\"\"><a href=\"https://million.dev/blog/lint\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Million Lint is in public beta</a></h3><p>I haven’t had a change to try this out myself, but they tout themselves on the website as as &quot;ESLint, but for performance&quot;. I rarely have to use React devtools to identify perf issues, but using and making sense of the flame-graph can be daunting. Since this is a VS Code extension and a plugin for your React app it looks like it might help me find perf issues before they start effecting users.</p><h3 class=\"text-balance\" id=\"\"><a href=\"https://alex.party/posts/2024-02-29-the-devrel-went-down-to-georgia/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">The DevRel Went Down to Georgia</a></h3><p>I love the chaotic 90’s aesthetic of this website.</p><h3 class=\"text-balance\" id=\"\"><a href=\"https://www.joshuakgoldberg.com/blog/please-stop-sending-me-nested-dependency-security-reports/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Please Stop Sending Me Nested Dependency Security Reports</a></h3><p>Security issues sound scary so I get why this happens, but most of the time they’re not a real issue:</p><blockquote><p>These excessive reports are produced because traditional scanners such as <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">npm audit</code> and Dependabot don’t have a way to indicate which API(s) in a package are impacted by a reported issue. That means every downstream package that directly or transitively relies on an impacted package -even if they never use the impacted APIs- will get a security report.</p></blockquote><p>Make sure to also read <a href=\"https://overreacted.io/npm-audit-broken-by-design/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">the linked post by Dan Abramov</a> if you haven’t already.</p><h3 class=\"text-balance\" id=\"\"><a href=\"https://devblogs.microsoft.com/typescript/announcing-typescript-5-4/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Announcing TypeScript 5.4</a></h3><p>Probably the most useful change in this update is the addition of the <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">NoInfer</code> utility type. But the change I’m most excited about is the auto-import support for subpath imports (e.g., the <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">imports</code> key in your <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">package.json</code>). I’m excited for two reasons. Firstly, hopefully, it will mean fewer people will use the path mapping in <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">tsconfig.js</code>, which was annoying because it was a TypeScript feature and not all tooling supported it. But the second (and more important) reason is that it was a community contribution from Emma Hamilton, whom I’m lucky enough to work with at Thinkmill!</p><h3 class=\"text-balance\" id=\"\"><a href=\"https://modernfontstacks.com/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Modern Font Stacks</a></h3><p>Who knew there were so many great native font stacks? I think I might switch to using one of these instead of using custom fonts like I currently am.</p><h3 class=\"text-balance\" id=\"\"><a href=\"https://www.thudfactor.com/posts/2024/03/pixels/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Maybe sometimes you should use pixels</a></h3><p>Interesting case against the conventional wisdom of using <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">rem</code> for almost everything since it respects users base font-size. Josh W Comeau wrote <a href=\"https://www.joshwcomeau.com/css/surprising-truth-about-pixels-and-accessibility/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">a similar post last year</a>.</p><p>I still think I’ll continue to mostly use <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">rem</code> units, but both posts make some good points and are worth reading.</p><h3 class=\"text-balance\" id=\"\"><a href=\"https://unmaintained.tech/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">No Maintenance Intended</a></h3><p>Lucky for me, I haven’t written anything popular enough to receive maintenance requests. But if I did, maybe I would add this badge to the README.</p><h3 class=\"text-balance\" id=\"\"><a href=\"https://joyeecheung.github.io/blog/2024/03/18/require-esm-in-node-js/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">require(esm) in Node.js</a></h3><p>This is experimental for now, but could be huge if/when it becomes stable.</p><h3 class=\"text-balance\" id=\"\"><a href=\"https://www.radix-ui.com/blog/themes-3\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Radix Themes 3.0</a></h3><p>Some really great additions in this release, in particular I thought the Skeleton component was clever.</p><h3 class=\"text-balance\" id=\"\"><a href=\"https://samthor.au/2024/kuto/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Kuto, a reverse JS bundler</a></h3><p>Clever use of circular dependencies (usually considered a bad thing) to enable hoisting for non-hoistable code.</p><h3 class=\"text-balance\" id=\"\"><a href=\"https://www.canva.com/newsroom/news/affinity/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Welcome to Canva, Affinity!</a></h3><p>This looks like a big play from Canva to directly compete with Adobe. I sure hope they don’t ruin the Affinity apps in the process.</p><p>The two companies have made <a href=\"https://affinity.serif.com/en-us/press/newsroom/affinity-and-canva-pledge/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">a joint pledge to continue to offer perpetual licenses</a>, amongst other things.</p><p>So far I’m cautiously optimistic.</p><h3 class=\"text-balance\" id=\"\"><a href=\"https://blog.jim-nielsen.com/2024/the-case-for-design-engineers-pt-iii/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">The Case for Design Engineers, Pt. III</a></h3><p>This one really summed up the way I think about making software.</p><blockquote><p>Over the course of making anything, new understandings will always arise. And if you’re unable to shift, evolve, and <em>design through the process of production</em>, you will lose out on these new understandings discovered through the process of making — and your finished product will be the poorer because of it.</p></blockquote><h3 class=\"text-balance\" id=\"\"><a href=\"https://romgrk.com/posts/optimizing-javascript\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Optimizing Javascript for fun and for profit</a></h3><p>I found this super interesting to read even if I only fully understood about a third of it!</p><h3 class=\"text-balance\" id=\"\"><a href=\"https://kittygiraudel.com/2024/03/29/on-disabled-and-aria-disabled-attributes/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">On disabled and aria-disabled attributes</a></h3><p>I’ve been guilty in the past of going too hard in the direction of using <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">aria-disabled</code> for everything and <em>never</em> using <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">disabled</code>, but Kitty makes some good points here.</p><p>Once again, the correct answer is always “it depends.”</p><h2 class=\"text-balance\" id=\"social-media\">Social media:</h2><ul><li><a href=\"https://twitter.com/rauchg/status/1763597428513804301\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Next.js hydration error diffs coming soon to Next.js</a></li><li><a href=\"https://twitter.com/paddix/status/1763664035529294011\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">CSS-Tricks might be making a comeback</a></li><li><a href=\"https://twitter.com/paddix/status/1770851651756138606\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">CSS-Tricks follow-up</a></li><li><a href=\"https://twitter.com/shadcn/status/1765084030960345143\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Nice to see React Select still hitting a niche</a></li><li><a href=\"https://twitter.com/anthonysheww/status/1765631490908406205\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Anthony Shew from Turborepo praising Emma for her recent addition to TypeScript</a></li><li><a href=\"https://twitter.com/mattgperry/status/1768223137307541887\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">How to speedrun a repo ban</a></li></ul><p></p>",
			"date_published": "2024-04-01",
			"id": "https://www.lukebennett.dev//posts/top-picks-2024-march",
			"title": "Top picks — 2024 March",
			"url": "https://www.lukebennett.dev//posts/top-picks-2024-march"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>It seems I’m not very good at posting regularly, so I thought I might borrow an idea I first saw on <a href=\"https://pawelgrzybek.com/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Paweł Grzybek’s blog</a> and try posting a monthly roundup of links.</p><p>Here are my most interesting links from February.</p><hr/><h2 class=\"text-balance\" id=\"links\">Links:</h2><h3 class=\"text-balance\" id=\"\"><a href=\"https://heydonworks.com/article/offloading-javascript-with-custom-properties/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Offloading JavaScript With Custom Properties</a></h3><p>Heydon Pickering makes use of applying CSS custom properties in an Intersection Observer which can then be used in CSS to apply animations. Nothing to revelatory here, but I found it to be a very nice example of using the right tool for the job.</p><h3 class=\"text-balance\" id=\"\"><a href=\"https://tonsky.me/blog/checkbox/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">In Loving Memory of Square Checkbox</a></h3><p>As someone who spends a lot of time working on design systems, it really bothers me when people mess with intuitive design. I really hope the round checkboxes in VisionOS get rolled back, or at the very least, don’t spread to other Apple operating systems.</p><h3 class=\"text-balance\" id=\"\"><a href=\"https://www.totaltypescript.com/forwardref-with-generic-components\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">How To Use forwardRef With Generic Components</a></h3><p>If you’ve worked with React components that accept a generic prop that also needs to be wrapped with <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">React.forwardRef</code>, you’ll know that it doesn’t work.\nMatt Pocock shows you how to work around it with a wrapper component that fixes the types.</p><p>Even better (in my opinion) if you’re just using these components in your app, and you don’t need to bundle them, you can use module augmentation to &quot;fix&quot; the types so you don’t need to use the wrapper:</p><div><pre class=\"shiki poimandres\" style=\"background-color:#1b1e28;color:#a6accd\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#5DE4C7\">declare</span><span style=\"color:#91B4D5\"> module</span><span style=\"color:#A6ACCD\"> '</span><span style=\"color:#5DE4C7\">react</span><span style=\"color:#A6ACCD\">'</span><span style=\"color:#A6ACCD\"> {</span></span>\n<span class=\"line\"><span style=\"color:#91B4D5\">\tfunction</span><span style=\"color:#ADD7FF\"> forwardRef</span><span style=\"color:#A6ACCD\">&#x3C;</span><span style=\"color:#A6ACCDC0\">T</span><span style=\"color:#A6ACCD\">, </span><span style=\"color:#A6ACCDC0\">P</span><span style=\"color:#91B4D5\"> =</span><span style=\"color:#A6ACCD\"> {}>(</span></span>\n<span class=\"line\"><span style=\"color:#ADD7FF\">\t\trender</span><span style=\"color:#91B4D5\">:</span><span style=\"color:#A6ACCD\"> (</span><span style=\"color:#E4F0FB\">props</span><span style=\"color:#91B4D5\">:</span><span style=\"color:#A6ACCDC0\"> P</span><span style=\"color:#A6ACCD\">, </span><span style=\"color:#E4F0FB\">ref</span><span style=\"color:#91B4D5\">:</span><span style=\"color:#A6ACCDC0\"> React</span><span style=\"color:#A6ACCD\">.</span><span style=\"color:#A6ACCDC0\">Ref</span><span style=\"color:#A6ACCD\">&#x3C;</span><span style=\"color:#A6ACCDC0\">T</span><span style=\"color:#A6ACCD\">>) </span><span style=\"color:#91B4D5\">=></span><span style=\"color:#A6ACCDC0\"> React</span><span style=\"color:#A6ACCD\">.</span><span style=\"color:#A6ACCDC0\">ReactNode</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">\t)</span><span style=\"color:#91B4D5\">:</span><span style=\"color:#A6ACCD\"> (</span><span style=\"color:#E4F0FB\">props</span><span style=\"color:#91B4D5\">:</span><span style=\"color:#A6ACCDC0\"> P</span><span style=\"color:#91B4D5\"> &#x26;</span><span style=\"color:#A6ACCDC0\"> React</span><span style=\"color:#A6ACCD\">.</span><span style=\"color:#A6ACCDC0\">RefAttributes</span><span style=\"color:#A6ACCD\">&#x3C;</span><span style=\"color:#A6ACCDC0\">T</span><span style=\"color:#A6ACCD\">>) </span><span style=\"color:#91B4D5\">=></span><span style=\"color:#A6ACCDC0\"> React</span><span style=\"color:#A6ACCD\">.</span><span style=\"color:#A6ACCDC0\">ReactNode</span><span style=\"color:#A6ACCD\">;</span></span>\n<span class=\"line\"><span style=\"color:#A6ACCD\">}</span></span></code></pre></div><h3 class=\"text-balance\" id=\"\"><a href=\"https://adrianroselli.com/2024/02/dont-disable-form-controls.html\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Don’t Disable Form Controls</a></h3><p>Really great article on why you really shouldn’t be disabling form controls (including submit buttons).</p><h3 class=\"text-balance\" id=\"\"><a href=\"https://www.mayank.co/blog/safari-focus/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">When will Apple focus on Safari?</a></h3><p>I can’t, for the life of me, understand why Apple doesn’t allow tabbing through buttons and links by default. It’s one of the first things I change whenever I set up a new Mac, and I always end up having to look it up 😡</p><h3 class=\"text-balance\" id=\"\"><a href=\"https://textslashplain.com/2024/02/22/the-importance-of-feedback-loops/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">The Importance of Feedback Loops</a></h3><blockquote><p>Sweat the small stuff, before it becomes the big stuff.</p></blockquote><h3 class=\"text-balance\" id=\"\"><a href=\"https://tkdodo.eu/blog/avoiding-hydration-mismatches-with-use-sync-external-store\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Avoiding Hydration Mismatches with useSyncExternalStore</a></h3><p>Hydration mismatches have to be one of the most frustrating things to debug in React.</p><p>I wouldn’t have thought to use <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">useSyncExternalStore</code> for this, but it after reading it makes sense. The <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">ClientGate</code> component is also a clever idea.</p><h3 class=\"text-balance\" id=\"\"><a href=\"https://react.dev/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">React Labs: What We’ve Been Working On – February 2024</a></h3><p>Really looking forward to the Canary features that are exposed in Next.js to finally become stable. Kudos to the React team for taking the time to get these things right. React 19 is going to be awesome, and I’m really looking forward to never having to worry about <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">useMemo</code> or <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">useCallback</code> ever again when React Compiler becomes stable (hopefully later this year).</p><h3 class=\"text-balance\" id=\"\"><a href=\"https://tempo.formkit.com/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Tempo</a></h3><p>5 kB, tree-shakable date library that has first-class support for working with time zones? This could be good...</p><h3 class=\"text-balance\" id=\"\"><a href=\"https://notes.rolandcrosby.com/posts/unexpectedly-eponymous/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Things Unexpectedly Named After People</a></h3><p>I don’t think I knew about <em>any</em> of these.</p><h3 class=\"text-balance\" id=\"\"><a href=\"https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Syntax Highlighting code snippets with Prism and the Custom Highlight API</a></h3><p>Bramus Van Damme talks a bit about syntax highlighting and introduces an interesting new browser API that enables syntax highlighting without excessive use of <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">&lt;span&gt;</code> elements.</p><h3 class=\"text-balance\" id=\"\"><a href=\"https://www.theverge.com/c/23194235/ai-fiction-writing-amazon-kindle-sudowrite-jasper\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">The Great Fiction of AI</a></h3><p>I love a good art-directed post.</p><h2 class=\"text-balance\" id=\"you-tube\">YouTube:</h2><ul><li><a href=\"https://www.youtube.com/watch?v=MxjCLqdk4G4\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">React’s new cache function</a></li><li><a href=\"https://www.youtube.com/watch?v=_gZ3ctkbGPo\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Preloading data (React’s cache part 2)</a></li><li><a href=\"https://www.youtube.com/watch?v=23bHSDJD9y4&amp;t=567s\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Out-of-order streaming in React</a></li><li><a href=\"https://www.youtube.com/watch?v=dxWLp-8mXes&amp;t=17s\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Asset loading in React</a></li><li><a href=\"https://www.youtube.com/watch?v=FD3aC_Ke8uk\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Is this a good idea?</a></li><li><a href=\"https://www.youtube.com/watch?v=aolI_Rz0ZqY\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">So You Think You Know Git - FOSDEM 2024</a></li></ul><h2 class=\"text-balance\" id=\"social-media\">Social media:</h2><ul><li><a href=\"https://twitter.com/diegohaz/status/1753448052893876697\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Interesting discussion about component composition</a></li><li><a href=\"https://twitter.com/saadeghi/status/1756994057396428800\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Me, editing my GitHub Action file until it runs correctly.</a></li><li><a href=\"https://mastodon.social/@LifesAHaskell/111904476759967560\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Never roll your own date time library kids</a></li></ul><p></p>",
			"date_published": "2024-03-01",
			"id": "https://www.lukebennett.dev//posts/top-picks-2024-february",
			"title": "Top picks — 2024 February",
			"url": "https://www.lukebennett.dev//posts/top-picks-2024-february"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>I recently <a href=\"/posts/using-keystatic-as-a-cms\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">revived my long-dormant website</a> as an excuse to better understand <a href=\"https://keystatic.com/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Keystatic</a>, as well as the new Next.js <a href=\"https://nextjs.org/blog/layouts-rfc#introducing-the-app-directory\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">app router</a>, and <a href=\"https://react.dev/blog/2020/12/21/data-fetching-with-react-server-components\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">server components</a>.</p><p>A few months have passed since then, and I&#x27;ve rebuilt the site again, this time using Astro. This post explains why.</p><hr/><p>My personal website is a space to learn new things and over-engineer the hell out of what is essentially a pretty basic blog (that, up until very recently, I haven&#x27;t really written anything for). This is the third design of my website, and it&#x27;s the <em>fifth</em> framework I&#x27;ve used. Here they are in order:</p><ol><li>Hugo</li><li>Gatsby</li><li>Remix</li><li>Next.js</li><li>Astro</li></ol><p>I&#x27;ve had something on this domain <a href=\"https://web.archive.org/web/20190313191850/https://www.lukebennett.com.au/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">since 2019</a>, that&#x27;s roughly one framework a year. So why did I drop Next.js after just a few months?</p><p>I actually really like Next.js; it&#x27;s my go-to in my day job. However, Next 13 has its issues. For the most part, I like the new app router, and I&#x27;ve wanted nested layouts in Next.js ever since I got a taste of them in Remix. The new app router, however, is <em>slow</em>. So slow that the Next.js team felt they had to publicly address it <a href=\"https://nextjs.org/blog/june-2023-update\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">on their blog</a>. I fear that the app router won&#x27;t be practical for medium-sized websites and above until <a href=\"https://turbo.build/pack\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Turbopack</a> becomes stable. As of right now, I can&#x27;t use Turbopack on <em>any</em> of the websites that I&#x27;ve built with Next.js, so a stable release may still be some time away.</p><p>Server components also, on the whole, seem to be a great addition. They let me colocate fetching data with the components that use them, and they <a href=\"https://www.youtube.com/watch?v=3JB_qEk39w0\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">can also be leveraged so you can server render components that would otherwise require shipping large libraries to the client</a>. Using server components usually results in a lot less JavaScript making its way to the client, which is always a good thing.</p><p>Apart from being one of the main reasons that the Next.js app router is so slow, they&#x27;re also a little awkward to work with sometimes. Server components only run on the server and therefore can&#x27;t have any state. This means we can&#x27;t use any hooks in them, and it turns out they&#x27;re a pretty big part of React!</p><p>When I&#x27;m building a new page for a website, I typically keep all of my components in the same file until either one of two issues comes up:</p><ul><li>I have to use a component on another page</li><li>The page becomes too big to be able to quickly find the section I&#x27;m looking for</li></ul><p>With server components, if I need some state (or even if I just want to generate a unique id for something), I have to extract that out into a new file. A lot of the time, these components aren&#x27;t really reusable and are very page-specific, so putting them in a components folder feels wrong, and co-locating it with the page is also kinda weird because Next.js has some file naming conventions, so it&#x27;s sometimes not clear if that file will be treated as &quot;special&quot; by Next.js or not. I typically add an underscore to the beginning of the filename of page-specific client components, which is fine, I guess ¯_(ツ)_/¯</p><p>Were any of these a deal breaker for me? No. In fact, I like using bleeding-edge stuff for non-critical stuff so that I have a good idea of when it&#x27;s ready to be used for client work.</p><p>I actually did most of the work of porting the site to Astro about a month ago, but I decided I didn&#x27;t like it enough to switch.</p><p>The full page refreshes didn&#x27;t feel very nice, and I lost the animation in my navigation. My theme switcher didn&#x27;t work properly and would require rebuilding. The <a href=\"https://github.com/vercel/react-tweet\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">react-tweet</a> library didn&#x27;t work properly for the handful of links to tweets I had wired up. I also don&#x27;t love the Astro template syntax. It&#x27;s just close enough to React to be annoying when it doesn&#x27;t work how I expect it to. There are also a bunch of Astro-specific things like the numerous template directives that have me reaching for the docs a lot. It feels like the API surface of Astro is bigger than with Next.js or Remix, but maybe that&#x27;s just because I&#x27;m still getting used to it.</p><p>So why did I switch to Astro?</p><p>There were a few reasons, all of them good, but the main reason I&#x27;m somewhat ashamed to admit was a purely cosmetic one is the stupidest of them all — View Transitions!</p><p>When Astro 3 came out with the stable View Transitions API, I decided to revive the Astro port and try it out. I added the <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">&lt;ViewTransitions /&gt;</code> component to the head of my document, and almost magically, all of my page transitions were super smooth, and the frustrations I had with Astro faded away.</p><p>The View Transitions fixed my issue with the navigation no longer animating, and I could drop framer motion here as the built-in animation was pretty much identical. This meant the only place I was using framer-motion was the animated steam on my coffee in SVG illustration on the home page. It took a bit of time to get right, but I was able to produce the effect I wanted with a keyframe animation (in fact, I think it looks better than it did before!)</p><p>I referenced Astro&#x27;s new Starlight to learn how to implement a theme switcher. It wasn&#x27;t as simple as using ﻿next-theme, but now I have complete control over the code for switching themes instead of relying on a library.</p><p>I got rid of the react-tweet library and inline the text of the tweets I was linking to. Twitter will probably go under soon anyway, so it&#x27;s probably more resilient this way.</p><p>I still think Astro has some quirks, but I love the <a href=\"https://jasonformat.com/islands-architecture/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">islands architecture</a>. For most small to medium sites, it&#x27;s probably the best choice. Sure, I removed a few libraries in the process of porting to Astro, but I&#x27;m pretty happy with the results.</p><div><img alt=\"Screenshot of the PageSpeed Insights website showing 99 for performance, 100 for accessibility, 100 for best practices, and 100 for SEO\" class=\"rounded-lg bg-white object-cover dark:bg-gray-800\" loading=\"lazy\" decoding=\"async\" sizes=\"(min-width: 700px) 700px, 100vw\" style=\"object-fit:cover;background-image:url(https://whwfzhw0vq15.keystatic.net/twnwnaffxs5x/images/j34frbz8rg7c/pagespeed-insights?fit=scale-down&amp;w=24&amp;h=18&amp;auto=format);background-size:cover;background-repeat:no-repeat;max-width:700px;max-height:532px;aspect-ratio:1.3157894736842106;width:100%\" srcSet=\"https://whwfzhw0vq15.keystatic.net/twnwnaffxs5x/images/j34frbz8rg7c/pagespeed-insights?fit=scale-down&amp;w=300&amp;h=228&amp;auto=format 300w,\nhttps://whwfzhw0vq15.keystatic.net/twnwnaffxs5x/images/j34frbz8rg7c/pagespeed-insights?fit=scale-down&amp;w=400&amp;h=304&amp;auto=format 400w,\nhttps://whwfzhw0vq15.keystatic.net/twnwnaffxs5x/images/j34frbz8rg7c/pagespeed-insights?fit=scale-down&amp;w=450&amp;h=342&amp;auto=format 450w,\nhttps://whwfzhw0vq15.keystatic.net/twnwnaffxs5x/images/j34frbz8rg7c/pagespeed-insights?fit=scale-down&amp;w=500&amp;h=380&amp;auto=format 500w,\nhttps://whwfzhw0vq15.keystatic.net/twnwnaffxs5x/images/j34frbz8rg7c/pagespeed-insights?fit=scale-down&amp;w=600&amp;h=456&amp;auto=format 600w,\nhttps://whwfzhw0vq15.keystatic.net/twnwnaffxs5x/images/j34frbz8rg7c/pagespeed-insights?fit=scale-down&amp;w=700&amp;h=532&amp;auto=format 700w,\nhttps://whwfzhw0vq15.keystatic.net/twnwnaffxs5x/images/j34frbz8rg7c/pagespeed-insights?fit=scale-down&amp;w=750&amp;h=570&amp;auto=format 750w,\nhttps://whwfzhw0vq15.keystatic.net/twnwnaffxs5x/images/j34frbz8rg7c/pagespeed-insights?fit=scale-down&amp;w=800&amp;h=608&amp;auto=format 800w,\nhttps://whwfzhw0vq15.keystatic.net/twnwnaffxs5x/images/j34frbz8rg7c/pagespeed-insights?fit=scale-down&amp;w=900&amp;h=684&amp;auto=format 900w,\nhttps://whwfzhw0vq15.keystatic.net/twnwnaffxs5x/images/j34frbz8rg7c/pagespeed-insights?fit=scale-down&amp;w=1000&amp;h=760&amp;auto=format 1000w,\nhttps://whwfzhw0vq15.keystatic.net/twnwnaffxs5x/images/j34frbz8rg7c/pagespeed-insights?fit=scale-down&amp;w=1050&amp;h=798&amp;auto=format 1050w,\nhttps://whwfzhw0vq15.keystatic.net/twnwnaffxs5x/images/j34frbz8rg7c/pagespeed-insights?fit=scale-down&amp;w=1200&amp;h=912&amp;auto=format 1200w,\nhttps://whwfzhw0vq15.keystatic.net/twnwnaffxs5x/images/j34frbz8rg7c/pagespeed-insights?fit=scale-down&amp;w=1400&amp;h=1064&amp;auto=format 1400w\" src=\"https://whwfzhw0vq15.keystatic.net/twnwnaffxs5x/images/j34frbz8rg7c/pagespeed-insights?fit=scale-down&amp;w=700&amp;h=532&amp;auto=format\"/></div><p>For now, I think I&#x27;ll stick with Astro, but who knows for how long.</p>",
			"date_published": "2023-09-10",
			"id": "https://www.lukebennett.dev//posts/astro-nomical-gains-why-i-switched-to-astro-from-next-js",
			"title": "Astro-nomical Gains: Why I Switched to Astro from Next.js",
			"url": "https://www.lukebennett.dev//posts/astro-nomical-gains-why-i-switched-to-astro-from-next-js"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>New lightweight <a href=\"https://zod.dev/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">zod</a> alternative.</p><p>I always feel bad about adding too much bundle weight — especially when I use zod with <a href=\"https://react-hook-form.com/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">react-hook-form</a>, <em>and</em> the <a href=\"https://github.com/react-hook-form/resolvers\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">react-hook-form zod resolver</a>.</p>",
			"date_published": "2023-08-26",
			"external_url": "https://www.builder.io/blog/introducing-valibot",
			"id": "https://www.lukebennett.dev//links/introducing-valibot-a-1kb-zod-alternative",
			"title": "Introducing Valibot, a < 1kb Zod Alternative",
			"url": "https://www.lukebennett.dev//links/introducing-valibot-a-1kb-zod-alternative"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>Yes please.</p>",
			"date_published": "2023-08-25",
			"external_url": "https://github.com/w3c/csswg-drafts/issues/9107",
			"id": "https://www.lukebennett.dev//links/allow-elements-to-escape-the-scroll-container",
			"title": "Allow elements to escape the scroll container",
			"url": "https://www.lukebennett.dev//links/allow-elements-to-escape-the-scroll-container"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>It&#x27;s sad to see Netlify becoming less and less relevant. The first website I ever built was originally hosted on BitBalloon (Netlify&#x27;s predecessor) and I hosted everything I could on Netlify back when I was working at <a href=\"https://pd.design/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Phiranno Designs</a>.</p><p>Anecdotally people don&#x27;t seem to be talking about the &quot;Jamstack&quot; anymore. Netlify claims it&#x27;s because Jamstack has become the new norm, but I&#x27;m not so sure. The term has became more and more nebulous over time, to the point where it doesn&#x27;t really mean much anymore. Plus, it always felt like a marketing term, so I think people have just soured on it.</p><p>On a side note, I started to lose interest in what Netlify was doing after <a href=\"https://jason.energy/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Jason Lengstorf</a> left. The same thing happened after he left Gatsby.</p>",
			"date_published": "2023-08-02",
			"external_url": "https://www.swyx.io/netlify-era-jamstack-end",
			"id": "https://www.lukebennett.dev//links/how-to-blow-up-a-category-netlifys-new-era-and-the-jamstack-endgame",
			"title": "How to Blow Up a Category - Netlify's New Era and The JAMstack Endgame",
			"url": "https://www.lukebennett.dev//links/how-to-blow-up-a-category-netlifys-new-era-and-the-jamstack-endgame"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>Interesting read. Not sure I entirely agree, but there are definitely some footguns with a lot of toggle designs in the wild such as <a href=\"https://www.w3.org/WAI/WCAG21/Understanding/use-of-color.html\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">relying too heavily on colour to convey information</a>.</p>",
			"date_published": "2023-07-30",
			"external_url": "https://axesslab.com/toggles-suck/",
			"id": "https://www.lukebennett.dev//links/toggles-suck",
			"title": "Toggles suck!",
			"url": "https://www.lukebennett.dev//links/toggles-suck"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>I can highly recommend the new podcast by PJ Vogt (former host of Reply All). It&#x27;s called Search Engine and the most recent episode was especially eye opening (no pun intended 😬)</p>",
			"date_published": "2023-07-30",
			"external_url": "https://pjvogt.substack.com/p/whats-it-like-to-slowly-go-blind",
			"id": "https://www.lukebennett.dev//links/what-s-it-like-to-slowly-go-blind",
			"title": "What's it like to slowly go blind?",
			"url": "https://www.lukebennett.dev//links/what-s-it-like-to-slowly-go-blind"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>This is cool, but I&#x27;m curious what <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">prefers-contrast: less</code> is for — I can&#x27;t imagine it would be an accessibility feature (unlike <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">prefers-contrast: more</code> which definitely is). Is it just for people who have a personal preference for a thinner font weights?</p>",
			"date_published": "2023-07-28",
			"external_url": "https://web.dev/articles/adapting-typography-to-user-preferences-with-css",
			"id": "https://www.lukebennett.dev//links/adapting-typography-to-user-preferences-with-css",
			"title": "Adapting typography to user preferences with CSS",
			"url": "https://www.lukebennett.dev//links/adapting-typography-to-user-preferences-with-css"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>Some positive vibes heading into the weekend 🫶</p><blockquote><p>College graduation speeches are often evergreen. They can be given at any time, similar from decade to decade.</p><p>Which is why Illinois Governor J. B. Pritzker’s “How To Spot An Idiot” at Northwestern’s graduation is so genius.</p><p>It is the speech for TODAY</p></blockquote><p></p>",
			"date_published": "2023-07-28",
			"external_url": "https://twitter.com/darrenrovell/status/1673659149417078788",
			"id": "https://www.lukebennett.dev//links/kindness-as-a-signifier-of-intelligence",
			"title": "Kindness as a signifier of intelligence",
			"url": "https://www.lukebennett.dev//links/kindness-as-a-signifier-of-intelligence"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>Looks like the Polaris team have been busy! Very cool to see my former colleagues <a href=\"https://github.com/gwyneplaine\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Charles Lee</a>, <a href=\"https://github.com/dominikwilkowski\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Dominik Wilkowski</a> and <a href=\"https://github.com/jesstelford\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Jess Telford</a> listed in the credits.</p><p>Super weird that this was a &quot;members only&quot; article on Medium though, Shopify should put this on a blog they own.</p>",
			"date_published": "2023-07-27",
			"external_url": "https://ux.shopify.com/uplifting-shopify-polaris-7c54fc6564d9",
			"id": "https://www.lukebennett.dev//links/uplifting-shopify-polaris",
			"title": "Uplifting Shopify Polaris",
			"url": "https://www.lukebennett.dev//links/uplifting-shopify-polaris"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>This is a really nice looking design system. It&#x27;s got me wondering if I need to start taking web components seriously 🤔</p>",
			"date_published": "2023-07-26",
			"external_url": "https://nordhealth.design/",
			"id": "https://www.lukebennett.dev//links/nord-design-system",
			"title": "Nord Design System",
			"url": "https://www.lukebennett.dev//links/nord-design-system"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>I never really thought about it before, but there&#x27;s no easy way of parsing a domain out of a URL because some &quot;TLDs&quot; have just 1 &quot;part&quot; like <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">.com</code> and some have two <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">.com.au</code> etc 🤔</p>",
			"date_published": "2023-07-25",
			"external_url": "https://blog.jim-nielsen.com/2023/domain-nuance/",
			"id": "https://www.lukebennett.dev//links/the-nuance-of-domain",
			"title": "The Nuance of “Domain”",
			"url": "https://www.lukebennett.dev//links/the-nuance-of-domain"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>I personally like <a href=\"https://daringfireball.net/misc/2023/07/office-fonts/Seaford%20Specimen.pdf\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Seaford</a> the most, which doesn&#x27;t surprise me as it was co-designed by <a href=\"https://en.wikipedia.org/wiki/Tobias_Frere-Jones\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Tobias Frere-Jones</a> who has designed some of my favourite typefaces such as Whitney and the slightly overused Gotham.</p>",
			"date_published": "2023-07-22",
			"external_url": "https://daringfireball.net/2023/07/aptos",
			"id": "https://www.lukebennett.dev//links/aptos-microsoft-s-new-default-font-for-office-documents",
			"title": "Aptos, Microsoft’s New Default Font for Office Documents",
			"url": "https://www.lukebennett.dev//links/aptos-microsoft-s-new-default-font-for-office-documents"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>End of an era.</p><blockquote><p>i feel bittersweet sharing i’m leaving my job at meta in a few weeks. working in the react org at meta has been an honor. i am thankful to my past and present colleagues for taking me in, letting me make mistakes, helping me see my strengths, being kind, and sharing their time.</p></blockquote><p></p>",
			"date_published": "2023-07-22",
			"external_url": "https://twitter.com/dan_abramov/status/1682029195843739649",
			"id": "https://www.lukebennett.dev//links/dan-abramov-is-leaving-meta",
			"title": "Dan Abramov is leaving Meta",
			"url": "https://www.lukebennett.dev//links/dan-abramov-is-leaving-meta"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>I&#x27;m not normally one for scroll-jacking, but this is wild.</p>",
			"date_published": "2023-07-21",
			"external_url": "https://narrowdesign.com/",
			"id": "https://www.lukebennett.dev//links/nick-jones",
			"title": "Nick Jones",
			"url": "https://www.lukebennett.dev//links/nick-jones"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<blockquote><p>Don’t make people solve, recall, or transcribe something to log in.</p></blockquote><p>Amen to that. Websites that don&#x27;t allow you to paste a password are the absolute worst, good to see the W3C acknowledging this.</p>",
			"date_published": "2023-07-21",
			"external_url": "https://www.w3.org/WAI/standards-guidelines/wcag/new-in-22/",
			"id": "https://www.lukebennett.dev//links/wcag-2-2-published-as-a-w3c-proposed-recommendation",
			"title": "WCAG 2.2 published as a W3C 'Proposed Recommendation'",
			"url": "https://www.lukebennett.dev//links/wcag-2-2-published-as-a-w3c-proposed-recommendation"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p></p>",
			"date_published": "2023-07-20",
			"external_url": "https://retro.pizza/@Cax6ton/110738513356164294",
			"id": "https://www.lukebennett.dev//links/cover-of-bring-me-to-life-by-evanescence-using-otamatones",
			"title": "Cover of \"Bring Me to Life\" by Evanescence using Otamatones",
			"url": "https://www.lukebennett.dev//links/cover-of-bring-me-to-life-by-evanescence-using-otamatones"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>This is a brilliant accessibility feature, anything that makes it more likely for people to provide alt text is win in my book.</p>",
			"date_published": "2023-07-20",
			"external_url": "https://isfeeling.social/@matt/110736966665218302",
			"id": "https://www.lukebennett.dev//links/ivory-mastodon-clients-clever-use-of-live-text",
			"title": "Ivory Mastodon Clients clever use of Live Text",
			"url": "https://www.lukebennett.dev//links/ivory-mastodon-clients-clever-use-of-live-text"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>Nice to see that pnpm can afford to pay a maintainer full time due to the donations they receive. Doubly nice to go to their website and see the Thinkmill logo listed as a sponsor.</p><blockquote><p>Thanks to your donations we have now one maintainer who can support pnpm full time. Give him a follow:</p><p><a href=\"https://twitter.com/hvksmr1996\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">@hvksmr1996</a></p></blockquote><p></p>",
			"date_published": "2023-07-14",
			"external_url": "https://twitter.com/pnpmjs/status/1679540300623011842",
			"id": "https://www.lukebennett.dev//links/pnpm-now-has-a-full-time-maintainer",
			"title": "pnpm now has a full time maintainer",
			"url": "https://www.lukebennett.dev//links/pnpm-now-has-a-full-time-maintainer"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>I kinda like this idea:</p><blockquote><p>I&#x27;d propose a 4-digit semver:</p><p>epoch.major.minor.patch</p><p>Major indicates there are technical breaking changes, but not necessarily affect many usages.</p><p>The new Epoch digit indicates an overhaul rewrite or massive update that usually requires migrations. Or for marketing.</p></blockquote><p><a href=\"https://keystonejs.com/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Keystone</a> gets around this by literally calling the package <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">@keystone-6</code> (the epoch is right there in the name rather than the version). It would be nice to <em>not</em> have to do that.</p><p>...but that ship has already sailed, any changes to semver at this point and we quickly enter <a href=\"https://xkcd.com/927/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">xkcd</a> territory, so I&#x27;m not suggesting anyone should actually do this, more of a &quot;this would have been nice&quot;.</p>",
			"date_published": "2023-07-13",
			"external_url": "https://twitter.com/antfu7/status/1679184417930059777",
			"id": "https://www.lukebennett.dev//links/anthony-fus-proposed-4-digit-semver",
			"title": "Anthony Fu's proposed 4-digit semver",
			"url": "https://www.lukebennett.dev//links/anthony-fus-proposed-4-digit-semver"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p></p>",
			"date_published": "2023-07-06",
			"external_url": "https://www.threads.net/@foreversean/post/CuVjwXqJ__f",
			"id": "https://www.lukebennett.dev//links/unexpectedly-wholesome-slipknot-fan-art",
			"title": "Unexpectedly wholesome Slipknot fan art",
			"url": "https://www.lukebennett.dev//links/unexpectedly-wholesome-slipknot-fan-art"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>🤦‍♂️</p>",
			"date_published": "2023-07-02",
			"external_url": "https://github.com/mdn/yari/issues/9208",
			"id": "https://www.lukebennett.dev//links/mdn-can-now-automatically-lie-to-people-seeking-technical-information",
			"title": "MDN can now automatically lie to people seeking technical information",
			"url": "https://www.lukebennett.dev//links/mdn-can-now-automatically-lie-to-people-seeking-technical-information"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>Interesting read on why pangrams aren&#x27;t all they&#x27;re cracked up to be.</p>",
			"date_published": "2023-07-01",
			"external_url": "https://www.typography.com/blog/text-for-proofing-fonts",
			"id": "https://www.lukebennett.dev//links/text-for-proofing-fonts",
			"title": "Text for Proofing Fonts",
			"url": "https://www.lukebennett.dev//links/text-for-proofing-fonts"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<blockquote><p>“If Google made the iPod,” he says, “they would have called it the Google Hardware MP3 Player For Music, you know?”</p></blockquote><p></p>",
			"date_published": "2023-07-01",
			"external_url": "https://www.theverge.com/23778253/google-reader-death-2013-rss-social",
			"id": "https://www.lukebennett.dev//links/who-killed-google-reader",
			"title": "Who killed Google Reader?",
			"url": "https://www.lukebennett.dev//links/who-killed-google-reader"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p></p>",
			"date_published": "2023-06-28",
			"external_url": "https://xkcd.com/2794/",
			"id": "https://www.lukebennett.dev//links/design-notes-on-the-alphabet",
			"title": "Design notes on the alphabet",
			"url": "https://www.lukebennett.dev//links/design-notes-on-the-alphabet"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>It&#x27;s interesting to see the use of psychedelics having seemingly quite positive effects on peoples mental health. <a href=\"https://www.youtube.com/watch?v=a546lxxJIhE\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Last Week Tonight had a great segment on this recently.</a></p>",
			"date_published": "2023-06-28",
			"external_url": "https://www.bbc.com/future/article/20230614-how-a-dose-of-mdma-transformed-a-white-supremacist",
			"id": "https://www.lukebennett.dev//links/how-a-dose-of-mdma-transformed-a-white-supremacist",
			"title": "How a dose of MDMA transformed a white supremacist",
			"url": "https://www.lukebennett.dev//links/how-a-dose-of-mdma-transformed-a-white-supremacist"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>This is a fun, absurdist spoof of the seemingly arbitrary rules around creating passwords online.</p>",
			"date_published": "2023-06-28",
			"external_url": "https://neal.fun/password-game/",
			"id": "https://www.lukebennett.dev//links/the-password-game",
			"title": "The Password Game",
			"url": "https://www.lukebennett.dev//links/the-password-game"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>Cool idea. It reminds me of <a href=\"https://tanstack.com/query/v4/docs/react/guides/window-focus-refetching\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Window Focus Refetching with TanStack Query</a>.</p>",
			"date_published": "2023-06-27",
			"external_url": "https://buildui.com/recipes/refresh-react-server-component-on-focus",
			"id": "https://www.lukebennett.dev//links/refresh-react-server-component-on-focus",
			"title": "Refresh React Server Component on Focus",
			"url": "https://www.lukebennett.dev//links/refresh-react-server-component-on-focus"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>The Mercator Projection really messes up your perspective on things. I had quite a few 🤯 moments reading this.</p>",
			"date_published": "2023-06-25",
			"external_url": "https://unchartedterritories.tomaspueyo.com/p/maps-distort-how-we-see-the-world",
			"id": "https://www.lukebennett.dev//links/maps-distort-how-we-see-the-world",
			"title": "Maps Distort How We See the World",
			"url": "https://www.lukebennett.dev//links/maps-distort-how-we-see-the-world"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>The transition to the new App Router in Next.js has been rough. It&#x27;s nice to see Vercel acknowledging this publicly.</p><p>Personally I&#x27;ve been enjoying using it on this site, but there are definitely some rough edges.</p>",
			"date_published": "2023-06-23",
			"external_url": "https://nextjs.org/blog/june-2023-update",
			"id": "https://www.lukebennett.dev//links/next-js-app-router-update",
			"title": "Next.js App Router Update",
			"url": "https://www.lukebennett.dev//links/next-js-app-router-update"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>TIL you can use a <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">media</code> attribute on a <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">&lt;link&gt;</code> element to offer both light and dark mode versions of your favicon.</p><p>The cool thing about this approach is that it doesn&#x27;t appear to require a manual page refresh when you change modes for the favicon to switch. That&#x27;s not the case with <a href=\"https://css-tricks.com/dark-mode-favicons/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">this approach</a> which I&#x27;ve used in the past.</p>",
			"date_published": "2023-06-21",
			"external_url": "https://www.epicweb.dev/tips/responsive-favicons",
			"id": "https://www.lukebennett.dev//links/support-responsive-favicons-for-a-professional-look",
			"title": "Support Responsive Favicons for a Professional Look",
			"url": "https://www.lukebennett.dev//links/support-responsive-favicons-for-a-professional-look"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>The Reddit saga just got more interesting. Some subreddits have marked themselves as NSFW so Reddit can&#x27;t run ads on them anymore!</p>",
			"date_published": "2023-06-21",
			"external_url": "https://www.platformer.news/p/what-were-learning-from-the-reddit",
			"id": "https://www.lukebennett.dev//links/what-were-learning-from-the-reddit-blackout",
			"title": "What we’re learning from the Reddit blackout",
			"url": "https://www.lukebennett.dev//links/what-were-learning-from-the-reddit-blackout"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>Segun Adebayo (of Chakra UI fame) has just announced the release of <a href=\"https://panda-css.com/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Panda CSS,</a> a new CSS-in-JS library that generates utility styles at build time (instead of runtime).</p><p>This looks like just the thing I&#x27;ve been wanting, you can still create dynamic styles like you can with Tailwind, but defining styles in objects rather than doing string concatenation makes the story around customising and overriding styles much simpler.</p>",
			"date_published": "2023-06-17",
			"external_url": "https://twitter.com/thesegunadebayo/status/1669732623994834946",
			"id": "https://www.lukebennett.dev//links/panda-css",
			"title": "Panda CSS",
			"url": "https://www.lukebennett.dev//links/panda-css"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>Imagine being the CEO of Reddit, looking at what&#x27;s happened to Twitter over the last few months and thinking &quot;I like what this Elon guy is doing, we should do something similar&quot; 🙃</p><p>Huffman also did an interview with <a href=\"https://www.theverge.com/2023/6/15/23762868/reddit-ceo-steve-huffman-interview\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">The Verge</a>. Gruber wrote about the Verge interview <a href=\"https://daringfireball.net/linked/2023/06/16/peters-huffman-interview\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">on Daring Fireball</a> and absolutely nails it with this line:</p><blockquote><p>No surprise that the CEO of a company whose website is so bad that they’ve had to keep <a href=\"https://old.reddit.com/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">the old one</a> around as an alternative doesn’t see the value Apollo adds to the Reddit experience.</p></blockquote><p></p>",
			"date_published": "2023-06-17",
			"external_url": "https://www.nbcnews.com/tech/tech-news/reddit-blackout-protest-private-ceo-elon-musk-huffman-rcna89700",
			"id": "https://www.lukebennett.dev//links/reddit-ceo-praises-elon-musks-cost-cutting-as-protests-rock-the-platform",
			"title": "Reddit CEO praises Elon Musk’s cost-cutting as protests rock the platform",
			"url": "https://www.lukebennett.dev//links/reddit-ceo-praises-elon-musks-cost-cutting-as-protests-rock-the-platform"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>I guess this is <a href=\"https://www.netlify.com/press/netlify-acquires-gatsby-inc-to-accelerate-adoption-of-composable-web-architectures/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">what Netlify wanted Gatsby for</a>.</p>",
			"date_published": "2023-06-14",
			"external_url": "https://www.netlify.com/products/connect/",
			"id": "https://www.lukebennett.dev//links/netlify-connect",
			"title": "Netlify Connect",
			"url": "https://www.lukebennett.dev//links/netlify-connect"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>I’m helping to build a design system for a client, and a colleague of mine (<a href=\"https://twitter.com/stowball\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Matt Stow</a>) introduced me to an interesting component that he created, which has fast become one of my most used layout components, so I thought I’d write about it.</p><p>“Track” is a layout component for distributing content horizontally. It has two “rails” (one on each end) and a flexible “center”. Once you start using this component, you realise you can use it everywhere. A checkbox with an inline label is a Track. A user avatar with your name next to it — that’s a Track. Anything with an icon and some corresponding text is also a Track. I’m sure you get the idea.</p><p>The rails each have <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">flex-shrink: 0</code> applied to them so that things like icons and images can’t get squished when there isn’t enough space. The center has <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">flex-grow: 1</code> to take up the remaining available space, as well as <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">min-width: 0px</code> to prevent horizontal overflow and ensure that text truncation behaves the way you’d expect.</p><p>We expose a <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">gap</code> prop for controlling spacing between the rails and the content, and a <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">verticalAlign</code> prop for aligning content to the top, middle, or bottom. At least, that’s what it used to do. We just added some new options to this prop to make it even more powerful!</p><p>Let’s say you’re displaying an address on a website, and you want to put an icon at the start to make it more scannable and visually interesting. Here’s what that markup might look like:</p><div><pre class=\"shiki poimandres\" style=\"background-color:#1b1e28;color:#a6accd\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#E4F0FB\">&#x3C;</span><span style=\"color:#5DE4C7\">Track</span></span>\n<span class=\"line\"><span style=\"color:#91B4D5;font-style:italic\">\trailStart</span><span style=\"color:#91B4D5\">=</span><span style=\"color:#E4F0FB\">{&#x3C;</span><span style=\"color:#5DE4C7\">MapPinIcon</span><span style=\"color:#E4F0FB\"> />}</span></span>\n<span class=\"line\"><span style=\"color:#91B4D5;font-style:italic\">\tcenter</span><span style=\"color:#91B4D5\">=</span><span style=\"color:#E4F0FB\">{&#x3C;</span><span style=\"color:#5DE4C7\">Text</span><span style=\"color:#E4F0FB\">>42 Wallaby Way, Sydney&#x3C;/</span><span style=\"color:#5DE4C7\">Text</span><span style=\"color:#E4F0FB\">>}</span></span>\n<span class=\"line\"><span style=\"color:#91B4D5;font-style:italic\">\tgap</span><span style=\"color:#91B4D5\">=</span><span style=\"color:#A6ACCD\">\"</span><span style=\"color:#5DE4C7\">4</span><span style=\"color:#A6ACCD\">\"</span></span>\n<span class=\"line\"><span style=\"color:#91B4D5;font-style:italic\">\tverticalAlign</span><span style=\"color:#91B4D5\">=</span><span style=\"color:#A6ACCD\">\"</span><span style=\"color:#5DE4C7\">middle</span><span style=\"color:#A6ACCD\">\"</span></span>\n<span class=\"line\"><span style=\"color:#E4F0FB\">/></span></span></code></pre></div><p>Nice, that looks great. But what happens if that address is dynamic? Well, now you have to handle edge cases. If a user has an address like: <em><strong>Unit 456, Building 789, Street 1234, Complex XYZ, Block A, Level 12, Wing C, Suite 345, Lot 678, Road 9012, Estate UVW, Sydney</strong></em>, well, that’s probably not going to fit on one line. We’ve told the icon to align to the middle, so now instead of the icon lining up with the text, it’s floating somewhere in the middle, and it looks kinda weird. You could try <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">verticalAlign=&quot;top&quot;</code>, but if the icon’s height is different from the text, it’s still going to be misaligned. So, how do you fix this?</p><p>Well, inspired by <a href=\"https://twitter.com/adamwathan/status/1217864323466432516\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">this tweet from Adam Wathan</a>, the secret is to use a zero-width space. We updated the <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">verticalAlign</code> prop to also accept typography tokens. In that case, we add a zero-width space alongside the rail content, apply <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">display: flex</code> and <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">align-items: center</code> to the element that wraps the rail content, and <code class=\"rounded-md border px-1 py-0.5 font-medium font-mono border-gray-300 bg-gray-200 text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50\">align-items: flex-start</code> to the track itself. Now, the icon is centred with the first line of text, and things wrap without issue.</p>",
			"date_published": "2023-06-14",
			"id": "https://www.lukebennett.dev//posts/track-the-best-layout-component-that-youve-probably-never-heard-of",
			"title": "Track: The Best Layout Component That You’ve Probably Never Heard Of",
			"url": "https://www.lukebennett.dev//posts/track-the-best-layout-component-that-youve-probably-never-heard-of"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>Such a bummer, every now and then I run into a layout issue that this CSS feature would have made trivial 🫤</p>",
			"date_published": "2023-06-11",
			"external_url": "https://ericwbailey.website/published/display-contents-considered-harmful/",
			"id": "https://www.lukebennett.dev//links/maybe-dont-use-display-contents",
			"title": "Maybe don't use display:contents",
			"url": "https://www.lukebennett.dev//links/maybe-dont-use-display-contents"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>Most of the text you read on this website was written using <a href=\"https://keystatic.com\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Keystatic</a> — a new tool developed by <a href=\"https://www.thinkmill.com.au\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Thinkmill</a> to manage website content. It supports human-readable Markdown (technically Markdoc), JSON, and YAML output formats.</p><p>For years now, I&#x27;ve had a personal website because:</p><ol><li>I&#x27;m a web developer, and every framework out there has a tutorial on how to use said framework to set up a blog.</li><li>I have a domain that I use for email, so I might as well use it for something!</li></ol><p>I&#x27;ve always been jealous of people who are able to maintain a blog. I&#x27;m not one of those people (but I would like to be!). I&#x27;m hoping Keystatic will remove some of the friction I&#x27;ve experienced in the past, so that I&#x27;m more likely to write. Only time will tell.</p><p>Over the years, I&#x27;ve experimented with numerous CMSs, including WordPress, SiteLeaf, Forestry, Sanity, Netlify CMS, Prismic, Contentful, and probably several others that I can&#x27;t recall right now. Truth be told, I didn&#x27;t really like <em>any</em> of them. I may be biased (since I work at Thinkmill, though I haven&#x27;t directly contributed to Keystatic beyond beta testing), but I genuinely believe Keystatic has the most potential to make blogging stick for developers like myself.</p><p>Having no database is very appealing to me. In the past, I&#x27;ve tried setting up a blog that reads Markdown files directly from the file system, but I never stuck with it. There was just too much friction to get started (and I&#x27;m lazy when it comes to writing). Having my content integrated within the codebase makes anything I write far more portable, and using a human-readable format like Markdoc gives me plenty of options when it comes to editing it (though I usually prefer using Keystatic). By keeping my content in code rather than a database, I never have to worry about database migrations or encountering broken deploy previews because the database and the website are out of sync.</p><p>While Keystatic isn&#x27;t the first CMS to offer a WYSIWYG editor for markdown files, in my opinion, it&#x27;s the first to really nail the execution. Defining the schema is straightforward (and surprisingly powerful), and the editing experience is very nice (and getting better all the time).</p><p>I&#x27;m keenly watching the development of Keystatic. It&#x27;s early days, and there are still a few rough edges here and there, but overall it&#x27;s been a joy to work with. Now, if only there was a nice way to manage assets like images that are a little out of place in source control... 🤔</p>",
			"date_published": "2023-06-11",
			"id": "https://www.lukebennett.dev//posts/using-keystatic-as-a-cms",
			"title": "Using Keystatic as a CMS",
			"url": "https://www.lukebennett.dev//posts/using-keystatic-as-a-cms"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>Nostalgia hit.</p>",
			"date_published": "2023-06-04",
			"external_url": "https://infinitemac.org",
			"id": "https://www.lukebennett.dev//links/infinite-mac",
			"title": "Infinite Mac",
			"url": "https://www.lukebennett.dev//links/infinite-mac"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>First third party Twitter clients were shut off, now third party Reddit clients are getting priced out.</p><p>Why can&#x27;t we have nice things 🥲</p>",
			"date_published": "2023-06-01",
			"external_url": "https://reddit.com/r/apolloapp/comments/13ws4w3/had_a_call_with_reddit_to_discuss_pricing_bad",
			"id": "https://www.lukebennett.dev//links/reddit-pricing-third-party-apps-out-of-existence",
			"title": "Reddit pricing third party apps out of existence",
			"url": "https://www.lukebennett.dev//links/reddit-pricing-third-party-apps-out-of-existence"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<blockquote><p>happy 10th birthday to @reactjs! 🎂⚛️</p></blockquote><p></p>",
			"date_published": "2023-05-30",
			"external_url": "https://twitter.com/dan_abramov/status/1663263814333153286",
			"id": "https://www.lukebennett.dev//links/react-is-now-10-years-old",
			"title": "React is now 10 years old",
			"url": "https://www.lukebennett.dev//links/react-is-now-10-years-old"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>I was looking through the <a href=\"https://www.mozilla.org/en-US/firefox/113.0/releasenotes/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">release notes for Firefox 113</a> and saw that they&#x27;ve added support for OKLCH.</p><p>Firefox is the last of the &quot;evergreen&quot; browsers to support this new colour space.</p><p>If you&#x27;re wondering why this is cool, check out <a href=\"https://evilmartians.com/chronicles/oklch-in-css-why-quit-rgb-hsl\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">this article by Evil Martians</a>.</p><p>Update: <a href=\"https://web.dev/color-spaces-and-functions/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Rachel Andrew recently wrote about this on the web.dev blog</a>.</p>",
			"date_published": "2023-05-29",
			"external_url": "https://www.mozilla.org/en-US/firefox/113.0/releasenotes/#note-789524",
			"id": "https://www.lukebennett.dev//links/oklch-support-now-in-all-evergreen-browsers",
			"title": "OKLCH support now in all evergreen browsers",
			"url": "https://www.lukebennett.dev//links/oklch-support-now-in-all-evergreen-browsers"
		},
		{
			"authors": [
				{
					"name": "Luke Bennett"
				}
			],
			"content_html": "<p>Here is a list of the tools I use for work, inspired by <a href=\"https://wesbos.com/uses\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">this post from Wes Bos</a>.</p><h2 class=\"text-balance\" id=\"computer-office\">Computer / Office</h2><ul><li>MacBook Air (M1, 2020)</li><li>Desk: <a href=\"https://updowndesk.com.au/products/updown-desk-pro-series-electric-standing-desk-with-bamboo-desktop?variant=37579954847913\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Updown Desk 180cm Bamboo standing desk</a></li><li>Monitor: Samsung U28H75x</li><li>Keyboard: <a href=\"https://www.keychron.com/products/keychron-k2-wireless-mechanical-keyboard\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Keychron K2</a></li><li>Mouse: Logitech MX Master 3</li><li>Trackpad: Apple Magic Trackpad 2</li></ul><h2 class=\"text-balance\" id=\"coding\">Coding</h2><ul><li>Editor: Visual Studio Code (<a href=\"https://gist.github.com/lukebennett88/9b2ca9671a808b81bddbf6bce51507e9\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Settings</a> / <a href=\"https://gist.github.com/lukebennett88/9bf01c9f2ecf870dcefa7a63b5afb7a7\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Extensions</a>)</li><li>Theme: Night Owl</li><li>Font: <a href=\"https://www.ibm.com/plex/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">IBM Plex Mono</a></li><li>Terminal(s): Integrated terminal in VS Code with <a href=\"https://fig.io/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Fig</a>, <a href=\"https://www.warp.dev/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Warp</a></li></ul><h2 class=\"text-balance\" id=\"clis\">CLIs</h2><ul><li><a href=\"https://brew.sh/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Homebrew</a></li><li><a href=\"https://dns.lookup.dog/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">dog</a> (a better <a href=\"https://en.wikipedia.org/wiki/Dig_(command)\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">dig</a>)</li><li><a href=\"https://volta.sh/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">volta</a> (a better <a href=\"https://github.com/nvm-sh/nvm\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">nvm</a>)</li><li><a href=\"https://cli.github.com/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">gh</a> (for working with GitHub)</li><li><a href=\"https://nodejs.org/en/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">node/npm</a> (duh)</li><li><a href=\"https://pnpm.io/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">pnpm</a> (npm alternative that Thinkmill sponsors)</li></ul><h2 class=\"text-balance\" id=\"browser-s\">Browser(s)</h2><p><a href=\"https://brave.com/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Brave</a> is pretty decent, it&#x27;s basically Google Chrome without all the gross Google stuff. This is my primary browser.</p><p>Safari is better than people give it credit for, but I have to admit the dev tools aren&#x27;t as good, and there are still plenty of websites that don&#x27;t work as well in it (or at at all in some cases, but I don&#x27;t think this is necessarily Safari&#x27;s fault). I use Safari to keep work and personal stuff seperate as well as access tabs that I have open on my phone.</p><p>I keep trying to use <a href=\"https://arc.net/\" class=\"-mx-0.5 rounded-md px-0.5 decoration-teal-700 underline-offset-2 transition-colors hover:bg-teal-700/50\">Arc</a> because it&#x27;s very well designed, but I&#x27;m not sure it&#x27;s for me. I&#x27;m still experimenting though.</p><h2 class=\"text-balance\" id=\"desktop-applications\">Desktop Applications</h2><ul><li>1Password</li><li>Affinity Designer</li><li>Choosy</li><li>Backblaze</li><li>Fantastical 3</li><li>Figma</li><li>Ivory</li><li>Raycast</li><li>Rocket</li><li>Setapp<ul><li>CleanMyMac X</li><li>CleanShot X</li><li>PixelSnap</li></ul></li></ul><p></p>",
			"date_published": "2023-05-29",
			"id": "https://www.lukebennett.dev//posts/uses",
			"title": "Uses",
			"url": "https://www.lukebennett.dev//posts/uses"
		}
	],
	"title": "Luke Bennett",
	"version": "https://jsonfeed.org/version/1.1"
}