<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Alex Carter — Full-stack Developer</title><description>Full-stack developer building fast, scalable web applications. Writing about TypeScript, performance, and developer tooling.</description><link>https://example.com</link><language>en-us</language><item><title>My developer tooling stack for 2026</title><link>https://example.com/blog/developer-tooling-2026</link><guid isPermaLink="true">https://example.com/blog/developer-tooling-2026</guid><description>The editors, terminals, CLI tools, and workflow glue I actually use daily — with honest notes on what earned its place and what got cut.</description><pubDate>Wed, 15 Jul 2026 00:00:00 GMT</pubDate><category>Tooling</category><category>TypeScript</category><category>Workflow</category><author>hello@example.com (Alex Carter)</author></item><item><title>Shipping genuinely fast websites with Astro</title><link>https://example.com/blog/shipping-fast-astro-sites</link><guid isPermaLink="true">https://example.com/blog/shipping-fast-astro-sites</guid><description>Why static-first architecture wins for content sites, and the concrete techniques I use to keep Lighthouse scores at 100 without giving up interactivity.</description><pubDate>Thu, 18 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Every few years the pendulum swings between “ship everything as JavaScript” and “ship as little JavaScript as possible”. For content sites — blogs, portfolios, docs — the second camp has clearly won, and Astro is the sharpest tool in it.&lt;/p&gt;
&lt;h2 id=&quot;the-islands-model-briefly&quot;&gt;The islands model, briefly&lt;/h2&gt;
&lt;p&gt;Astro renders your entire page to static HTML at build time. Components that &lt;em&gt;need&lt;/em&gt; interactivity opt in explicitly:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;/_astro/ec.p9ju4.css&quot;&gt;&lt;script type=&quot;module&quot; src=&quot;/_astro/ec.0vx5m.js&quot;&gt;&lt;/script&gt;&lt;figure class=&quot;frame has-title&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;src/pages/index.astro&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;astro&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#99A0A6;--1:#616972&quot;&gt;---&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; Chart &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&apos;../components/Chart.tsx&apos;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#99A0A6;--1:#616972&quot;&gt;---&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#99A0A6;--1:#616972&quot;&gt;&amp;lt;!-- Rendered to HTML, zero JS shipped --&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;--0:#85E89D;--1:#1E7734&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;&amp;gt;Traffic report&amp;lt;/&lt;/span&gt;&lt;span style=&quot;--0:#85E89D;--1:#1E7734&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#99A0A6;--1:#616972&quot;&gt;&amp;lt;!-- Hydrated only when it scrolls into view --&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;Chart&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;client:visible&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; /&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;---import Chart from &amp;#x27;../components/Chart.tsx&amp;#x27;;---&lt;!-- Rendered to HTML, zero JS shipped --&gt;&lt;h1&gt;Traffic report&lt;/h1&gt;&lt;!-- Hydrated only when it scrolls into view --&gt;&lt;Chart client:visible /&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Everything else — headers, footers, article bodies, cards — ships as plain HTML and CSS. The result is that your baseline JavaScript cost is close to zero, and every kilobyte you &lt;em&gt;do&lt;/em&gt; ship is a deliberate decision.&lt;/p&gt;
&lt;h2 id=&quot;techniques-that-actually-move-the-needle&quot;&gt;Techniques that actually move the needle&lt;/h2&gt;
&lt;h3 id=&quot;prefetch-on-hover-and-viewport&quot;&gt;Prefetch on hover and viewport&lt;/h3&gt;
&lt;p&gt;Astro’s built-in prefetch turns full page loads into near-instant navigations:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame has-title&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;astro.config.ts&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;defineConfig&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;site: &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&apos;https://example.com&apos;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight mark&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;prefetch: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight mark&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;prefetchAll: &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#004fa9&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight mark&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;defaultStrategy: &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&apos;viewport&apos;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight mark&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;export default defineConfig({  site: &amp;#x27;https://example.com&amp;#x27;,  prefetch: {    prefetchAll: true,    defaultStrategy: &amp;#x27;viewport&amp;#x27;,  },});&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;By the time a visitor clicks a link, the next page’s HTML is usually already in the cache. You get SPA-feeling navigation without a router, a hydration step, or a client-side state bug budget.&lt;/p&gt;
&lt;h3 id=&quot;self-host-your-fonts&quot;&gt;Self-host your fonts&lt;/h3&gt;
&lt;p&gt;A &lt;code&gt;@fontsource&lt;/code&gt; import costs one line and removes an entire third-party origin:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&apos;@fontsource-variable/inter&apos;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;import &amp;#x27;@fontsource-variable/inter&amp;#x27;;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;No connection setup to fonts.googleapis.com, no layout shift from late-loading fonts, no consent-mode headaches. Variable fonts mean one file covers every weight you use.&lt;/p&gt;
&lt;h3 id=&quot;optimize-the-images-you-forgot-about&quot;&gt;Optimize the images you forgot about&lt;/h3&gt;
&lt;p&gt;Hero images get attention; the avatar in your footer doesn’t. Audit with the network tab sorted by size — the offenders are rarely where you expect. Set explicit &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; everywhere so the browser can reserve space before the image arrives.&lt;/p&gt;
&lt;h2 id=&quot;what-i-stopped-worrying-about&quot;&gt;What I stopped worrying about&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Client-side routing.&lt;/strong&gt; Prefetched MPA navigation is indistinguishable for content sites.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Skeleton screens.&lt;/strong&gt; If the page renders in under 100ms, a skeleton is just a flash of fake content.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complex state management.&lt;/strong&gt; Static pages don’t have state. This is a feature.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-scoreboard&quot;&gt;The scoreboard&lt;/h2&gt;
&lt;p&gt;This theme — the one you’re reading — ships roughly 4KB of JavaScript on a typical page: the theme toggle, mobile navigation, and a lazily-loaded search index. Lighthouse gives it a 100 in performance, and more importantly, it &lt;em&gt;feels&lt;/em&gt; instant on a mid-range phone over 4G.&lt;/p&gt;
&lt;p&gt;Fast is a feature. Static-first is how you afford it.&lt;/p&gt;
</content:encoded><category>Astro</category><category>Performance</category><category>Web</category><author>hello@example.com (Alex Carter)</author></item><item><title>TypeScript narrowing patterns I reach for every week</title><link>https://example.com/blog/typescript-narrowing-patterns</link><guid isPermaLink="true">https://example.com/blog/typescript-narrowing-patterns</guid><description>Discriminated unions, assertion functions, and the satisfies operator — a working tour of the narrowing techniques that make TypeScript feel like a superpower.</description><pubDate>Mon, 04 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Most TypeScript pain comes from fighting the narrowing engine instead of working with it. These are the patterns I use constantly — not clever tricks, just the bread and butter that keeps a codebase honest.&lt;/p&gt;
&lt;h2 id=&quot;discriminated-unions-over-optional-fields&quot;&gt;Discriminated unions over optional fields&lt;/h2&gt;
&lt;p&gt;The single biggest improvement you can make to a data model is replacing “bag of optionals” with a tagged union:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;/_astro/ec.p9ju4.css&quot;&gt;&lt;script type=&quot;module&quot; src=&quot;/_astro/ec.0vx5m.js&quot;&gt;&lt;/script&gt;&lt;figure class=&quot;frame has-title&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;src/types.ts&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line highlight del&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#fa96a1;--1:#952933&quot;&gt;interface&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#c0a5f3;--1:#613aa9&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight del&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#883b05&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;--0:#fa96a1;--1:#952933&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#004fa9&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight del&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#883b05&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;--0:#fa96a1;--1:#952933&quot;&gt;?:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#c0a5f3;--1:#613aa9&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;[];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight del&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#883b05&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;--0:#fa96a1;--1:#952933&quot;&gt;?:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#004fa9&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight del&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight ins&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#c2a8f3;--1:#613aa9&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight ins&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#8b3c06&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&apos;loading&apos;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight ins&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#8b3c06&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&apos;success&apos;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#8b3c06&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#c2a8f3;--1:#613aa9&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;[] }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight ins&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#8b3c06&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&apos;error&apos;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#8b3c06&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#004fa9&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; };&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;interface Request {  status: string;  data?: User[];  error?: string;}type Request =  | { status: &amp;#x27;loading&amp;#x27; }  | { status: &amp;#x27;success&amp;#x27;; data: User[] }  | { status: &amp;#x27;error&amp;#x27;; error: string };&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Now &lt;code&gt;request.data&lt;/code&gt; simply does not exist unless &lt;code&gt;status === &apos;success&apos;&lt;/code&gt;. Whole categories of “why is data undefined here” bugs stop compiling.&lt;/p&gt;
&lt;h2 id=&quot;assertion-functions-at-the-boundaries&quot;&gt;Assertion functions at the boundaries&lt;/h2&gt;
&lt;p&gt;Data from the outside world — APIs, forms, file reads — should pass through an assertion exactly once:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame has-title&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;src/lib/assert.ts&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;assertUser&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;unknown&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;asserts&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;is&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;typeof&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; value &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;!==&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&apos;object&apos;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;||&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;value &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;||&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;typeof&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; (value &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;).id &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;!==&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&apos;string&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;throw&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&apos;Invalid user payload&apos;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;export function assertUser(value: unknown): asserts value is User {  if (    typeof value !== &amp;#x27;object&amp;#x27; ||    value === null ||    typeof (value as User).id !== &amp;#x27;string&amp;#x27;  ) {    throw new Error(&amp;#x27;Invalid user payload&amp;#x27;);  }}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;After the call site, TypeScript treats the value as &lt;code&gt;User&lt;/code&gt; with no casting. The unsafe cast lives in one audited function instead of being scattered across the codebase as &lt;code&gt;as User&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;satisfies-for-config-objects&quot;&gt;&lt;code&gt;satisfies&lt;/code&gt; for config objects&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;satisfies&lt;/code&gt; checks a value against a type &lt;em&gt;without&lt;/em&gt; widening it:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;routes&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;home: &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;blog: &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&apos;/blog&apos;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;projects: &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&apos;/projects&apos;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;satisfies&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;Record&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;`/${&lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;&amp;gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;routes.blog; &lt;/span&gt;&lt;span style=&quot;--0:#99A0A6;--1:#616972&quot;&gt;// type is &apos;/blog&apos;, not string&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;const routes = {  home: &amp;#x27;/&amp;#x27;,  blog: &amp;#x27;/blog&amp;#x27;,  projects: &amp;#x27;/projects&amp;#x27;,} satisfies Record&lt;string, &amp;#x60;/${string}&amp;#x60;&gt;;routes.blog; // type is &amp;#x27;/blog&amp;#x27;, not string&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;You get validation &lt;em&gt;and&lt;/em&gt; precise literal types. Before &lt;code&gt;satisfies&lt;/code&gt;, you had to choose one.&lt;/p&gt;
&lt;h2 id=&quot;exhaustiveness-with-never&quot;&gt;Exhaustiveness with &lt;code&gt;never&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Close the loop on unions with a default branch that fails to compile when a variant is added:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;label&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&apos;status&apos;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;])&lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;switch&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; (status) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;case&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&apos;loading&apos;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&apos;Loading…&apos;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;case&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&apos;success&apos;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&apos;Done&apos;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;case&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&apos;error&apos;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight mark&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&apos;Failed&apos;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight mark&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; status &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;satisfies&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;never&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;function label(status: Request[&amp;#x27;status&amp;#x27;]): string {  switch (status) {    case &amp;#x27;loading&amp;#x27;:      return &amp;#x27;Loading…&amp;#x27;;    case &amp;#x27;success&amp;#x27;:      return &amp;#x27;Done&amp;#x27;;    case &amp;#x27;error&amp;#x27;:      return &amp;#x27;Failed&amp;#x27;;    default:      return status satisfies never;  }}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Add a &lt;code&gt;&apos;cancelled&apos;&lt;/code&gt; variant next quarter and this function becomes a compile error instead of a silent bug.&lt;/p&gt;
&lt;h2 id=&quot;the-habit-that-ties-it-together&quot;&gt;The habit that ties it together&lt;/h2&gt;
&lt;p&gt;Narrow at the edges, trust the middle. If every input passes through a discriminated union or an assertion function on entry, the core of your application never touches &lt;code&gt;unknown&lt;/code&gt;, never casts, and never surprises you at runtime.&lt;/p&gt;
</content:encoded><category>TypeScript</category><category>Patterns</category><author>hello@example.com (Alex Carter)</author></item><item><title>Five Postgres indexing mistakes I keep seeing</title><link>https://example.com/blog/postgres-indexing-mistakes</link><guid isPermaLink="true">https://example.com/blog/postgres-indexing-mistakes</guid><description>A billion-row education in what the query planner actually does with your indexes — and the EXPLAIN habits that would have caught each mistake early.</description><pubDate>Sun, 22 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’ve reviewed a lot of slow-query incidents over the years. The same five indexing mistakes account for most of them.&lt;/p&gt;
&lt;h2 id=&quot;1-indexing-the-column-not-the-query&quot;&gt;1. Indexing the column, not the query&lt;/h2&gt;
&lt;p&gt;An index on &lt;code&gt;created_at&lt;/code&gt; does nothing for this query:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;/_astro/ec.p9ju4.css&quot;&gt;&lt;script type=&quot;module&quot; src=&quot;/_astro/ec.0vx5m.js&quot;&gt;&lt;/script&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; events&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; account_id &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; $&lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;ORDER BY&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; created_at &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;DESC&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;LIMIT&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;SELECT * FROM eventsWHERE account_id = $1ORDER BY created_at DESCLIMIT 50;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The planner needs a composite index that matches the access pattern — filter first, then sort:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;INDEX&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;events_account_recent&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; events (account_id, created_at &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;DESC&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;CREATE INDEX events_account_recentON events (account_id, created_at DESC);&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Design indexes by starting from your five most expensive queries, not from the schema.&lt;/p&gt;
&lt;h2 id=&quot;2-functions-in-the-where-clause&quot;&gt;2. Functions in the WHERE clause&lt;/h2&gt;
&lt;p&gt;Wrapping an indexed column in a function silently disables the index:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line highlight del&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#aeb3b8;--1:#4c5259&quot;&gt;-- Sequential scan: the index on email is useless here&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight del&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#fa96a1;--1:#952933&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#004fa9&quot;&gt;lower&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;(email) &lt;/span&gt;&lt;span style=&quot;--0:#fa96a1;--1:#952933&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&apos;alex@example.com&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight ins&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#afb5b9;--1:#4d545b&quot;&gt;-- Either use an expression index...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight ins&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;INDEX&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#c2a8f3;--1:#613aa9&quot;&gt;users_email_lower&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; users (&lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#004fa9&quot;&gt;lower&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;(email));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;-- Sequential scan: the index on email is useless hereWHERE lower(email) = &amp;#x27;alex@example.com&amp;#x27;-- Either use an expression index...CREATE INDEX users_email_lower ON users (lower(email));&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;…or better, store the data in the shape you query it: normalize emails to lowercase on write.&lt;/p&gt;
&lt;h2 id=&quot;3-believing-the-index-is-used-because-it-exists&quot;&gt;3. Believing the index is used because it exists&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;EXPLAIN (ANALYZE, BUFFERS)&lt;/code&gt; is non-negotiable. The planner skips indexes for all sorts of good reasons — small tables, low selectivity, stale statistics — and one &lt;code&gt;ANALYZE events;&lt;/code&gt; has fixed more “index isn’t working” tickets than any amount of query rewriting.&lt;/p&gt;
&lt;h2 id=&quot;4-partial-indexes-left-on-the-table&quot;&gt;4. Partial indexes left on the table&lt;/h2&gt;
&lt;p&gt;If 95% of your queries touch 5% of your rows, index just that slice:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;INDEX&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;orders_pending&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; orders (created_at)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&apos;pending&apos;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;CREATE INDEX orders_pendingON orders (created_at)WHERE status = &amp;#x27;pending&amp;#x27;;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Partial indexes are smaller, hotter in cache, and cheaper to maintain on writes. They’re the highest-leverage indexing feature Postgres has, and most schemas I audit have none.&lt;/p&gt;
&lt;h2 id=&quot;5-forgetting-that-writes-pay-for-reads&quot;&gt;5. Forgetting that writes pay for reads&lt;/h2&gt;
&lt;p&gt;Every index is a tax on &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, and &lt;code&gt;VACUUM&lt;/code&gt;. A table with twelve indexes is a table where every write does twelve extra btree updates. Drop the ones nothing uses:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; indexrelname, idx_scan&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; pg_stat_user_indexes&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; idx_scan &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;ORDER BY&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; pg_relation_size(indexrelid) &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;DESC&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;SELECT indexrelname, idx_scanFROM pg_stat_user_indexesWHERE idx_scan = 0ORDER BY pg_relation_size(indexrelid) DESC;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Zero scans since the last stats reset and it’s not enforcing a constraint? It’s a candidate for deletion.&lt;/p&gt;
&lt;h2 id=&quot;the-meta-lesson&quot;&gt;The meta-lesson&lt;/h2&gt;
&lt;p&gt;None of these are exotic. They’re all visible in one &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; and one look at &lt;code&gt;pg_stat_user_indexes&lt;/code&gt;. Slow queries are rarely mysterious — they’re just unexamined.&lt;/p&gt;
</content:encoded><category>PostgreSQL</category><category>Performance</category><category>Databases</category><author>hello@example.com (Alex Carter)</author></item><item><title>Edge rendering: the trade-offs nobody puts in the launch post</title><link>https://example.com/blog/edge-rendering-tradeoffs</link><guid isPermaLink="true">https://example.com/blog/edge-rendering-tradeoffs</guid><description>We moved a production app to edge rendering and back again for half of it. What the latency numbers actually said, and a decision framework for when the edge earns its complexity.</description><pubDate>Fri, 30 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last year we moved a customer-facing dashboard to edge rendering. Six months later we moved half of it back. Both decisions were correct. Here’s the framework that would have saved us the detour.&lt;/p&gt;
&lt;h2 id=&quot;what-the-edge-actually-buys-you&quot;&gt;What the edge actually buys you&lt;/h2&gt;
&lt;p&gt;Edge rendering moves your &lt;em&gt;compute&lt;/em&gt; close to users. It does nothing about your &lt;em&gt;data&lt;/em&gt;. That distinction is the entire story.&lt;/p&gt;
&lt;p&gt;For a page that renders from nothing — marketing pages, docs, personalization from a cookie — the edge is a strict win. Time-to-first-byte drops from ~300ms to ~40ms globally, and there’s no origin to keep warm.&lt;/p&gt;
&lt;p&gt;For a page that needs your database, the request path becomes:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;/_astro/ec.p9ju4.css&quot;&gt;&lt;script type=&quot;module&quot; src=&quot;/_astro/ec.0vx5m.js&quot;&gt;&lt;/script&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#e1e4e8;--1:#24292e&quot;&gt;user → edge (40ms) → database in us-east-1 (280ms) → edge → user&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;user → edge (40ms) → database in us-east-1 (280ms) → edge → user&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;You moved the rendering next to the user and left the data on the other side of the ocean. One data round-trip erases the entire edge benefit; three round-trips make it &lt;em&gt;slower&lt;/em&gt; than rendering at the origin, where the database is 2ms away.&lt;/p&gt;
&lt;h2 id=&quot;the-waterfall-trap&quot;&gt;The waterfall trap&lt;/h2&gt;
&lt;p&gt;ORMs make sequential queries invisible. This innocent-looking loader does four serial round-trips:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame has-title&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;app/dashboard/loader.ts&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;loader&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;userId&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;Args&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; db.user.&lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;(userId);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight mark&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#004fa9&quot;&gt;team&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; db.team.&lt;/span&gt;&lt;span style=&quot;--0:#c2a8f3;--1:#613aa9&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;(user.teamId);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight mark&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#004fa9&quot;&gt;projects&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; db.projects.&lt;/span&gt;&lt;span style=&quot;--0:#c2a8f3;--1:#613aa9&quot;&gt;byTeam&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;(team.id);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight mark&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#004fa9&quot;&gt;usage&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; db.usage.&lt;/span&gt;&lt;span style=&quot;--0:#c2a8f3;--1:#613aa9&quot;&gt;byTeam&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;(team.id);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight mark&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; { user, team, projects, usage };&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;export async function loader({ userId }: Args) {  const user = await db.user.find(userId);  const team = await db.team.find(user.teamId);  const projects = await db.projects.byTeam(team.id);  const usage = await db.usage.byTeam(team.id);  return { user, team, projects, usage };}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;At the origin: 4 × 2ms — nobody notices. At the edge: 4 × 280ms — your dashboard takes over a second before rendering starts. The edge doesn’t just expose latency, it multiplies whatever query discipline you already lacked.&lt;/p&gt;
&lt;h2 id=&quot;the-decision-framework&quot;&gt;The decision framework&lt;/h2&gt;
&lt;p&gt;Render at the edge when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The page needs &lt;strong&gt;zero&lt;/strong&gt; data round-trips (static, cached, or cookie-derived)&lt;/li&gt;
&lt;li&gt;The data itself is replicated to the edge (KV, D1 read replicas, CDN-cached APIs)&lt;/li&gt;
&lt;li&gt;You can batch the page’s data into &lt;strong&gt;one&lt;/strong&gt; request to a regional API&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Render at the origin when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The page needs your primary database more than once&lt;/li&gt;
&lt;li&gt;You depend on libraries that assume Node (native modules, filesystem access)&lt;/li&gt;
&lt;li&gt;Debugging matters more than TTFB — origin observability is still years ahead&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;where-we-landed&quot;&gt;Where we landed&lt;/h2&gt;
&lt;p&gt;Marketing, docs, and the login shell render at the edge. The dashboard renders in the region where the database lives, behind a CDN that caches the shell. TTFB for the app is 90ms globally — not the 40ms of pure edge, but with none of the waterfall risk.&lt;/p&gt;
&lt;p&gt;The edge is a real tool, not a default. Put compute next to whatever it talks to most — sometimes that’s the user, but more often than the launch posts admit, it’s your database.&lt;/p&gt;
</content:encoded><category>Architecture</category><category>Performance</category><category>Web</category><author>hello@example.com (Alex Carter)</author></item></channel></rss>