<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Prismix</title>
    <description>The latest articles on DEV Community by Prismix (@max_98b3db49c06de66802dcd).</description>
    <link>https://dev.to/max_98b3db49c06de66802dcd</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3939773%2F02ea64f5-7067-445a-9fd0-d86f2f343ee0.png</url>
      <title>DEV Community: Prismix</title>
      <link>https://dev.to/max_98b3db49c06de66802dcd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/max_98b3db49c06de66802dcd"/>
    <language>en</language>
    <item>
      <title>We built a free status monitor for 77 AI APIs. Here's what 6 weeks of data taught us.</title>
      <dc:creator>Prismix</dc:creator>
      <pubDate>Mon, 22 Jun 2026 09:59:03 +0000</pubDate>
      <link>https://dev.to/max_98b3db49c06de66802dcd/we-built-a-free-status-monitor-for-77-ai-apis-heres-what-6-weeks-of-data-taught-us-56ko</link>
      <guid>https://dev.to/max_98b3db49c06de66802dcd/we-built-a-free-status-monitor-for-77-ai-apis-heres-what-6-weeks-of-data-taught-us-56ko</guid>
      <description>&lt;p&gt;Every AI developer has been here: your app is throwing 503s, users are pinging you, and you have 12 browser tabs open — OpenAI status page, Anthropic status page, the GitHub Copilot health page, three different Discord servers — trying to figure out &lt;em&gt;is this me or is it them?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's the problem we set out to solve. &lt;a href="https://prismix.dev" rel="noopener noreferrer"&gt;Prismix&lt;/a&gt; aggregates status from 77 AI services in one place. Six weeks of running it in production taught us some things that might save you time.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem is worse than you think
&lt;/h2&gt;

&lt;p&gt;AI APIs don't fail like traditional infrastructure. They fail in weird, partial ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Degraded performance&lt;/strong&gt; that passes your health checks but makes your product feel broken&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regional outages&lt;/strong&gt; — OpenAI US-East is down while EU is fine, so half your users are affected&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silent rate-limit cascades&lt;/strong&gt; — the API returns 429s but their status page says "operational" for another 20 minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incident lag&lt;/strong&gt; — providers often post status updates 10–30 minutes after engineers are already aware&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The official status pages are optimistic by design. They're customer-facing communications tools, not real-time engineering dashboards. There's nothing wrong with this — but it means you need a different mental model for "is this service down?"&lt;/p&gt;




&lt;h2&gt;
  
  
  What 77 status pages look like in aggregate
&lt;/h2&gt;

&lt;p&gt;When you watch 77 AI services simultaneously, patterns emerge fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenAI&lt;/strong&gt; is the most-watched service (and has the most incidents to watch). The pattern is almost always the same: &lt;code&gt;investigating&lt;/code&gt; → &lt;code&gt;identified&lt;/code&gt; → &lt;code&gt;monitoring&lt;/code&gt; → &lt;code&gt;resolved&lt;/code&gt;, typically in 45–90 minutes. The &lt;code&gt;investigating&lt;/code&gt; phase is where most developers panic — it looks bad but usually resolves without action on your end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anthropic&lt;/strong&gt; runs noticeably clean compared to its API usage growth. Incidents are rarer and shorter. When they do happen, updates arrive faster than most providers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The long tail is interesting.&lt;/strong&gt; Services like Replicate, Runway, ElevenLabs, and Suno have incident patterns that don't correlate with OpenAI at all. If you're routing across multiple providers for redundancy, these are genuinely independent failure domains — worth knowing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The "silent degradation" problem is real.&lt;/strong&gt; Multiple times we've seen a service show "operational" on its status page while our uptime probe was timing out. This is the main reason Prismix shows a latency sparkline per service — the status page is authoritative for &lt;em&gt;announced&lt;/em&gt; incidents, but the probe catches &lt;em&gt;real&lt;/em&gt; ones.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Prismix built (and why it's free)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://prismix.dev" rel="noopener noreferrer"&gt;Prismix&lt;/a&gt; pulls from official status pages, aggregates them into a single dashboard, and adds a few things that the individual pages don't have:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-service latency probes&lt;/strong&gt; — 24-hour sparklines showing actual response times, not just announced incidents. This catches the "silent degradation" cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-service incident timeline&lt;/strong&gt; — &lt;code&gt;/incidents&lt;/code&gt; shows everything that happened across all 77 services in one scrollable feed. Useful for postmortems ("was anything else degraded when our error rate spiked at 3pm Tuesday?").&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Embeddable status badges&lt;/strong&gt; — put a live "OpenAI: operational" badge in your own app's status page with one line of HTML.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Public REST API&lt;/strong&gt; — &lt;code&gt;GET /api/v1/statuses&lt;/code&gt; returns current status for all 77 services as JSON. No auth, no rate limit for reasonable use, CORS open. Free forever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RSS feed&lt;/strong&gt; — &lt;code&gt;/incidents.rss&lt;/code&gt; if you want AI incident updates in your feed reader.&lt;/p&gt;

&lt;p&gt;It's free because it runs entirely on Cloudflare's free tier (Workers + KV). The Pro tier ($10/mo) adds email and webhook alerts for services you care about, but the core dashboard stays free.&lt;/p&gt;




&lt;h2&gt;
  
  
  The technical part (because this is dev.to)
&lt;/h2&gt;

&lt;p&gt;The stack is Astro 5 SSR + Cloudflare Workers + KV. We wrote about the performance walls we hit &lt;a href="https://dev.to/max_98b3db49c06de66802dcd/4-perf-walls-i-hit-shipping-an-ai-hub-on-cloudflare-workers-kv-246"&gt;in a previous post&lt;/a&gt; — the short version is that 77 parallel KV reads per request is a bad idea and a single pre-aggregated snapshot blob is much better.&lt;/p&gt;

&lt;p&gt;One thing that surprised us: KV's free tier gives you 100,000 &lt;em&gt;reads&lt;/em&gt; per day but only 1,000 &lt;em&gt;writes&lt;/em&gt;. The cron job that refreshes status runs every 5 minutes, so every write is conditional — only write if the content actually changed. That dropped writes from ~8,400/day to ~600/day. Monitoring infrastructure has to be cheap to run, otherwise the incentive to keep it free disappears.&lt;/p&gt;




&lt;h2&gt;
  
  
  What we don't know yet — and why we're writing this
&lt;/h2&gt;

&lt;p&gt;Six weeks in, Prismix tracks 77 services with a clean incident timeline and growing usage. What we don't have yet is signal on what matters to &lt;em&gt;you&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Some things we're genuinely uncertain about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Which services are missing?&lt;/strong&gt; The list is opinionated — mostly LLM APIs, popular AI tools, and infrastructure adjacent to them. We've probably missed something obvious in your stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is the latency probe useful?&lt;/strong&gt; It tells you "this service is slow right now" but not "slow compared to what" — no historical baseline yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What would make you actually use this every day?&lt;/strong&gt; A Slack bot? A PagerDuty integration? Something in your terminal?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of that resonates, drop a comment. Honest feedback shapes what gets built next.&lt;/p&gt;

&lt;p&gt;Live at &lt;a href="https://prismix.dev" rel="noopener noreferrer"&gt;prismix.dev&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Also at Prismix: an &lt;a href="https://prismix.dev/mcp" rel="noopener noreferrer"&gt;MCP server directory&lt;/a&gt; with 500+ servers and a &lt;a href="https://prismix.dev/news" rel="noopener noreferrer"&gt;curated AI news feed&lt;/a&gt; — but the status monitoring is the part we're most curious to hear about.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cloudflare</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>4 perf walls I hit shipping an AI hub on Cloudflare Workers KV</title>
      <dc:creator>Prismix</dc:creator>
      <pubDate>Tue, 19 May 2026 09:02:22 +0000</pubDate>
      <link>https://dev.to/max_98b3db49c06de66802dcd/4-perf-walls-i-hit-shipping-an-ai-hub-on-cloudflare-workers-kv-246</link>
      <guid>https://dev.to/max_98b3db49c06de66802dcd/4-perf-walls-i-hit-shipping-an-ai-hub-on-cloudflare-workers-kv-246</guid>
      <description>&lt;p&gt;Status pages don't aggregate. AI news lives in 60 RSS feeds. MCP servers are scattered across awesome-lists.&lt;/p&gt;

&lt;p&gt;So I built Prismix - one URL for all three - on Cloudflare Workers + Astro 5. Here's what broke and what fixed it.&lt;/p&gt;

&lt;p&gt;Live: &lt;a href="https://prismix.dev" rel="noopener noreferrer"&gt;prismix.dev&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this exists
&lt;/h2&gt;

&lt;p&gt;Three daily frustrations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;OpenAI / Anthropic status pages don't aggregate — I had 5 tabs open every morning&lt;/li&gt;
&lt;li&gt;AI news lives in 60 different RSS feeds and Twitter accounts&lt;/li&gt;
&lt;li&gt;MCP servers are scattered across awesome-lists with no canonical index&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Prismix solves all three on one page.&lt;/p&gt;




&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Framework&lt;/td&gt;
&lt;td&gt;Astro 5 SSR&lt;/td&gt;
&lt;td&gt;Islands architecture, $0 hosting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hosting&lt;/td&gt;
&lt;td&gt;Cloudflare Pages&lt;/td&gt;
&lt;td&gt;Free tier covers MVP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;Cloudflare Workers KV&lt;/td&gt;
&lt;td&gt;No DB to operate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;Self-rolled (email + GitHub)&lt;/td&gt;
&lt;td&gt;Saved $25/mo vs Clerk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payments&lt;/td&gt;
&lt;td&gt;Ko-fi&lt;/td&gt;
&lt;td&gt;No Stripe overhead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests&lt;/td&gt;
&lt;td&gt;Vitest + MockKV&lt;/td&gt;
&lt;td&gt;1112 tests, runs in 8s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The whole thing costs $0 to run. That constraint forced 4 lessons.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 1 — KV writes are the rare resource
&lt;/h2&gt;

&lt;p&gt;Cloudflare's free tier gives you 100,000 KV reads/day but only &lt;strong&gt;1,000 writes&lt;/strong&gt;. I hit the wall at 1pm on day 12.&lt;/p&gt;

&lt;p&gt;The fix wasn't fewer writes - it was a diff-or-skip pattern: read the existing value, compare, only write if changed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setIfChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;KVNamespace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// skip&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cron writes dropped from &lt;strong&gt;8,400/day → 600/day&lt;/strong&gt;. Reads are 100× more generous - trade them freely.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 2 - JSON.parse has a cliff at 1 MB
&lt;/h2&gt;

&lt;p&gt;The news index grew to 5,000 items. Each KV read returned a 3.2 MB JSON blob. SSR for &lt;code&gt;/news&lt;/code&gt; was taking 330ms just to parse.&lt;/p&gt;

&lt;p&gt;The fix was splitting the index into a "first page" slice (250 items, 165 KB) and a meta blob (facet counts, 12 KB). When the user hits &lt;code&gt;/news&lt;/code&gt; with no filters or pagination, we only read the slice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// hot path — 17ms&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;news:index:latest:firstpage:v1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;news:index:latest:meta:v1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// cold path (filters, sort, page&amp;gt;1) falls back to full index&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parse time: &lt;strong&gt;330ms → 17ms&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 3 — CF Workers subrequest concurrency is per-request
&lt;/h2&gt;

&lt;p&gt;This one bit me hardest. &lt;code&gt;/mcp&lt;/code&gt; was rendering in 5.5s. Server-Timing showed 4.31s spent on KV reads.&lt;/p&gt;

&lt;p&gt;I assumed 60 parallel &lt;code&gt;kv.get()&lt;/code&gt; calls would run in parallel. They don't — Workers caps concurrent subrequests at ~6, so the rest queue.&lt;/p&gt;

&lt;p&gt;The fix: a denormalised snapshot blob updated by cron once a day.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before: 60 parallel reads, serialised by concurrency limit → 4.31s&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;likes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slugs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`mcp:likes:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

&lt;span class="c1"&gt;// After: 1 blob read → 30ms&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;snapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mcp:bulk-engagement:v1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Total &lt;code&gt;/mcp&lt;/code&gt; SSR time: &lt;strong&gt;5460ms → 380ms&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 4 — Even fast SSR feels broken without instant feedback
&lt;/h2&gt;

&lt;p&gt;After fixing perf, the site loaded in 200-400ms. Users still complained it was slow.&lt;/p&gt;

&lt;p&gt;The problem: the browser doesn't repaint the address bar until the new HTML arrives. For 200ms the user sees nothing — they click again, thinking the button didn't register.&lt;/p&gt;

&lt;p&gt;Two fixes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pointerdown progress bar&lt;/strong&gt; — render a 2px green bar at the top of the screen on &lt;code&gt;pointerdown&lt;/code&gt;, before the navigation even fires.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Astro &lt;code&gt;tap&lt;/code&gt; prefetch&lt;/strong&gt; — start fetching the next page on &lt;code&gt;pointerdown&lt;/code&gt;, finish during the click delay.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pointerdown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;closest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a[href]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;showProgressBar&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perceived load time dropped from "slow" to "instant" without a single SSR optimisation.&lt;/p&gt;




&lt;h2&gt;
  
  
  What didn't work
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Algolia for MCP search&lt;/strong&gt; — overkill; switched to in-memory tokenised index&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stripe&lt;/strong&gt; — Ko-fi's webhook + idempotency was 1 day vs Stripe's week&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clerk&lt;/strong&gt; — self-rolled OTP + GitHub OAuth was 3 days, $0/mo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Actions &lt;code&gt;schedule:&lt;/code&gt;&lt;/strong&gt; — drifts by minutes, silently skips. Moved cron to cron-job.org&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Per-service alert thresholds (Pro feature)&lt;/li&gt;
&lt;li&gt;AI news instant-notify via webhook (Discord / Slack)&lt;/li&gt;
&lt;li&gt;Public read API + SDK&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've shipped on Cloudflare Workers and hit different walls, I'd love to hear what broke for you. Comments open.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://prismix.dev" rel="noopener noreferrer"&gt;prismix.dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
      <category>ai</category>
      <category>cloudflare</category>
    </item>
  </channel>
</rss>
