<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:content="http://purl.org/rss/1.0/modules/content/"
  xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Josh Hoy's Blog</title>
    <link>https://joshhoy.com</link>
    <description>Notes on cloud, distributed systems, and the bugs that only show up in prod at 2am — by Josh Hoy.</description>
    <language>en-US</language>
    <dc:creator>Josh Hoy</dc:creator>
    <lastBuildDate>Wed, 03 Jun 2026 01:46:34 GMT</lastBuildDate>
    <atom:link href="https://joshhoy.com/rss.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Using Fluent UI v9 without looking like a Fluent UI demo</title>
      <link>https://joshhoy.com/posts/fluent-ui-without-the-fluent-look</link>
      <guid isPermaLink="true">https://joshhoy.com/posts/fluent-ui-without-the-fluent-look</guid>
      <pubDate>Wed, 25 Feb 2026 12:00:00 GMT</pubDate>
      <description>The problemFluent UI v9 is genuinely great. The primitives are accessible, the API is sensible, the runtime cost (Griffel) is near-zero. But the default identity is unmistakable, and a personal site built with default Fluent looks like an enterprise dashboard wearing a costume.Th</description>
      <content:encoded><![CDATA[<h2>The problem</h2><p>Fluent UI v9 is genuinely great. The primitives are accessible, the API is sensible, the runtime cost (Griffel) is near-zero. But the default identity is unmistakable, and a personal site built with default Fluent looks like an enterprise dashboard wearing a costume.</p><h2>The fix</h2><p>You don't have to throw out the components. You just have to override the theme tokens.</p><ul><li>Build a custom <code>BrandVariants</code> ramp — your accent, not theirs</li><li>Override the neutral surface tokens — slate or zinc, not the default grays</li><li>Override the font family tokens — Inter / JetBrains Mono works well</li><li>Use <code>createDarkTheme(brand)</code> / <code>createLightTheme(brand)</code> as a base, then spread your overrides</li></ul><p>The result: Fluent's primitives, your identity. Best of both worlds.</p>]]></content:encoded>
      <category>React</category>
      <category>Fluent UI</category>
      <category>Design</category>
    </item>
    <item>
      <title>Deploying a SPA to Azure for the cost of nothing</title>
      <link>https://joshhoy.com/posts/deploying-spa-azure-blob</link>
      <guid isPermaLink="true">https://joshhoy.com/posts/deploying-spa-azure-blob</guid>
      <pubDate>Fri, 20 Feb 2026 16:30:00 GMT</pubDate>
      <description>Static hosting, the right wayThis site is a single-page app. It runs on Azure Blob Storage with Front Door in front. Total monthly cost is somewhere between "free" and "rounding error".The setupCreate a Storage Account with static website hosting enabledSet the index document to </description>
      <content:encoded><![CDATA[<h2>Static hosting, the right way</h2><p>This site is a single-page app. It runs on Azure Blob Storage with Front Door in front. Total monthly cost is somewhere between "free" and "rounding error".</p><h2>The setup</h2><ol><li>Create a Storage Account with static website hosting enabled</li><li>Set the index document to <code>index.html</code></li><li>Set the error document to <code>index.html</code> too — that's how SPA routing survives a refresh</li><li>Upload your build output to the <code>$web</code> container</li><li>Stick Azure Front Door in front for CDN, TLS, and WAF</li></ol><h2>Why bother?</h2><p>Because the alternative is paying $20/month for a managed platform that runs the same five static files behind a fancier dashboard. The Azure setup is more clicks once, then forever cheap.</p>]]></content:encoded>
      <category>Azure</category>
      <category>DevOps</category>
      <category>Deployment</category>
    </item>
    <item>
      <title>Turn on TypeScript strict mode. Today.</title>
      <link>https://joshhoy.com/posts/typescript-strict-mode</link>
      <guid isPermaLink="true">https://joshhoy.com/posts/typescript-strict-mode</guid>
      <pubDate>Tue, 10 Feb 2026 08:00:00 GMT</pubDate>
      <description>The case for strict modeStrict mode catches an embarrassing fraction of the bugs you'd otherwise hit at runtime. The cost is some short-term pain — annotations you'd been getting away without, narrowing where you'd been hand-waving. The benefit is years of "wait, that should've b</description>
      <content:encoded><![CDATA[<h2>The case for strict mode</h2><p>Strict mode catches an embarrassing fraction of the bugs you'd otherwise hit at runtime. The cost is some short-term pain — annotations you'd been getting away without, narrowing where you'd been hand-waving. The benefit is years of "wait, that should've been impossible" bugs that simply do not happen.</p><h2>What you actually get</h2><ul><li><code>strictNullChecks</code> — no more "undefined is not a function"</li><li><code>noImplicitAny</code> — forces explicit types where the inference gives up</li><li><code>strictFunctionTypes</code> — catches variance bugs in callbacks</li><li><code>noUncheckedIndexedAccess</code> — array access returns <code>T | undefined</code>, which is what it actually is</li></ul><blockquote>Strict mode is not optional for production code. Enable it from day one. Migrating later is harder than you think.</blockquote>]]></content:encoded>
      <category>TypeScript</category>
      <category>Best Practices</category>
    </item>
    <item>
      <title>React in 2026: what I actually do</title>
      <link>https://joshhoy.com/posts/react-best-practices-2026</link>
      <guid isPermaLink="true">https://joshhoy.com/posts/react-best-practices-2026</guid>
      <pubDate>Sun, 01 Feb 2026 14:00:00 GMT</pubDate>
      <description>The short listLess hot takes, more boring choices that age well.Hooks, not classes. Still.Function components with hooks. There is no second answer. If you're writing a class component in 2026 it's either legacy or a render-prop holdout, and either way you should plan to migrate </description>
      <content:encoded><![CDATA[<h2>The short list</h2><p>Less hot takes, more boring choices that age well.</p><h2>Hooks, not classes. Still.</h2><p>Function components with hooks. There is no second answer. If you're writing a class component in 2026 it's either legacy or a render-prop holdout, and either way you should plan to migrate it.</p><h2>Server state and client state are different things</h2><p>Stop putting fetched data in <code>useState</code>. Use TanStack Query (or your team's pick) for anything that came from a server. Cache invalidation is hard, and a library that's spent five years on it has thought about it more than you have.</p><h2>Custom hooks for logic, components for shape</h2><p>If a component is more <code>useEffect</code> than JSX, the logic should live in a hook. The component is a shape — the hook is the brain.</p><blockquote>"But it's only used in one place." Yes. Extract it anyway. Future-you will rename and reuse it within six months.</blockquote>]]></content:encoded>
      <category>React</category>
      <category>JavaScript</category>
      <category>Best Practices</category>
    </item>
    <item>
      <title>Hello, world. Again.</title>
      <link>https://joshhoy.com/posts/hello-world</link>
      <guid isPermaLink="true">https://joshhoy.com/posts/hello-world</guid>
      <pubDate>Thu, 15 Jan 2026 10:30:00 GMT</pubDate>
      <description>Why a new blog?I had a blog. It died. I had another blog. It died too. The pattern was: spend a weekend setting up a CMS, write three posts, never log into the admin panel again.So this time I built the simplest thing that could possibly work — a static React site, posts as data,</description>
      <content:encoded><![CDATA[<h2>Why a new blog?</h2><p>I had a blog. It died. I had another blog. It died too. The pattern was: spend a weekend setting up a CMS, write three posts, never log into the admin panel again.</p><p>So this time I built the simplest thing that could possibly work — a static React site, posts as data, deployed on Azure for the cost of a coffee per month. No CMS to maintain. No "did I update the plugin" anxiety.</p><h2>What goes here</h2><ul><li>Things I'm learning, in public</li><li>Bugs I broke and how I un-broke them</li><li>Weekend projects that escaped the weekend</li><li>The occasional opinion that won't fit in a tweet</li></ul><p>If something here helps you, great. If it makes you angry, also great — that means it's actually saying something. Either way, the RSS feed is at <code>/rss.xml</code>.</p>]]></content:encoded>
      <category>Meta</category>
      <category>Blog</category>
    </item>
  </channel>
</rss>
