<?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: Nathan Schram</title>
    <description>The latest articles on DEV Community by Nathan Schram (@nathanschram).</description>
    <link>https://dev.to/nathanschram</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%2F3407497%2F7010b19e-8815-427b-9c52-de6b1c7bcf66.png</url>
      <title>DEV Community: Nathan Schram</title>
      <link>https://dev.to/nathanschram</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nathanschram"/>
    <language>en</language>
    <item>
      <title>Core Web Vitals in 2026: what small businesses need to know</title>
      <dc:creator>Nathan Schram</dc:creator>
      <pubDate>Sat, 18 Jul 2026 04:38:02 +0000</pubDate>
      <link>https://dev.to/nathanschram/core-web-vitals-in-2026-what-small-businesses-need-to-know-4p34</link>
      <guid>https://dev.to/nathanschram/core-web-vitals-in-2026-what-small-businesses-need-to-know-4p34</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Only 48% of mobile websites pass all three of Google's Core Web Vitals (&lt;a href="https://almanac.httparchive.org/en/2025/performance" rel="noopener noreferrer"&gt;HTTP Archive&lt;/a&gt;, 2025). If your site loads slowly, shifts around while loading, or feels unresponsive when people tap buttons, you're probably losing customers without realising it. This guide explains the three metrics in plain English and gives you a checklist to hand your developer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What are Core Web Vitals?&lt;/li&gt;
&lt;li&gt;Why should a small business owner care?&lt;/li&gt;
&lt;li&gt;LCP: how fast does your page actually appear?&lt;/li&gt;
&lt;li&gt;INP: does your site respond when people tap things?&lt;/li&gt;
&lt;li&gt;CLS: does your page stay still while loading?&lt;/li&gt;
&lt;li&gt;Do Core Web Vitals actually affect Google rankings?&lt;/li&gt;
&lt;li&gt;The WordPress problem (and what to do about it)&lt;/li&gt;
&lt;li&gt;How to check your own scores in five minutes&lt;/li&gt;
&lt;li&gt;Six things to ask your developer today&lt;/li&gt;
&lt;li&gt;The accessibility bonus you get for free&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you run a small business in Australia and you have a website, you're already ahead of 59% of Australian small businesses who don't have one at all (&lt;a href="https://www.godaddy.com/resources/au/stories/study-reveals-why-59-of-australian-small-businesses-dont-have-a-website" rel="noopener noreferrer"&gt;GoDaddy&lt;/a&gt;, 2024). But having a website isn't the same as having a website that works well.&lt;/p&gt;

&lt;p&gt;I spend a lot of time auditing websites for clients, and I keep running into the same problem. Business owners have spent good money getting a site built, but nobody told them it loads in six seconds on a phone, or that the buttons feel broken, or that the text jumps around while you're trying to read it. They didn't know these things had names. They didn't know Google was measuring them.&lt;/p&gt;

&lt;p&gt;Google calls these measurements Core Web Vitals. There are three of them, they've changed recently, and they affect both your search rankings and whether people actually stick around long enough to buy something. I'm going to explain all three in plain English, show you exactly how to check your own scores, and give you a checklist you can hand to whoever built your website.&lt;/p&gt;

&lt;p&gt;No jargon. No developer prerequisites. Just the stuff that actually matters for your business.&lt;/p&gt;




&lt;h2&gt;
  
  
  What are Core Web Vitals?
&lt;/h2&gt;

&lt;p&gt;Core Web Vitals are three specific measurements Google uses to evaluate how your website feels to real visitors, based on data from actual Chrome browser sessions (&lt;a href="https://developers.google.com/search/docs/appearance/core-web-vitals" rel="noopener noreferrer"&gt;Google Search Central&lt;/a&gt;, 2026). Not how it looks. Not what it says. How it &lt;em&gt;feels&lt;/em&gt; to use - and whether people stick around or leave.&lt;/p&gt;

&lt;p&gt;Think of it like a restaurant inspection, but for websites. Google checks three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;LCP (Largest Contentful Paint)&lt;/strong&gt; - How fast does the main content appear? Like waiting for the menu to arrive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;INP (Interaction to Next Paint)&lt;/strong&gt; - Does the site respond when you tap things? Like pressing the elevator button and nothing happening.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLS (Cumulative Layout Shift)&lt;/strong&gt; - Does the page stay still while loading? Like reading a newspaper and someone keeps moving it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Google evaluates these at the 75th percentile of real user data (&lt;a href="https://web.dev/articles/defining-core-web-vitals-thresholds" rel="noopener noreferrer"&gt;web.dev&lt;/a&gt;, 2025). That means they're not testing your site on a fast computer with fibre internet. They're looking at how it performs for your actual visitors, including the ones on 4G in regional areas.&lt;/p&gt;

&lt;p&gt;Each metric gets a traffic-light rating: green (good), amber (needs improvement), or red (poor). You need all three green to pass.&lt;/p&gt;

&lt;p&gt;Only 48% of websites manage that on mobile (&lt;a href="https://almanac.httparchive.org/en/2025/performance" rel="noopener noreferrer"&gt;HTTP Archive&lt;/a&gt;, 2025). More than half of all websites fail. That number has been climbing - it was 36% in 2023 and 44% in 2024 - but there's still a long way to go.&lt;/p&gt;

&lt;p&gt;The thresholds for each metric:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Good&lt;/th&gt;
&lt;th&gt;Needs improvement&lt;/th&gt;
&lt;th&gt;Poor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LCP (loading)&lt;/td&gt;
&lt;td&gt;Under 2.5 seconds&lt;/td&gt;
&lt;td&gt;2.5 - 4.0 seconds&lt;/td&gt;
&lt;td&gt;Over 4.0 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INP (responsiveness)&lt;/td&gt;
&lt;td&gt;Under 200ms&lt;/td&gt;
&lt;td&gt;200 - 500ms&lt;/td&gt;
&lt;td&gt;Over 500ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLS (stability)&lt;/td&gt;
&lt;td&gt;Under 0.1&lt;/td&gt;
&lt;td&gt;0.1 - 0.25&lt;/td&gt;
&lt;td&gt;Over 0.25&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Why should a small business owner care?
&lt;/h2&gt;

&lt;p&gt;A 0.1-second improvement in mobile load time increased retail conversions by 8.4% and travel conversions by 10.1% across a study of 37 brand websites and 30 million user sessions (&lt;a href="https://web.dev/case-studies/milliseconds-make-millions" rel="noopener noreferrer"&gt;Deloitte/Google&lt;/a&gt;, 2020). For a small business doing $10,000 a month online, that's $840 extra per month from a change your visitors won't consciously notice.&lt;/p&gt;

&lt;p&gt;The business case isn't theoretical. Vodafone ran an A/B test with roughly 100,000 clicks per day per variant and found that a 31% improvement in LCP led to 8% more sales, 15% better lead generation, and 11% better cart completion (&lt;a href="https://web.dev/case-studies/vodafone" rel="noopener noreferrer"&gt;web.dev&lt;/a&gt;, 2021). Fotocasa, a Spanish real estate platform, moved their INP score from "needs improvement" to "good" and saw a 27% increase in contact leads (&lt;a href="https://web.dev/case-studies/fotocasa-cwv" rel="noopener noreferrer"&gt;web.dev&lt;/a&gt;, 2024).&lt;/p&gt;

&lt;p&gt;You might be thinking those are big companies. Fair point. But the maths scales down. If 53% of mobile visitors abandon sites that take longer than three seconds to load (&lt;a href="https://www.thinkwithgoogle.com/marketing-strategies/app-and-mobile/mobile-page-speed-new-industry-benchmarks/" rel="noopener noreferrer"&gt;Google&lt;/a&gt;, 2017), and your site takes five seconds, you're losing people before they even see what you sell.&lt;/p&gt;

&lt;p&gt;The bounce rate curve tells the story:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqajg5pq82fzhvw5fkryx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqajg5pq82fzhvw5fkryx.png" alt="Lollipop chart showing bounce rate by page load time: 1 second 7%, 3 seconds 11%, 5 seconds 38%. Source: Google Think with Google / Tooltester" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The cliff at five seconds is the important bit. That's where you stop losing a few visitors and start losing most of them.&lt;/p&gt;

&lt;p&gt;And in Australia specifically, 97.1% of the population is online, with 34.1 million mobile connections for a population of about 27 million (&lt;a href="https://datareportal.com/reports/digital-2026-australia" rel="noopener noreferrer"&gt;DataReportal&lt;/a&gt;, 2026). People have multiple devices. They're browsing on their phones at lunch, on the tram, waiting at the school gate. If your site doesn't work well on mobile, it doesn't work well for the majority of your customers.&lt;/p&gt;

&lt;p&gt;If your business serves a specific city or suburb, page speed feeds directly into your &lt;a href="https://nathanschram.com/post/local-seo-melbourne-2026" rel="noopener noreferrer"&gt;local SEO Map Pack rankings&lt;/a&gt; too — Google measures page experience as part of how it picks the top three local results.&lt;/p&gt;




&lt;h2&gt;
  
  
  LCP: how fast does your page actually appear?
&lt;/h2&gt;

&lt;p&gt;LCP measures how long it takes for the largest piece of content on your page to become visible, and it's the metric most people think of when they talk about "page speed." Google considers under 2.5 seconds good, 2.5 to 4 seconds needs improvement, and over 4 seconds poor (&lt;a href="https://developers.google.com/search/docs/appearance/core-web-vitals" rel="noopener noreferrer"&gt;Google Search Central&lt;/a&gt;, 2026).&lt;/p&gt;

&lt;p&gt;In practice, this is usually your hero image, a large heading, or a video thumbnail. It's the moment your visitor stops staring at a white screen and starts seeing your actual website.&lt;/p&gt;

&lt;p&gt;Currently, 62% of mobile websites pass LCP, but only 74% on desktop (&lt;a href="https://almanac.httparchive.org/en/2025/performance" rel="noopener noreferrer"&gt;HTTP Archive&lt;/a&gt;, 2025). That 12-percentage-point gap matters because Google uses mobile-first indexing, meaning Google ranks your site based on its mobile performance, not its desktop performance (&lt;a href="https://developers.google.com/search/docs/crawling-indexing/mobile/mobile-sites-mobile-first-indexing" rel="noopener noreferrer"&gt;Google Search Central&lt;/a&gt;, 2026).&lt;/p&gt;

&lt;h3&gt;
  
  
  What causes bad LCP
&lt;/h3&gt;

&lt;p&gt;The usual suspects for small business websites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Massive images.&lt;/strong&gt; A 4MB hero photo that looks identical at 200KB after compression. This is the single most common issue I see in audits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slow hosting.&lt;/strong&gt; Cheap shared hosting where your site competes with hundreds of others for server resources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Too many fonts.&lt;/strong&gt; Loading four different Google Fonts when one or two would do the job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Render-blocking scripts.&lt;/strong&gt; JavaScript and CSS files that the browser has to download and process before it can show anything. 67.7% of websites request at least one render-blocking third party (&lt;a href="https://calendar.perfplanet.com/2025/third-parties-and-single-points-of-failure/" rel="noopener noreferrer"&gt;HTTP Archive&lt;/a&gt;, 2025).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No caching.&lt;/strong&gt; Your server rebuilds every page from scratch for every visitor instead of serving a cached copy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Quick test
&lt;/h3&gt;

&lt;p&gt;Open your site on your phone using a mobile data connection (turn off WiFi). Count "one Mississippi, two Mississippi" from when you tap the link to when you see the main content. If you get past three, your LCP probably needs work.&lt;/p&gt;




&lt;h2&gt;
  
  
  INP: does your site respond when people tap things?
&lt;/h2&gt;

&lt;p&gt;INP replaced an older metric called FID in March 2024 and measures how long your page takes to visually respond after a user taps, clicks, or types something (&lt;a href="https://web.dev/articles/inp" rel="noopener noreferrer"&gt;web.dev&lt;/a&gt;, 2025). Good is under 200 milliseconds - that's a fifth of a second. Over 500 milliseconds is poor.&lt;/p&gt;

&lt;p&gt;You know that feeling when you tap a button on a website and nothing happens? So you tap it again, and then two things happen at once? That's bad INP. The page is doing so much work behind the scenes that it can't respond to your taps quickly enough.&lt;/p&gt;

&lt;p&gt;This is the newest Core Web Vital, and it's catching a lot of websites off guard. Desktop handles it fine - 97% of desktop pages pass. But only 77% of mobile pages do, and only 53% of the top 1,000 most-visited websites pass on mobile (&lt;a href="https://almanac.httparchive.org/en/2025/performance" rel="noopener noreferrer"&gt;HTTP Archive&lt;/a&gt;, 2025). If the biggest sites in the world are struggling with this, smaller sites loading chat widgets, analytics scripts, and social media plugins are probably doing worse.&lt;/p&gt;

&lt;h3&gt;
  
  
  What causes bad INP
&lt;/h3&gt;

&lt;p&gt;The primary culprit is JavaScript. Specifically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Third-party scripts.&lt;/strong&gt; Live chat widgets, analytics packages, social media embeds, ad networks. Each one adds processing time. Industry analysis suggests that ten or more third-party scripts can add over 500 milliseconds of interaction delay (&lt;a href="https://www.spycemedia.com/insights/beyond-mobile-first-how-googles-2025-inp-update-impacts-your-website" rel="noopener noreferrer"&gt;SpyceMedia&lt;/a&gt;, 2025).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heavy page builders.&lt;/strong&gt; Some WordPress page builders add enormous amounts of code. One analysis found Elementor alone adds over 21MB of unzipped code to a site (&lt;a href="https://www.corewebvitals.io/core-web-vitals/wordpress-guide" rel="noopener noreferrer"&gt;CoreWebVitals.io&lt;/a&gt;, 2026).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Too much happening on page load.&lt;/strong&gt; Animations, carousels, pop-ups, floating elements - all competing for the browser's attention.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The mobile gap
&lt;/h3&gt;

&lt;p&gt;Here's what makes INP particularly tricky: according to one industry analysis, the median Total Blocking Time (a related measurement) is 67 milliseconds on desktop but 1,209 milliseconds on mobile (&lt;a href="https://hashmeta.com/blog/interaction-to-next-paint-inp-complete-optimization-guide-for-2025/" rel="noopener noreferrer"&gt;Hashmeta&lt;/a&gt;, 2025). That's roughly an 18x difference. Mobile processors are significantly weaker than desktop ones. All that JavaScript your developer tested on their MacBook Pro runs very differently on a visitor's three-year-old Android phone.&lt;/p&gt;




&lt;h2&gt;
  
  
  CLS: does your page stay still while loading?
&lt;/h2&gt;

&lt;p&gt;CLS measures how much your page content shifts around unexpectedly while it loads. A score under 0.1 is good (&lt;a href="https://developers.google.com/search/docs/appearance/core-web-vitals" rel="noopener noreferrer"&gt;Google Search Central&lt;/a&gt;, 2026). This is the one metric where desktop actually performs worse than mobile - 81% of mobile pages pass but only 72% of desktop pages do (&lt;a href="https://almanac.httparchive.org/en/2025/performance" rel="noopener noreferrer"&gt;HTTP Archive&lt;/a&gt;, 2025).&lt;/p&gt;

&lt;p&gt;If you've ever been reading an article and the text suddenly jumps because an ad loaded above it, or you've tried to tap a button and hit the wrong thing because the layout shifted, that's CLS in action.&lt;/p&gt;

&lt;h3&gt;
  
  
  What causes bad CLS
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Images without dimensions.&lt;/strong&gt; If your HTML doesn't specify how big an image will be, the browser renders the text first and then shoves everything down when the image loads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Late-loading ads.&lt;/strong&gt; Display ads that inject themselves into the page after the initial content has rendered.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web fonts loading late.&lt;/strong&gt; The browser renders text in a fallback font, then swaps to the custom font at a different size, shifting everything around.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic content injection.&lt;/strong&gt; Cookie consent banners, newsletter pop-ups, or promotional bars that push content down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iframes without reserved space.&lt;/strong&gt; Embedded maps, videos, or social media widgets that expand after the page has already drawn itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CLS is usually the easiest of the three metrics to fix. Setting image dimensions and reserving space for ads solves most problems.&lt;/p&gt;




&lt;h2&gt;
  
  
  Do Core Web Vitals actually affect Google rankings?
&lt;/h2&gt;

&lt;p&gt;Google's official position is that Core Web Vitals are a ranking signal, but content relevance matters more. Their documentation states: "For many queries, there is lots of helpful content available. Having a great page experience can contribute to success in Search, in such cases" (&lt;a href="https://developers.google.com/search/docs/appearance/page-experience" rel="noopener noreferrer"&gt;Google Search Central&lt;/a&gt;, 2026).&lt;/p&gt;

&lt;p&gt;John Mueller from Google has been more direct. In 2024, he said Core Web Vitals are "not giant factors in ranking" but described them as more than just a tiebreaker (&lt;a href="https://www.searchenginejournal.com/googles-mueller-dismisses-core-web-vitals-impact-on-rankings/530715/" rel="noopener noreferrer"&gt;Search Engine Journal&lt;/a&gt;, 2024).&lt;/p&gt;

&lt;p&gt;The way I explain it to clients: if you sell plumbing supplies in Melbourne and you're the only result with good content about a specific fitting, your page speed doesn't matter much for that search. But if there are ten similar pages competing for "best plumber Melbourne," the one that loads fastest and feels smoothest has an edge.&lt;/p&gt;

&lt;p&gt;Think of it as a tiebreaker that gets more powerful the more competitive your niche is. And given that December 2025 saw Google roll out another core algorithm update across 18 days (&lt;a href="https://searchengineland.com/google-december-2025-core-update-rollout-is-now-complete-466362" rel="noopener noreferrer"&gt;Search Engine Land&lt;/a&gt;, 2025), the bar keeps rising.&lt;/p&gt;

&lt;p&gt;The bigger impact is indirect. Faster sites have lower bounce rates, higher engagement, and better conversion rates. Those user behaviour signals probably influence rankings more than the CWV scores themselves. If you want to understand how page speed fits into a broader &lt;a href="https://nathanschram.com/services/seo/" rel="noopener noreferrer"&gt;SEO and search optimisation strategy&lt;/a&gt;, it's one piece of a much larger puzzle - but it's the piece most business owners can actually measure.&lt;/p&gt;

&lt;p&gt;The same speed signals also help &lt;a href="https://nathanschram.com/post/what-is-geo" rel="noopener noreferrer"&gt;AI crawlers parse your site for ChatGPT and Perplexity citations&lt;/a&gt;. Slow, JavaScript-heavy pages are harder for AI search engines to read, and AI Overviews are increasingly part of how customers find local businesses.&lt;/p&gt;




&lt;h2&gt;
  
  
  The WordPress problem (and what to do about it)
&lt;/h2&gt;

&lt;p&gt;WordPress powers roughly 43% of all websites (&lt;a href="https://wordpress.com/blog/2025/04/17/wordpress-market-share/" rel="noopener noreferrer"&gt;W3Techs via WordPress.com&lt;/a&gt;, 2025). If you run a small business website, there's a decent chance it's on WordPress. And WordPress has a Core Web Vitals problem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw8yon9vrgd4br4tr3g5m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw8yon9vrgd4br4tr3g5m.png" alt="Horizontal bar chart comparing CMS platforms on Core Web Vitals mobile pass rates: Duda 84%, Shopify 75%, Wix 71%, Squarespace 68%, Drupal 59%, WordPress 43%. Source: Search Engine Journal / CrUX / HTTP Archive" width="800" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Only 43.44% of WordPress sites pass all three Core Web Vitals on mobile, compared to 75.22% for Shopify, 70.76% for Wix, and 67.66% for Squarespace (&lt;a href="https://www.searchenginejournal.com/2025-core-web-vitals-cms-rankings/552679/" rel="noopener noreferrer"&gt;Search Engine Journal&lt;/a&gt;, 2025).&lt;/p&gt;

&lt;p&gt;That's not because WordPress is bad. It's because WordPress gives you freedom, and freedom includes the freedom to install 25 plugins, a bloated theme, and three different analytics scripts. Shopify and Squarespace limit what you can do, and that limitation is actually protecting their users' page speed.&lt;/p&gt;

&lt;h3&gt;
  
  
  It's not hopeless
&lt;/h3&gt;

&lt;p&gt;WordPress 6.8 introduced speculative loading (the Speculation Rules API), which pre-loads pages a visitor is likely to click on. Sites that enabled this saw roughly a 1.9% improvement in LCP passing rates (&lt;a href="https://make.wordpress.org/core/2025/03/06/speculative-loading-in-6-8/" rel="noopener noreferrer"&gt;Make WordPress Core&lt;/a&gt;, 2025). Small improvement, but it's free.&lt;/p&gt;

&lt;p&gt;The real wins on WordPress come from a conversation with your developer about plugins. Most WordPress performance issues come from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Plugin bloat.&lt;/strong&gt; A typical WordPress site runs 20-30 plugins (&lt;a href="https://www.corewebvitals.io/core-web-vitals/wordpress-guide" rel="noopener noreferrer"&gt;CoreWebVitals.io&lt;/a&gt;, 2026). Each one adds CSS and JavaScript.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heavy themes.&lt;/strong&gt; Multi-purpose themes that load features you never use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Page builders.&lt;/strong&gt; Some are more efficient than others. Ask your developer which one they're using and what it costs in performance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unoptimised images.&lt;/strong&gt; WordPress doesn't compress uploaded images aggressively by default.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your site is on Shopify, Squarespace, or Wix, you get less control but better defaults. The platform handles most performance optimisation for you. Worth knowing when you're choosing (or reconsidering) a platform.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to check your own scores in five minutes
&lt;/h2&gt;

&lt;p&gt;You don't need technical knowledge for this. Open &lt;a href="https://pagespeed.web.dev" rel="noopener noreferrer"&gt;pagespeed.web.dev&lt;/a&gt; in your browser, type in your website address, and click "Analyze."&lt;/p&gt;

&lt;p&gt;It takes about 30 seconds. You'll get a score out of 100 and individual grades for each Core Web Vital. The important bit is the section labelled "Core Web Vitals Assessment" at the top. You're looking for either a green pass or a red fail.&lt;/p&gt;

&lt;p&gt;Run the test on "Mobile" first (it's the default tab). That's what Google uses for ranking.&lt;/p&gt;

&lt;h3&gt;
  
  
  What to do with the results
&lt;/h3&gt;

&lt;p&gt;If you pass all three: brilliant. You're in the better-performing half of the internet. Keep an eye on it quarterly to make sure nothing's slipped.&lt;/p&gt;

&lt;p&gt;If you fail one or more: don't panic. Screenshot the results and send them to whoever manages your website. The report lists specific problems and suggestions, ordered by impact. Your developer will understand what to fix. You don't need to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other free tools worth knowing about
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Search Console&lt;/strong&gt; (&lt;a href="https://search.google.com/search-console" rel="noopener noreferrer"&gt;search.google.com/search-console&lt;/a&gt;) - shows your Core Web Vitals over time, based on real visitor data. Requires a quick setup but then runs automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chrome DevTools Lighthouse&lt;/strong&gt; - built into the Chrome browser. Right-click any page, click "Inspect," go to the "Lighthouse" tab, and run an audit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GTmetrix&lt;/strong&gt; (&lt;a href="https://gtmetrix.com" rel="noopener noreferrer"&gt;gtmetrix.com&lt;/a&gt;) - shows a detailed waterfall chart of what's loading and how long each thing takes.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Six things to ask your developer today
&lt;/h2&gt;

&lt;p&gt;You don't need to understand the technical details to get better Core Web Vitals. You just need to ask the right questions. Here's a checklist you can send directly to your developer or agency:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;"Can you convert our images to WebP format?"&lt;/strong&gt; WebP images are 25-34% smaller than JPEG with no visible quality loss (&lt;a href="https://developers.google.com/speed/webp" rel="noopener noreferrer"&gt;Google&lt;/a&gt;). This is usually the single biggest quick win.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;"Can you add lazy loading to images below the fold?"&lt;/strong&gt; Images that aren't visible when the page first loads shouldn't be loaded until the visitor scrolls down to them. One line of code: &lt;code&gt;loading="lazy"&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;"Are we deferring non-critical JavaScript?"&lt;/strong&gt; Analytics, chat widgets, social media embeds, and tracking scripts don't need to load before the main content. They should load after.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;"Do our images have width and height attributes?"&lt;/strong&gt; This prevents layout shift. The browser reserves the correct space before the image loads.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;"Are we using a CDN?"&lt;/strong&gt; A Content Delivery Network serves your site from a server geographically close to each visitor. Cloudflare offers a free tier that handles this for most small business websites.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;"Can you run PageSpeed Insights and show me the results?"&lt;/strong&gt; If your developer can't explain your Core Web Vitals scores, that tells you something.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these are expensive changes. Most take a competent developer an hour or two. And the odds are good that your site has low-hanging fruit to pick - the majority of websites are loading unnecessary scripts before the main content even appears. If you don't have a developer you trust, or you're not sure where to start, &lt;a href="https://nathanschram.com/get-in-touch/" rel="noopener noreferrer"&gt;get in touch&lt;/a&gt; and I can point you in the right direction.&lt;/p&gt;




&lt;h2&gt;
  
  
  The accessibility bonus you get for free
&lt;/h2&gt;

&lt;p&gt;Something that rarely comes up in the Core Web Vitals conversation: fixing your CWV scores often fixes accessibility problems at the same time.&lt;/p&gt;

&lt;p&gt;94.8% of the top one million homepages have at least one WCAG accessibility failure, with an average of 51 errors per page (&lt;a href="https://webaim.org/projects/million/" rel="noopener noreferrer"&gt;WebAIM&lt;/a&gt;, 2025). The most common? Insufficient text contrast, affecting 79.1% of pages.&lt;/p&gt;

&lt;p&gt;But the overlap goes deeper than contrast ratios. INP directly maps to keyboard accessibility. When your page is too busy processing JavaScript to respond to a tap, it's also too busy to respond to a keyboard press. Screen reader users, people navigating with a keyboard or switch device - they all experience the same delay. Fix your INP and you've fixed their experience too.&lt;/p&gt;

&lt;p&gt;CLS affects focus order. When content shifts unexpectedly, the element a keyboard user has focused on can move off-screen. That's not just annoying, it can make a site unusable for people with motor disabilities.&lt;/p&gt;

&lt;p&gt;The European Accessibility Act took effect in June 2025, mandating accessibility compliance for websites in the EU (&lt;a href="https://ec.europa.eu/social/main.jsp?catId=1202" rel="noopener noreferrer"&gt;European Commission&lt;/a&gt;, 2025). Australia's Disability Discrimination Act has similar requirements. Improving your Core Web Vitals is a step toward meeting both.&lt;/p&gt;

&lt;p&gt;Two problems. One set of fixes. That's a good deal for any small business.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What are the three Core Web Vitals in 2026?
&lt;/h3&gt;

&lt;p&gt;The three Core Web Vitals are LCP (Largest Contentful Paint, measuring load speed), INP (Interaction to Next Paint, measuring responsiveness), and CLS (Cumulative Layout Shift, measuring visual stability). INP replaced the older FID metric in March 2024. Google uses all three to assess your site's user experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much do Core Web Vitals affect Google rankings?
&lt;/h3&gt;

&lt;p&gt;Core Web Vitals are a confirmed ranking signal, but content relevance still outweighs them. Google's John Mueller described them as "not giant factors" but more than a tiebreaker (&lt;a href="https://www.searchenginejournal.com/googles-mueller-dismisses-core-web-vitals-impact-on-rankings/530715/" rel="noopener noreferrer"&gt;Search Engine Journal&lt;/a&gt;, 2024). They matter most in competitive niches where multiple pages have similar content quality.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is WordPress bad for Core Web Vitals?
&lt;/h3&gt;

&lt;p&gt;WordPress isn't inherently bad, but it has the lowest CWV pass rate of major platforms at 43.44%, compared to Shopify at 75.22% (&lt;a href="https://www.searchenginejournal.com/2025-core-web-vitals-cms-rankings/552679/" rel="noopener noreferrer"&gt;Search Engine Journal&lt;/a&gt;, 2025). The problem is usually plugin bloat and heavy themes, not WordPress itself. A well-optimised WordPress site can score perfectly.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I check my Core Web Vitals score?
&lt;/h3&gt;

&lt;p&gt;Go to &lt;a href="https://pagespeed.web.dev" rel="noopener noreferrer"&gt;pagespeed.web.dev&lt;/a&gt;, enter your website address, and click "Analyze." The test takes about 30 seconds and shows you pass/fail grades for each Core Web Vital. Run the mobile test first, as that's what Google uses for ranking decisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the fastest way to improve Core Web Vitals?
&lt;/h3&gt;

&lt;p&gt;Converting images to WebP format and deferring non-critical JavaScript typically deliver the biggest improvements. The Deloitte/Google study found that even a 0.1-second improvement in load time increased retail conversions by 8.4% (&lt;a href="https://web.dev/case-studies/milliseconds-make-millions" rel="noopener noreferrer"&gt;web.dev&lt;/a&gt;, 2020). Start with the "Opportunities" section of your PageSpeed Insights report.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need a developer to fix Core Web Vitals?
&lt;/h3&gt;

&lt;p&gt;For most fixes, yes. Image optimisation, JavaScript deferral, and caching configuration require some technical knowledge. But checking your scores and understanding the results doesn't require any technical ability. Run PageSpeed Insights yourself, then share the results with your developer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does site speed affect mobile conversions?
&lt;/h3&gt;

&lt;p&gt;Significantly. 53% of mobile visitors abandon sites that take longer than three seconds to load (&lt;a href="https://www.thinkwithgoogle.com/marketing-strategies/app-and-mobile/mobile-page-speed-new-industry-benchmarks/" rel="noopener noreferrer"&gt;Google&lt;/a&gt;, 2017). The conversion rate drops roughly 4.42% with each additional second of load time in the first five seconds (&lt;a href="https://abralytics.com/how-website-performance-affects-conversions/" rel="noopener noreferrer"&gt;Portent&lt;/a&gt;, 2022). On mobile, speed is revenue. Slow landing pages also tank your &lt;a href="https://nathanschram.com/post/google-ads-working" rel="noopener noreferrer"&gt;Quality Score in Google Ads&lt;/a&gt;, driving up your cost per click on the same keywords your competitors are bidding on.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://nathanschram.com/about/" rel="noopener noreferrer"&gt;Nathan Schram&lt;/a&gt; is a digital consultant and founder of &lt;a href="https://littlebearapps.com" rel="noopener noreferrer"&gt;Little Bear Apps&lt;/a&gt; based in Melbourne, Australia. He helps small businesses with &lt;a href="https://nathanschram.com/services/web-design/" rel="noopener noreferrer"&gt;web design&lt;/a&gt;, SEO, and technology strategy. For more on how page speed connects to broader search visibility, see &lt;a href="https://nathanschram.com/post/local-seo-melbourne-2026" rel="noopener noreferrer"&gt;local SEO for Melbourne businesses&lt;/a&gt;, &lt;a href="https://nathanschram.com/post/google-ads-working" rel="noopener noreferrer"&gt;how to tell if your Google Ads are actually working&lt;/a&gt;, and the guide to &lt;a href="https://nathanschram.com/post/what-is-geo" rel="noopener noreferrer"&gt;GEO and AI search optimisation&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webperf</category>
      <category>webdev</category>
      <category>seo</category>
      <category>performance</category>
    </item>
    <item>
      <title>How to make sure ChatGPT and AI Overviews mention you</title>
      <dc:creator>Nathan Schram</dc:creator>
      <pubDate>Sat, 18 Jul 2026 04:37:41 +0000</pubDate>
      <link>https://dev.to/nathanschram/how-to-make-sure-chatgpt-and-ai-overviews-mention-you-5bf9</link>
      <guid>https://dev.to/nathanschram/how-to-make-sure-chatgpt-and-ai-overviews-mention-you-5bf9</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Generative Engine Optimisation (GEO) is how you get cited by ChatGPT, Perplexity, and Google AI Overviews when someone asks them a question. ChatGPT crossed &lt;a href="https://searchengineland.com/chatgpt-900-million-weekly-active-users-470492" rel="noopener noreferrer"&gt;900 million weekly users in February 2026&lt;/a&gt;, up from 400 million a year earlier. Nearly half of Australians (49%) used generative AI in the past year, so this is already how a chunk of your customers search. The minimum viable version takes about two hours, costs you nothing if you write the schema yourself, and plausibly matters more for Melbourne small businesses than another round of keyword research.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What is GEO?&lt;/li&gt;
&lt;li&gt;Why does GEO matter in 2026? Four numbers you need to hear&lt;/li&gt;
&lt;li&gt;The Australian context: we're further along than you think&lt;/li&gt;
&lt;li&gt;GEO vs SEO: what actually changed?&lt;/li&gt;
&lt;li&gt;What does the research say about GEO?&lt;/li&gt;
&lt;li&gt;What does Google now say about AI search? (May 2026)&lt;/li&gt;
&lt;li&gt;What actually moves the needle in GEO?&lt;/li&gt;
&lt;li&gt;The schema you should actually add, with code&lt;/li&gt;
&lt;li&gt;The 2-hour Melbourne SMB minimum viable GEO plan&lt;/li&gt;
&lt;li&gt;What won't save you?&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Someone in Hawthorn asks ChatGPT "best accountant near me for a small trades business." ChatGPT doesn't hand them ten blue links. It hands them a paragraph answer with three named firms and links to the sources it pulled from.&lt;/p&gt;

&lt;p&gt;If your accounting practice isn't one of the three, you don't exist in that conversation. Not later on page two, not "fighting for the click." You're just not there.&lt;/p&gt;

&lt;p&gt;That's the thing most small business owners I talk to haven't quite clocked yet. It's not that AI search is coming. It's already here, your competitors are showing up in it, and the standard SEO playbook alone won't get you into the answer. That's what GEO is about.&lt;/p&gt;

&lt;p&gt;This is the long version of the conversation I have with every new client who asks me whether they need to "do something about AI." Yes. But probably not as much as the people selling AI SEO packages want you to think. Here's the honest version.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is GEO?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Generative Engine Optimisation (GEO) is the practice of structuring your website content so that AI tools like ChatGPT, Perplexity, Google AI Overviews, and Gemini can find it, understand it, and cite it in their answers.&lt;/strong&gt; The term comes from a 2023 Princeton paper by Aggarwal, Murahari and colleagues that introduced GEO as a discipline and measured which techniques actually improve citation rates in generative search (&lt;a href="https://arxiv.org/abs/2311.09735" rel="noopener noreferrer"&gt;arXiv:2311.09735&lt;/a&gt;, published in &lt;a href="https://dl.acm.org/doi/10.1145/3637528.3671900" rel="noopener noreferrer"&gt;KDD 2024&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The short version: AI engines don't rank your site like Google does. They read across hundreds of sources, pick the ones that give them a clean, credible, specific answer, and then paraphrase or quote those sources in their response. Getting cited is the new getting ranked. GEO is how you make that more likely.&lt;/p&gt;

&lt;p&gt;It isn't a replacement for SEO. It's an extension of it. You still want to rank in traditional search. You just also want to be the source the AI tool pulls from when someone asks a question in natural language instead of typing keywords into a box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does GEO matter in 2026? Four numbers you need to hear
&lt;/h2&gt;

&lt;p&gt;Four numbers tell the story.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. ChatGPT crossed 900 million weekly active users in February 2026.&lt;/strong&gt; That's more than double the 400 million from a year earlier (&lt;a href="https://searchengineland.com/chatgpt-900-million-weekly-active-users-470492" rel="noopener noreferrer"&gt;Search Engine Land&lt;/a&gt;, Feb 2026). OpenAI also reports 50 million paying consumer subscribers and 9 million paying business users. This isn't a niche tool anymore. It's closer to "a quarter of the internet uses ChatGPT every week."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Google AI Overviews now show on 16-48% of searches, depending on the study.&lt;/strong&gt; Semrush's research tracked Google AI Overviews appearing on around 16% of US queries in late 2025, with informational queries hit hardest at 39.4% (&lt;a href="https://www.semrush.com/blog/semrush-ai-overviews-study/" rel="noopener noreferrer"&gt;Semrush AI Overviews study&lt;/a&gt;, 2025). BrightEdge's tracking of specific industries showed AIO surging from 31% of queries in Feb 2025 to 48% by Feb 2026, with education queries jumping from 18% to 83% (&lt;a href="https://searchengineland.com/google-ai-overviews-surge-pullback-data-466314" rel="noopener noreferrer"&gt;Search Engine Land summary&lt;/a&gt;, 2026). Use the range. One number alone hides the story.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Click-through rates drop 47-61% when an AI Overview appears.&lt;/strong&gt; Pew Research found users click a traditional search result on just 8% of visits where an AI summary appears, versus 15% where none does — roughly half as often (&lt;a href="https://www.pewresearch.org/short-reads/2025/07/22/google-users-are-less-likely-to-click-on-links-when-an-ai-summary-appears-in-the-results/" rel="noopener noreferrer"&gt;Pew Research&lt;/a&gt;, July 2025). Seer Interactive measured a 61% drop in organic CTR on SERPs with AIO present (&lt;a href="https://www.seerinteractive.com/insights/aio-impact-on-google-ctr-september-2025-update" rel="noopener noreferrer"&gt;Seer&lt;/a&gt;, Sept 2025). Ahrefs saw similar patterns in December 2025 (&lt;a href="https://ahrefs.com/blog/ai-overviews-reduce-clicks-update/" rel="noopener noreferrer"&gt;Ahrefs&lt;/a&gt;). The upside: brands cited inside the AI Overview itself get 35% more organic clicks than they would without it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1e79758217sze341jckb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1e79758217sze341jckb.png" alt="Lollipop chart showing organic click-through rate drop when Google AI Overviews appear, across three studies: Pew Research 47%, Ahrefs 58%, Seer Interactive 61%" width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three separate studies, three different methodologies, roughly the same picture. Being on the page but not cited is worth about half the traffic it used to be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. One in four consumers now start their product research with AI, not Google.&lt;/strong&gt; Adobe's 2026 AI and Digital Trends report found that 25% of customers now cite AI-powered platforms like ChatGPT as a top research tool, with 49% open to AI for product recommendations and 44% for customer service (&lt;a href="https://business.adobe.com/resources/digital-trends-consumer-report.html" rel="noopener noreferrer"&gt;Adobe, 2026&lt;/a&gt;). Gartner forecasts a 25% drop in search engine volume by 2026 due to AI chatbots and virtual agents (&lt;a href="https://www.gartner.com/en/newsroom/press-releases/2024-02-19-gartner-predicts-search-engine-volume-will-drop-25-percent-by-2026-due-to-ai-chatbots-and-other-virtual-agents" rel="noopener noreferrer"&gt;Gartner, Feb 2024&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Put them together and the picture is obvious. A growing share of the people who would have clicked on your site from Google are now getting their answer from an AI tool instead. Some still click through. Many don't. The only way to be in the conversation is to be one of the sources the AI pulls from.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Australian context: we're further along than you think
&lt;/h2&gt;

&lt;p&gt;Here's the bit that surprises my Melbourne clients. Australia isn't behind on AI search. We're ahead.&lt;/p&gt;

&lt;p&gt;DataReportal's &lt;a href="https://datareportal.com/reports/digital-2026-australia" rel="noopener noreferrer"&gt;Digital 2026 Australia&lt;/a&gt; report clocks Australian internet users at 26.2 million — 97.1% penetration, effectively the whole adult population online. And a &lt;a href="https://blog.google/intl/en-au/company-news/ai-adoption-in-australia-new-survey-reveals-increased-use-belief-in-potential/" rel="noopener noreferrer"&gt;Google/Ipsos survey&lt;/a&gt; found 49% of Australians used generative AI in the past year, up from 38% in 2023, with 74% of those using it at work.&lt;/p&gt;

&lt;p&gt;Nearly half the adult population is already asking AI tools questions they used to put into Google. A decent chunk of that is happening during the workday, which means local business searches, supplier research, service provider comparisons. Exactly the kind of searches a small business owner in Fitzroy or Coburg wants to show up for.&lt;/p&gt;

&lt;p&gt;In my experience auditing Melbourne businesses, roughly half the sites I look at haven't claimed their Google Business Profile properly, or they claimed it years ago and never touched it again. The foundations that feed AI citations for local queries (GBP, reviews, accurate address and hours) are wide open. If you're reading this and your plumbing business or café hasn't sorted its GBP out, the &lt;a href="https://nathanschram.com/blog/local-seo-melbourne-2026" rel="noopener noreferrer"&gt;local SEO guide&lt;/a&gt; is where I'd start. GEO sits on top of that work, not instead of it.&lt;/p&gt;

&lt;p&gt;There's a real window here. Most Melbourne SMBs haven't done anything about GEO yet because most haven't heard of it. Competing on attention is almost always harder than competing on being early.&lt;/p&gt;

&lt;h2&gt;
  
  
  GEO vs SEO: what actually changed?
&lt;/h2&gt;

&lt;p&gt;A lot of articles on this subject draw a sharp line between GEO and SEO. They're closer than that. GEO is what you get when you apply a decade of structured-content best practice to a new audience.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;SEO&lt;/th&gt;
&lt;th&gt;GEO&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Goal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Rank in search results&lt;/td&gt;
&lt;td&gt;Get cited inside AI answers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Audience&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Google's ranking algorithm&lt;/td&gt;
&lt;td&gt;Large language models: ChatGPT, Gemini, Perplexity, Claude&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Format signals&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Keywords, backlinks, technical health, Core Web Vitals&lt;/td&gt;
&lt;td&gt;Clear definitions, direct answers, verifiable facts, structured data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Blue link on page one&lt;/td&gt;
&lt;td&gt;Named source in an AI-generated paragraph&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Trust signals&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Domain authority, E-E-A-T, link profile&lt;/td&gt;
&lt;td&gt;Citability, factual specificity, schema markup, answer-first formatting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Measurement&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Rankings, organic clicks, impressions&lt;/td&gt;
&lt;td&gt;Citation appearances, referral traffic from AI tools, brand mentions in answers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The practical overlap is larger than the table suggests. A site that's fast, structured, well-written, and factually specific tends to do well in both. A site that hides its point under paragraphs of vague marketing copy tends to do badly in both.&lt;/p&gt;

&lt;p&gt;If you care about site speed and mobile experience (you should), the same &lt;a href="https://nathanschram.com/blog/core-web-vitals-small-business-2026" rel="noopener noreferrer"&gt;Core Web Vitals work&lt;/a&gt; that helps SEO also helps AI crawlers parse your site. If your Google Ads spend is leaking (it probably is), the &lt;a href="https://nathanschram.com/blog/google-ads-working" rel="noopener noreferrer"&gt;self-audit checklist in the Google Ads guide&lt;/a&gt; will tell you where. None of this is wasted when you start thinking about GEO. It's all compounding.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does the research say about GEO?
&lt;/h2&gt;

&lt;p&gt;The foundational paper is &lt;a href="https://arxiv.org/abs/2311.09735" rel="noopener noreferrer"&gt;Aggarwal et al., "GEO: Generative Engine Optimization"&lt;/a&gt;, from a Princeton and IIT Delhi team, published at KDD 2024. It's the first serious attempt to measure which content techniques actually improve citation rates in generative search, tested across 10,000 queries.&lt;/p&gt;

&lt;p&gt;They found that certain techniques boosted visibility in AI responses by up to 40%. These are the nine techniques they tested, strongest performers first:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Citing sources.&lt;/strong&gt; Adding credible references inside your content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adding statistics.&lt;/strong&gt; Replacing "a lot of people" with "49% of Australians" (with a source).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using quotations.&lt;/strong&gt; Including direct quotes from experts or studies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fluency optimisation.&lt;/strong&gt; Clear, well-structured writing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unique wording.&lt;/strong&gt; Specific phrasing the AI hasn't seen a thousand times elsewhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Technical terms.&lt;/strong&gt; Precise terminology where it suits the topic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authoritative tone.&lt;/strong&gt; Writing like someone who actually knows the subject.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy-to-understand language.&lt;/strong&gt; Simplifying dense writing so the answer is legible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keyword stuffing.&lt;/strong&gt; The one old-school SEO trick they tested — and it &lt;em&gt;didn't&lt;/em&gt; work. Cramming query keywords in barely moved the needle, which is the whole point: AI engines reward substance, not density.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The top three (citing sources, adding statistics, using quotations) did most of the heavy lifting, each adding roughly 30-40% visibility. None of this is a shock if you've read good writing advice for the last twenty years. The interesting bit is that a machine learned to prefer it for the same reasons humans do. Specific is better than vague. Sourced is better than asserted. Clear is better than clever.&lt;/p&gt;

&lt;p&gt;The practical takeaway: the paper measured &lt;em&gt;content&lt;/em&gt; techniques, not tooling. Answer-first formatting, schema markup, and clean HTML structure are all worth doing — I get to them below — but they're your own good practice, not something this study ranked. Get the writing right first; the rest is incremental on top of that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does Google now say about AI search? (May 2026)
&lt;/h2&gt;

&lt;p&gt;In May 2026, Google did something it hadn't before. It published an &lt;a href="https://developers.google.com/search/docs/fundamentals/ai-optimization-guide" rel="noopener noreferrer"&gt;official guide to optimising for AI features in Search&lt;/a&gt;. For a year the SEO world had been guessing. Now we have Google's actual position, and it's blunter than the "AI SEO" industry would like.&lt;/p&gt;

&lt;p&gt;The headline: for Google specifically, there is no separate AI ranking system. AI Overviews and AI Mode pull from the same index and the same ranking as regular search. In Google's words, to be eligible to appear in an AI Overview, &lt;a href="https://developers.google.com/search/docs/appearance/ai-features" rel="noopener noreferrer"&gt;"a page must be indexed and eligible to be shown in Google Search with a snippet... There are no additional technical requirements"&lt;/a&gt;. Translation: if you've done good SEO, you've done your Google GEO. There's no secret AI checklist.&lt;/p&gt;

&lt;p&gt;Google also spelled out what you don't need, which is the more useful half. You don't need an llms.txt file ("Google Search itself doesn't use them"). You don't need to chop your content into tiny chunks for the AI to read. You don't need to rewrite everything in some special "AI-friendly" voice. And you don't need special schema markup to show up in AI answers. Most of what gets sold as "GEO" is, for Google, unnecessary.&lt;/p&gt;

&lt;p&gt;So where do the GEO tactics actually earn their keep? The other engines. ChatGPT, Perplexity, and Claude don't follow Google's ranking, and they don't behave like each other. ChatGPT leans on consensus sources and clear answers. Perplexity weights recency hard and cites several sources per answer. Claude likes depth and well-structured content. Answer-first formatting, specific sourced statistics, freshness, clean structure: those genuinely help you get cited there, and they make the page better for humans, which is why I'd do them anyway. Just don't believe anyone who calls them a secret Google hack. Google has now said, on the record, that they aren't.&lt;/p&gt;

&lt;p&gt;The honest version hasn't really changed. Do excellent SEO for Google, and the same fundamentals (clear answers, specific facts, fresh content) are what get you cited across ChatGPT, Perplexity, and the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually moves the needle in GEO?
&lt;/h2&gt;

&lt;p&gt;This is the section where I tell you what I'd actually bother with, because a lot of the AI SEO packages being sold right now are 80% fluff on top of standard fundamentals.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I'd prioritise
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Answer-first formatting in every section.&lt;/strong&gt; If someone could only read the first two sentences of each H2, they should get the answer. Everything after is supporting evidence. This matches the research and, honestly, just makes the article better.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-paragraph definitions of what you do.&lt;/strong&gt; Your homepage, your service pages, your About page. Each should open with a single sentence an AI tool can quote verbatim. "We're passionate about delivering solutions" is not quotable. "Nathan Schram is a Melbourne-based digital marketing consultant specialising in SEO, GEO, and Google Ads for small businesses" is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FAQ sections with direct answers.&lt;/strong&gt; FAQ content maps almost exactly to the kind of question people ask AI tools. A &lt;a href="https://relixir.ai/blog/schema-markup-boost-geo-performance-2025-data" rel="noopener noreferrer"&gt;Relixir analysis of 50 domains&lt;/a&gt; found FAQPage schema produced the highest citation lift of any schema type at around 28% median. Yes, the firm publishing those numbers sells schema services, so take the magnitude with a grain of salt. The direction is consistent across other studies. One thing to know: Google &lt;a href="https://developers.google.com/search/docs/appearance/structured-data/faqpage" rel="noopener noreferrer"&gt;retired the FAQ rich result in May 2026&lt;/a&gt;, so FAQPage markup no longer earns you the expandable Q&amp;amp;A box in Google's results. The value now is the AI-citation angle and the clean structure, not a Google SERP feature. Write FAQs. Add the schema where the questions are real. The cost is low.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fresh content with clear dates.&lt;/strong&gt; Every article I publish has a "last updated" date. AI tools weight recency. A 2024 article about AI search is stale by 2026.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Specific, verifiable numbers.&lt;/strong&gt; "We've helped many businesses" is worthless. "I've audited Google Ads accounts for trades, healthcare, real estate, and professional services since 2013" is fact-checkable and citable.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I'd skip for now
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Buying an "AI SEO" package for $2,000 a month.&lt;/strong&gt; Most of what's in those is either standard SEO or basic schema implementation. If your site has good structure, clear writing, and current schema, you've already done 90% of the work. The rest is measurement, which barely any tool does well yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skip llms.txt.&lt;/strong&gt; The &lt;a href="https://www.answer.ai/posts/2024-09-03-llmstxt.html" rel="noopener noreferrer"&gt;llms.txt proposal&lt;/a&gt; from Jeremy Howard at Answer.AI in September 2024 is a tidy idea: a text file telling AI crawlers what matters on your site. Plenty of sites added one through 2025. Then the evidence came in. In May 2026 Google stated flatly that &lt;a href="https://developers.google.com/search/docs/fundamentals/ai-optimization-guide" rel="noopener noreferrer"&gt;its Search systems don't use it&lt;/a&gt;, and an Ahrefs analysis of 137,000 domains found &lt;a href="https://searchengineland.com/googles-mueller-says-llms-txt-cant-help-llms-differentiate-sites/579304" rel="noopener noreferrer"&gt;97% of llms.txt files got zero bot requests&lt;/a&gt;. John Mueller compared it to the discredited meta-keywords tag. I had this filed under "cheap insurance" a few months ago. It's now closer to "don't bother for most sites." If you want one for the agentic-browsing future, fine, but don't pay anyone to strategise about it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chasing every new "GEO tool."&lt;/strong&gt; The tooling market is a mess. Most tools show you mentions in some subset of AI tools, extrapolate wildly, and sell you a dashboard. Wait a quarter. The honest ones will still be around.&lt;/p&gt;

&lt;h2&gt;
  
  
  The schema you should actually add, with code
&lt;/h2&gt;

&lt;p&gt;Schema markup is where most small business websites miss the easiest win. Microsoft confirmed at &lt;a href="https://searchengineland.com/schema-markup-ai-search-no-hype-472339" rel="noopener noreferrer"&gt;SMX Munich in March 2025&lt;/a&gt; that Bing's AI tools use schema in their indexing. Google was clearer in its May 2026 guide: structured data &lt;a href="https://developers.google.com/search/docs/fundamentals/ai-optimization-guide" rel="noopener noreferrer"&gt;isn't required for AI search and there's no special markup to add&lt;/a&gt;, but it's still worth using because it helps you qualify for rich results in regular search. So add schema because it's cheap and well-supported for SEO and for engines like Bing, not because it's a magic AI-citation lever. The cost of adding it is one hour. The downside is zero.&lt;/p&gt;

&lt;p&gt;Drop these into the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of the relevant pages, inside &lt;code&gt;&amp;lt;script type="application/ld+json"&amp;gt;&lt;/code&gt; tags. Test them with Google's &lt;a href="https://search.google.com/test/rich-results" rel="noopener noreferrer"&gt;Rich Results Test&lt;/a&gt; and &lt;a href="https://validator.schema.org/" rel="noopener noreferrer"&gt;Schema Markup Validator&lt;/a&gt;. If either throws a red error, fix it before shipping.&lt;/p&gt;

&lt;h3&gt;
  
  
  Person schema (for a consultant or personal brand)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://schema.org"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Person"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Nathan Schram"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://nathanschram.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jobTitle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Digital Marketing Consultant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Melbourne-based digital marketing consultant specialising in SEO, GEO, and Google Ads for small businesses."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PostalAddress"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"addressLocality"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Melbourne"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"addressRegion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VIC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"addressCountry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AU"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sameAs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://www.linkedin.com/in/nathanschram"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://bsky.app/profile/nathanschram.bsky.social"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/littlebearapps"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  LocalBusiness schema (for a Melbourne SMB)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://schema.org"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LocalBusiness"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Your Business Name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/storefront.jpg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PostalAddress"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"streetAddress"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123 Brunswick St"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"addressLocality"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Fitzroy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"addressRegion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VIC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"postalCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3065"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"addressCountry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AU"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"geo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GeoCoordinates"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"latitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-37.7963&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"longitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;144.9780&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"telephone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+61-3-9000-0000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"openingHoursSpecification"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OpeningHoursSpecification"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dayOfWeek"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Monday"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"Tuesday"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"Wednesday"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"Thursday"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"Friday"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"opens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"09:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"closes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"17:00"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"priceRange"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$$"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  FAQPage schema (still useful for AI, no longer a Google rich result)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://schema.org"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FAQPage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mainEntity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Question"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"What is GEO?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"acceptedAnswer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Generative Engine Optimisation (GEO) is the practice of structuring website content so AI tools like ChatGPT, Perplexity, and Google AI Overviews cite it in their answers."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Organization schema (for a company homepage)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://schema.org"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Organization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Your Company"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"logo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/logo.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sameAs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://linkedin.com/company/your-company"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://twitter.com/yourcompany"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want a hand adding schema or doing a proper GEO audit rather than a quick pass, &lt;a href="https://nathanschram.com/get-in-touch/" rel="noopener noreferrer"&gt;get in touch&lt;/a&gt;. I do this for small businesses across Melbourne.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 2-hour Melbourne SMB minimum viable GEO plan
&lt;/h2&gt;

&lt;p&gt;Here's what I'd actually do if you gave me a Saturday morning and a Melbourne small business website that's currently invisible to AI search.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rewrite the homepage opening paragraph&lt;/strong&gt; (15 min). One sentence that says what you do, who you do it for, and where. No "passionate." No "innovative." Just the facts an AI tool could quote.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add Person or Organization schema to every page&lt;/strong&gt; (20 min). Use the templates above. Test in the Rich Results Test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add LocalBusiness schema to the homepage and contact page&lt;/strong&gt; (15 min). Especially if you have a physical location.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write one proper FAQ section&lt;/strong&gt; (30 min). Five to eight questions real customers actually ask. Direct answers. Add FAQPage schema to the page it lives on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claim and optimise your Google Business Profile&lt;/strong&gt; (20 min if you haven't, 5 min to double-check if you have). Hours, categories, photos, description. This feeds AI citations for local queries directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add a "last updated" date to every piece of content&lt;/strong&gt; (10 min). Recency is a signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check your key pages are crawlable and indexable&lt;/strong&gt; (10 min). No accidental &lt;code&gt;noindex&lt;/code&gt;, nothing important blocked in robots.txt, and your main content sitting in the HTML rather than hidden behind JavaScript. This is the one technical thing Google's own guide says actually gates whether a page can appear in an AI answer.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's about two hours. It will not solve every problem. It also won't leave you any worse off than you are now, and it covers the techniques the Princeton research actually measured as high-impact.&lt;/p&gt;

&lt;h2&gt;
  
  
  What won't save you?
&lt;/h2&gt;

&lt;p&gt;Before you hand money to anyone selling "GEO packages," three honest caveats.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schema alone isn't magic.&lt;/strong&gt; Some studies report strong citation lifts from structured data and FAQ blocks. &lt;a href="https://searchengineland.com/schema-markup-ai-search-no-hype-472339" rel="noopener noreferrer"&gt;AccuraCast's September 2025 analysis&lt;/a&gt; found only 1.8% of cited sources used FAQPage schema, and Reddit (the single most-cited source in AI answers) uses almost no Schema.org markup at all. The studies disagree on magnitude. The sensible move is to add schema because it's cheap and well-supported for regular SEO, not because a single stat is going to transform your citations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The "AI-first" funnel is still tiny.&lt;/strong&gt; Most small businesses I work with still get the majority of their traffic from Google, direct visits, and paid ads. AI referral traffic is real and growing, but for a Melbourne plumber right now it's probably 3-8% of sessions, not 50%. Don't neglect your &lt;a href="https://nathanschram.com/blog/core-web-vitals-small-business-2026" rel="noopener noreferrer"&gt;Core Web Vitals&lt;/a&gt; or your &lt;a href="https://nathanschram.com/blog/google-ads-working" rel="noopener noreferrer"&gt;Google Ads setup&lt;/a&gt; chasing an AI-only strategy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tooling is changing monthly.&lt;/strong&gt; OpenAI adds features, Google re-pulls AI Overviews from industries they'd previously rolled out to, Perplexity launches new products. The foundations (clear writing, direct answers, structured data, fresh content, real expertise) have survived a decade of Google algorithm changes and will survive this one. The specific tooling around them is going to keep moving.&lt;/p&gt;

&lt;p&gt;Do the fundamentals, don't panic-buy an AI SEO package, and check back in six months. That's how I'd approach it, anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is GEO different from AEO (Answer Engine Optimisation)?
&lt;/h3&gt;

&lt;p&gt;Mostly not. AEO came first, referring to optimisation for featured snippets and voice assistants. GEO is the broader term for generative AI search. Some practitioners use them interchangeably. I use GEO because it's the term the Princeton paper established.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to do GEO if I already rank well in Google?
&lt;/h3&gt;

&lt;p&gt;Yes, because ranking well in Google and being cited in Google AI Overviews are not the same thing. A page can be in position 1 for a keyword and still not appear in the AI Overview for the same query. That said, if your SEO fundamentals are strong, your GEO starting point is already better than most.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the single highest-impact GEO change I can make?
&lt;/h3&gt;

&lt;p&gt;Rewrite the first paragraph of your homepage and key service pages to open with a direct, specific, citable answer to "what do you do and who do you do it for." Everything else compounds on top of that.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I measure if GEO is working?
&lt;/h3&gt;

&lt;p&gt;It's harder than SEO. Look at referral traffic from AI tool domains (chat.openai.com, perplexity.ai, gemini.google.com) in Google Analytics, brand mentions in AI answers (test by asking ChatGPT and Perplexity questions a customer would ask), and whether your pages appear in Google AI Overviews for your target keywords. No tool does this perfectly yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does llms.txt actually do anything?
&lt;/h3&gt;

&lt;p&gt;For Google, no. In May 2026 Google stated its Search systems don't use llms.txt, and an Ahrefs study of 137,000 domains found 97% of the files got zero bot requests. The &lt;a href="https://www.answer.ai/posts/2024-09-03-llmstxt.html" rel="noopener noreferrer"&gt;proposal&lt;/a&gt; is sensible in theory and might matter for the agentic-browsing future, but as a 2026 SEO or GEO lever it does almost nothing. Treat it as optional, not a growth move.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I just use ChatGPT to write my GEO content?
&lt;/h3&gt;

&lt;p&gt;You can, and plenty of people do, but the &lt;a href="https://searchengineland.com/ai-generated-content-google-search-experiment-472234" rel="noopener noreferrer"&gt;Search Engine Land 16-month study&lt;/a&gt; of 2,000 AI-written articles found they collapsed to just 3% of Google's top 100 within three months (they clawed back some ground later, but most never returned). Write it yourself, or get a human to edit AI drafts properly. The Princeton research is very clear that specific, factual, well-cited writing wins. Generic AI copy is the opposite of all of that.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the minimum budget for GEO?
&lt;/h3&gt;

&lt;p&gt;Two hours of your time and zero dollars, if you follow the weekend plan above. If you want an expert audit and implementation, a decent local consultant (shameless plug: me) will do a thorough GEO pass on a small business site for a few hundred to a couple of thousand dollars, depending on size. Don't pay ongoing $2,000-a-month "AI SEO" fees unless someone's actually proving the traffic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is GEO still going to matter in a year?
&lt;/h3&gt;

&lt;p&gt;The specific tooling might change. The fundamentals have mattered for traditional SEO for two decades: clear writing, structured data, direct answers, verifiable facts, current content. Nothing in the research suggests those are going away. If anything, they're about to matter more.&lt;/p&gt;




&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;GEO matters more than the "AI SEO" crowd has earned the right to charge for it, and less than most of the panicked blog posts about "the death of Google" are suggesting. It's an extension of the work a good SEO should already be doing: clear answers, specific facts, structured markup, current content. Sort those out and you'll be fine.&lt;/p&gt;

&lt;p&gt;If you'd like a proper GEO audit of your Melbourne business website, or help with the schema implementation, &lt;a href="https://nathanschram.com/get-in-touch/" rel="noopener noreferrer"&gt;get in touch&lt;/a&gt;. I work with small and medium businesses across Melbourne on SEO, &lt;a href="https://nathanschram.com/services/ads/" rel="noopener noreferrer"&gt;Google Ads&lt;/a&gt;, web design, and the whole mess AI has made of digital marketing strategy. Results, not reports.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Nathan Schram is a Melbourne-based digital marketing consultant with 13 years in tech marketing, sales, and business development across startups and ASX-listed organisations. He writes about &lt;a href="https://nathanschram.com/blog/core-web-vitals-small-business-2026" rel="noopener noreferrer"&gt;Core Web Vitals&lt;/a&gt;, &lt;a href="https://nathanschram.com/blog/google-ads-working" rel="noopener noreferrer"&gt;Google Ads&lt;/a&gt;, and &lt;a href="https://nathanschram.com/blog/local-seo-melbourne-2026" rel="noopener noreferrer"&gt;local SEO for Melbourne businesses&lt;/a&gt;. Read more at &lt;a href="https://nathanschram.com/about/" rel="noopener noreferrer"&gt;About&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published April 2026; updated June 2026 to fold in Google's first official AI-search guidance (and to retire the llms.txt advice Google has since said it doesn't use). Statistics were verified against their original sources at publication. AI search data moves fast, so check the linked sources for the latest figures.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>seo</category>
      <category>ai</category>
      <category>webdev</category>
      <category>marketing</category>
    </item>
    <item>
      <title>I voice-code from my phone while walking my dog</title>
      <dc:creator>Nathan Schram</dc:creator>
      <pubDate>Thu, 02 Apr 2026 05:22:14 +0000</pubDate>
      <link>https://dev.to/nathanschram/i-voice-code-from-my-phone-while-walking-my-dog-3d8g</link>
      <guid>https://dev.to/nathanschram/i-voice-code-from-my-phone-while-walking-my-dog-3d8g</guid>
      <description>&lt;p&gt;Last Wednesday afternoon I was at the oval with Normi, my 13-year-old dog, playing tug of war with his favourite rope ball. Between rounds I pulled out my phone, recorded a voice note asking Claude Code to run the full engine test suite across six Telegram chats, and went back to playing. Twenty minutes later, Normi and I were both sitting on the grass, absolutely pooped. I checked Telegram. Claude Code had finished testing, logged the bugs it found, and created GitHub issues for each one. I hadn't typed a single character.&lt;/p&gt;

&lt;p&gt;That's most of my afternoons now.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I spend 2-4 hours a day walking my 13-year-old dog Normi. During those walks, I dictate coding tasks to Claude Code via Telegram voice notes using &lt;a href="https://github.com/littlebearapps/untether" rel="noopener noreferrer"&gt;Untether&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Voice input is roughly 4x faster than typing on a phone (150 WPM speaking vs 40 WPM typing). The walks themselves boost creative output by 60% compared to sitting (Stanford, &lt;a href="https://news.stanford.edu/stories/2014/04/walking-vs-sitting-042414" rel="noopener noreferrer"&gt;176 participants&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;This isn't a novelty. It's how I work every day. Honestly, I get more done on walks than I do at my desk.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Simon Willison &lt;a href="https://x.com/simonw/status/1853872615922012186" rel="noopener noreferrer"&gt;put it well&lt;/a&gt; back in November 2024: "Coding while walking the dog is an underrated benefit of AI tooling." He never wrote the detailed post. So here it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: AI coding agents need you at a terminal
&lt;/h2&gt;

&lt;p&gt;AI coding agents are genuinely useful. 71% of developers who regularly use AI agents use Claude Code (&lt;a href="https://www.getpanto.ai/blog/ai-coding-assistant-statistics" rel="noopener noreferrer"&gt;Pragmatic Engineer survey&lt;/a&gt;, 15,000 developers, Feb 2026). 4% of all public GitHub commits are now authored by Claude Code (&lt;a href="https://newsletter.semianalysis.com/p/claude-code-is-the-inflection-point" rel="noopener noreferrer"&gt;SemiAnalysis&lt;/a&gt;, Feb 2026).&lt;/p&gt;

&lt;p&gt;They all share one assumption: you're sitting at a terminal.&lt;/p&gt;

&lt;p&gt;Claude Code runs in a terminal. You watch it work. It asks permission to edit files or run commands. You type &lt;code&gt;y&lt;/code&gt; or &lt;code&gt;n&lt;/code&gt;. If you walk away, it stalls. The session just sits there waiting for you to come back and press a key.&lt;/p&gt;

&lt;p&gt;I'm a vibe coder. Not CS-trained, background in sales and ops, 13 years in tech. I run Little Bear Apps in Melbourne and I &lt;a href="https://littlebearapps.com/blog/dogfooding-bugs-tests-cant-find/" rel="noopener noreferrer"&gt;build tools to scratch my own itch&lt;/a&gt;. And I kept finding myself mid-session with Claude Code on my MacBook, absolutely in the zone, when I had to leave. Walk the dog. Go to the shops. Whatever. I hated it. Every time I walked away, the session died.&lt;/p&gt;

&lt;p&gt;I tried the Blink shell iOS app with TMUX and MOSH connecting to my VPS. That worked okay. I could at least see the terminal from my phone. Typing on a tiny screen while holding a leash isn't great.&lt;/p&gt;

&lt;p&gt;There are official solutions now. Claude Code Remote Control (February 2026) lets you scan a QR code from the Claude mobile app. Claude Code Channels (March 20, 2026) adds Telegram and Discord support through MCP. Both are Claude-only, text-only, and Channels still pauses at the terminal when it needs permission.&lt;/p&gt;

&lt;p&gt;As of March 2026, none of them support voice input, multiple AI engines, or interactive permission buttons from a phone. I needed a proper remote coding workflow - one where I could speak a task into my phone while walking my dog and have it just... work. Including the permission prompts.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I found takopi and why I rebuilt it
&lt;/h2&gt;

&lt;p&gt;I found &lt;a href="https://github.com/banteg/takopi" rel="noopener noreferrer"&gt;banteg/takopi&lt;/a&gt; in late December 2025. It's a Telegram notifier for AI coding agents, and at first it was an absolute godsend. I could hook up voice-to-text transcription via Telegram, record a voice note, and it would send the task to Claude Code. Brilliant.&lt;/p&gt;

&lt;p&gt;Then I hit the wall.&lt;/p&gt;

&lt;p&gt;Takopi doesn't handle Claude Code's interactive bits. When Claude Code needs permission to run a command, or wants to exit plan mode to implement something, or asks you a question, Takopi just... freezes. The agent sits there waiting for input that never comes. Your Telegram chat goes silent. You don't even know it's stuck unless you check.&lt;/p&gt;

&lt;p&gt;I opened issues. I waited three, maybe four weeks for a response from banteg on the repo. Nothing. The bugs are still there today.&lt;/p&gt;

&lt;p&gt;So I forked it and rebuilt it. &lt;a href="https://github.com/littlebearapps/untether" rel="noopener noreferrer"&gt;Untether&lt;/a&gt; launched in February 2026, and it's been my primary development tool since.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup: what connects to what
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpx9dqtyfi9qzy9w5dmxw.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpx9dqtyfi9qzy9w5dmxw.webp" alt="Architecture diagram showing the Untether pipeline: Telegram voice note to speech-to-text transcription via API, then to Untether and Claude Code (plus Codex, Gemini, OpenCode, Pi, and Amp) running on a Hetzner VPS, with interactive permissions, live streaming, and two-way file transfer flowing back" width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The chain looks like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;iPhone&lt;/strong&gt; (Telegram app) -&amp;gt; &lt;strong&gt;Telegram Bot API&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Untether&lt;/strong&gt; (Python, running on my VPS) -&amp;gt; &lt;strong&gt;Claude Code&lt;/strong&gt; (also on my VPS)&lt;/p&gt;

&lt;p&gt;The VPS (virtual private server) matters. I run Untether and Claude Code on a Hetzner server in Germany. Not on my MacBook, not on my home network. This means I don't care if the power's on at home, if my MacBook is sleeping, or if my home internet drops. The VPS is always on. Even if my phone dies mid-walk, the coding agent keeps working. I'll see the results when I get back. (The VPS also runs the infrastructure I wrote about in &lt;a href="https://littlebearapps.com/blog/d1-billing-disaster-circuit-breakers/" rel="noopener noreferrer"&gt;how a D1 billing disaster taught me to build circuit breakers&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/littlebearapps/untether" rel="noopener noreferrer"&gt;Untether&lt;/a&gt; is open source, Python 3.12+, and installs with one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv tool &lt;span class="nb"&gt;install &lt;/span&gt;untether
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key pieces:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Voice transcription.&lt;/strong&gt; When I record a voice note in Telegram, Untether sends it to a Whisper-compatible endpoint via &lt;a href="https://groq.com" rel="noopener noreferrer"&gt;Groq&lt;/a&gt; for transcription, then passes the text to Claude Code as a task. I don't type on my phone. I talk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Progress streaming.&lt;/strong&gt; As Claude Code works, Untether streams updates to my Telegram chat. Tool calls, file changes, elapsed time. I can watch it think in real time or just check back later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6nlj6xbvabwxi8hul59s.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6nlj6xbvabwxi8hul59s.webp" alt="Untether streaming progress in Telegram - tool calls, file changes, and working status visible in real time" width="590" height="1280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interactive permissions.&lt;/strong&gt; This is the part that makes it actually usable away from a terminal. When Claude Code needs to run a command, edit a file, or exit plan mode, Untether shows me inline Telegram buttons. Approve, Deny, or reply with instructions. No terminal required.&lt;/p&gt;

&lt;p&gt;I leave plan mode on and I leave permissions on. I prefer to have some control rather than letting Claude Code just go wild. I built a custom button called "Pause and outline plan" that forces Claude Code to write out a detailed plan before it does anything. In the version I'm about to ship (v0.35.0), I've added a second step after that: Approve, Deny, and a new "Stop and let's discuss" button. Sometimes you want to talk it through before committing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple engines.&lt;/strong&gt; Untether isn't locked to Claude Code. It supports Codex, OpenCode, and Pi today, with Gemini CLI and Amp coming. I mostly use Claude Code, but the multi-engine support matters for testing. I have one Telegram chat per engine, and Claude Code can actually switch between them during automated test runs using a &lt;a href="https://github.com/chigwell/telegram-mcp" rel="noopener noreferrer"&gt;Telegram MCP server&lt;/a&gt; I helped fix (we submitted &lt;a href="https://github.com/chigwell/telegram-mcp/pull/77" rel="noopener noreferrer"&gt;a PR&lt;/a&gt; fixing an entity cache bug that broke 87% of operations for session-based users).&lt;/p&gt;

&lt;p&gt;One thing worth knowing if you use multiple engines: each one has its own context file format. Claude Code reads CLAUDE.md, Codex wants agents.md, Gemini has its own thing. If you've only set up context for one engine, the others will still work but they'll take longer to get oriented. Your directory-level context, global context, working directory structure - all of it matters. Get your infrastructure right and Untether works perfectly regardless of which engine you're talking to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does talking beat typing on a phone?
&lt;/h2&gt;

&lt;p&gt;Speaking is roughly 4x faster than typing on a phone screen. 150 words per minute speaking versus about 40 WPM thumb-typing (&lt;a href="https://wisprflow.ai/vibe-coding" rel="noopener noreferrer"&gt;Wispr Flow&lt;/a&gt;). On a walk, with a leash in one hand, that difference matters.&lt;/p&gt;

&lt;p&gt;Speed isn't the real advantage, though. The real advantage is that talking forces you to think out loud, and thinking out loud produces better prompts.&lt;/p&gt;

&lt;p&gt;When I type a task for Claude Code at my desk, I tend to be terse: "refactor the auth middleware." When I'm walking and talking, I naturally add context: "Hey, the auth middleware in Viewpo is getting messy - the session validation is mixed in with the role checking. Can you split those into separate middleware functions? Keep the existing tests passing."&lt;/p&gt;

&lt;p&gt;The voice prompt is longer, more specific, and gives Claude Code more to work with. I'm not trying to be thorough. I'm just talking the way people talk.&lt;/p&gt;

&lt;p&gt;I'm a waffler. I love to talk things out, talk things through, often just to crystallise something for myself as I say it. Claude Code takes that waffle and rearranges it into something structured. It's a surprisingly good loop: I ramble with context, Claude Code extracts the actual task.&lt;/p&gt;

&lt;h3&gt;
  
  
  What voice transcription gets wrong
&lt;/h3&gt;

&lt;p&gt;Honestly? Not much. As long as you speak reasonably loudly and clearly, Groq handles it well. I've only had a couple of times where words genuinely got mangled beyond recognition.&lt;/p&gt;

&lt;p&gt;If I'm mumbling, or doing my neurodiverse ADHD waffle thing where I'm jumping between thoughts mid-sentence, yeah, it can struggle a bit. But Claude Code is pretty good at inferring intent even from imperfect transcription. Most of the time, close enough is close enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you handle AI permissions from your phone?
&lt;/h2&gt;

&lt;p&gt;AI coding agents from your phone have a problem: the agent is going to ask you questions. It's going to want permission to delete files, run tests, push code. If you can't respond to those prompts, the session stalls.&lt;/p&gt;

&lt;p&gt;Takopi didn't handle this. You could send a task, but when Claude Code hit a permission prompt, everything just stopped until you got back to a terminal.&lt;/p&gt;

&lt;p&gt;Untether solves this with inline Telegram buttons:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi9jbk63jjybkofryei2u.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi9jbk63jjybkofryei2u.webp" alt="Plan mode approval buttons in Telegram - Approve, Deny, and Pause and Outline Plan options appear inline" width="590" height="1280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Plan mode&lt;/strong&gt; toggles per-chat. I leave it on. When Claude Code wants to implement a plan, I get buttons: Approve, Deny, or my custom "Pause and outline plan"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approve/Deny buttons&lt;/strong&gt; appear inline when Claude Code needs permission for destructive operations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Progressive cooldown&lt;/strong&gt; reduces prompt frequency for repeated similar actions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask mode&lt;/strong&gt; lets Claude Code ask me questions through Telegram. I can reply with text or another voice note&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost controls&lt;/strong&gt; with per-run and daily budgets, &lt;code&gt;/usage&lt;/code&gt; breakdowns. Important when you're kicking off tasks and walking away&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The "Pause and outline plan" button is one I built for my own workflow. Claude Code in plan mode is a life saver. I'd rather read a plan and approve it than have the agent just start editing files. And in v0.35.0, after Claude Code writes the outline, you get three choices: approve it, deny it, or hit "Stop and let's discuss" if you want to talk it through first.&lt;/p&gt;

&lt;p&gt;This is the part that makes the workflow real rather than theoretical. Without interactive permissions, "code from your phone" means "start a task and hope for the best." With them, I have the same control I'd have at my desk. Just through buttons instead of keystrokes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a real walk looks like
&lt;/h2&gt;

&lt;p&gt;Normi is 13. He's a 16-kilo staffy cross pug cross French bulldog. A little fun-sized potato. Super friendly with everyone, loves people, tolerates cats, and sounds absolutely vicious when he plays. He isn't. He's having the time of his life.&lt;/p&gt;

&lt;p&gt;We go out two or three times a day. Sometimes we do the same tracks and parks we always do, sometimes we explore new ones. I'm outside for probably two to four hours total, depending on the weather and what we're up to. We'll often stop at an oval so Normi can play.&lt;/p&gt;

&lt;p&gt;I call the game "grrrrr" - which is basically the noise Normi makes while playing it. It's tug of war combined with chasey. I got these nearly indestructible dog balls with tug of war ropes on them, and Normi goes absolutely feral for them. He grabs one end, I grab the other, and he growls and shakes his head like he's fighting a crocodile. Then he bolts and wants me to chase him. Then he comes back and wants to do it again. For years I'd try to get him to drop the ball and he'd just stand there growling. "Norman. Come on." Only took me about a decade to realise he didn't want to drop it - he wanted the fight. He's an expert at grrrrr. Arguably he's never lost.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3nurl1w2oqzrcn2nzwxy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3nurl1w2oqzrcn2nzwxy.webp" alt="Normi standing at the oval, looking directly at the camera, ready to go" width="800" height="1066"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Between rounds of grrrrr, while Normi's catching his breath (or more often, while he's pretending he can't hear me calling him back), I pull out my phone and check Telegram. There's usually a response from Claude Code waiting in one of my working directory chats. I read it, record a quick voice note with the next task, put my phone away, and go back to playing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp908lgx2wzs4ma9i72nu.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp908lgx2wzs4ma9i72nu.webp" alt="Voice note transcription in Telegram - a 7-second voice note transcribed by Groq and sent to Claude Code as a task" width="590" height="1280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Five or ten minutes later I check again. Claude Code has been working the whole time. I might have three or four different working directory chats going - one time I had five or six running in parallel, testing bug fixes in the Untether repo while making website updates and working on various other projects at the same time.&lt;/p&gt;

&lt;p&gt;After thirty or forty minutes, Normi and I are both sitting on the grass, absolutely pooped, having a drink. And Claude Code is still working away on the VPS, finishing up the last task.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fitab41ldmyto70uf0ijh.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fitab41ldmyto70uf0ijh.webp" alt="Normi resting on the grass at the oval after playing - tongue out, absolutely pooped" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  This is not work-life balance
&lt;/h3&gt;

&lt;p&gt;Look, I think work-life balance is bullshit. I've never been able to find it. And I don't think most solo devs have either.&lt;/p&gt;

&lt;p&gt;But I can go for two or three walks a day with Normi, in the sun or the rain, and be outside for hours. I don't have to sit in traffic. I don't have to be in some office. I don't have to sit through meetings that could have been emails. I can play grrrrr at the oval and check in on my coding agents between rounds. I can be at Coles, on the bus, in bed at 6am. The VPS doesn't care. Telegram doesn't care. Claude Code keeps running whether I'm watching or not.&lt;/p&gt;

&lt;p&gt;That's living, I guess. To me, anyway.&lt;/p&gt;

&lt;h3&gt;
  
  
  The thinking loop
&lt;/h3&gt;

&lt;p&gt;There's a Stanford study that found walking increases creative output by 60% compared to sitting (&lt;a href="https://news.stanford.edu/stories/2014/04/walking-vs-sitting-042414" rel="noopener noreferrer"&gt;Oppezzo &amp;amp; Schwartz, 2014&lt;/a&gt;, 176 college students across four experiments). I'm not claiming causation for my own work. But I notice it.&lt;/p&gt;

&lt;p&gt;Something about walking with a reasonably clear mind, being outside with Normi, not staring at code - my best task descriptions come out on walks, not at my desk. I think it's because I'm not lost in implementation details. I'm thinking about what I actually want.&lt;/p&gt;

&lt;p&gt;Voice-to-text amplifies this. I'm a talker. I process by talking things through, often just to crystallise something for myself. The walk gives me space to think clearly, I talk it through as a voice note, and Claude Code rearranges my waffle into something structured. The loop works.&lt;/p&gt;

&lt;h2&gt;
  
  
  What doesn't work well?
&lt;/h2&gt;

&lt;p&gt;Honestly, most of it works. But there are a few things worth knowing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Voice notes are great for intent and convenience, but not always good for precision.&lt;/strong&gt; If I say "the auth middleware needs splitting into two separate functions, keep the tests passing" - that works brilliantly. Dictating actual code syntax is painful no matter how good the transcription is. The trick is prompting the same way you would at your laptop - be descriptive, give context, explain what you want and why. As long as you do that, voice works just as well as typing. The issue is never the voice input. It's being vague.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Screen size.&lt;/strong&gt; Reading a 200-line diff on a phone screen isn't great, I'll be honest. I'll skim the progress updates on a walk, approve or deny the obvious stuff, and do a proper review when I get home to a real screen. The agent handles the straightforward decisions - formatting, renames, clear-cut logic changes - and I handle the ones that need thought.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mobile signal.&lt;/strong&gt; This is actually one of the best bits about the whole setup. Your AI agents run on the VPS, not your phone. If you lose mobile coverage walking through a dead zone or duck into a building with no signal, the agents keep working. They don't care that your phone went quiet - they're on a server in Germany. When you find coverage again, all the updates are sitting there in Telegram waiting for you. Nothing stalls, nothing breaks. Telegram queues messages beautifully.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deep architecture sessions.&lt;/strong&gt; If I need to trace through a complicated chain of files or make big architectural decisions, I'll sometimes save that for home with a proper screen. But even then, I've been surprised how far I can get by just being clear in my voice prompts: "Create a plan and save it. Don't implement yet. Let's discuss first." Going back and forth on plans through voice notes genuinely works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transcription and mumbling.&lt;/strong&gt; If I'm not speaking clearly, or doing my ADHD thing where I jump between thoughts mid-sentence, transcription quality drops. Speak clearly and you'll be fine. Mumble and you'll confuse everyone, including the AI.&lt;/p&gt;

&lt;p&gt;The big thing for me is that using Untether means I actually get to enjoy the walks more, not less. I'm not hunched over a tiny keyboard slowly typing out messages. A voice note takes seven seconds, then I'm back to playing with Normi. The rest of the time I'm genuinely present - outside, moving, not staring at a screen. That's the whole point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;If this workflow sounds useful, here's how to try it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install Untether&lt;/strong&gt;: &lt;code&gt;uv tool install untether&lt;/code&gt; (requires Python 3.12+)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create a Telegram bot&lt;/strong&gt; via &lt;a href="https://t.me/BotFather" rel="noopener noreferrer"&gt;@BotFather&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure&lt;/strong&gt; &lt;code&gt;untether.toml&lt;/code&gt; with your bot token and Claude Code path&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Register your projects&lt;/strong&gt;: &lt;code&gt;untether init &amp;lt;shortname&amp;gt;&lt;/code&gt; in each repo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Send your first task&lt;/strong&gt; as a text message or voice note&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You don't need a VPS. You can run Untether on your laptop. But if you want the "my phone can die and work continues" setup, a cheap VPS does the trick. I use Hetzner.&lt;/p&gt;

&lt;p&gt;Untether is free and open source: &lt;a href="https://github.com/littlebearapps/untether" rel="noopener noreferrer"&gt;github.com/littlebearapps/untether&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Normi and I will be at the oval either way. Might as well ship something while we're there.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can I use Untether with AI coding agents other than Claude Code?&lt;/strong&gt;&lt;br&gt;
Yes. Untether supports Claude Code, Codex, OpenCode, and Pi today. Gemini CLI and Amp support are coming.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Untether work on Android?&lt;/strong&gt;&lt;br&gt;
Yes. It works through Telegram, which runs on iOS, Android, desktop, and web. The phone doesn't matter, only the Telegram app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is Untether free?&lt;/strong&gt;&lt;br&gt;
Yes. It's open source (MIT licence), free to install and use. You'll need your own API keys for the AI coding agent you connect to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How accurate is voice transcription for coding tasks?&lt;/strong&gt;&lt;br&gt;
Good enough for natural language task descriptions. Groq's Whisper-compatible transcription handles conversational English well. Technical terms occasionally get mangled, but Claude Code usually infers the correct intent from context. Speak clearly and you'll be fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does the AI agent keep running if my phone dies?&lt;/strong&gt;&lt;br&gt;
Yes, if you're running on a VPS. The agent runs on the server, not your phone. Telegram just delivers the messages. When your phone comes back online, you'll see everything that happened while you were offline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does Untether compare to Claude Code Channels?&lt;/strong&gt;&lt;br&gt;
Channels launched in March 2026 and adds Telegram and Discord support for Claude Code through MCP. It's Claude-only and text-only. It still pauses at the terminal for permission prompts. Untether supports six engines, accepts voice notes, and handles permissions with inline Telegram buttons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use voice notes to write code with an AI agent?&lt;/strong&gt;&lt;br&gt;
Yes. Untether transcribes Telegram voice notes using &lt;a href="https://groq.com" rel="noopener noreferrer"&gt;Groq&lt;/a&gt;'s Whisper-compatible endpoint, then passes the text to the AI coding agent as a task. Speaking is roughly 4x faster than typing on a phone (150 WPM vs 40 WPM).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What server specs does Untether need?&lt;/strong&gt;&lt;br&gt;
Minimal. Untether itself is lightweight Python. The AI agent does the heavy lifting. A basic VPS like a Hetzner CX22 is more than enough. You can also run it on your laptop if you don't need the always-on setup.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>telegram</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>My $5/month Cloudflare bill hit $4,868 because of an infinite loop</title>
      <dc:creator>Nathan Schram</dc:creator>
      <pubDate>Tue, 31 Mar 2026 05:11:24 +0000</pubDate>
      <link>https://dev.to/nathanschram/my-5month-cloudflare-bill-hit-4868-because-of-an-infinite-loop-13g8</link>
      <guid>https://dev.to/nathanschram/my-5month-cloudflare-bill-hit-4868-because-of-an-infinite-loop-13g8</guid>
      <description>&lt;p&gt;The invoice said $4,868.00. My Cloudflare account usually costs $5 a month.&lt;/p&gt;

&lt;p&gt;In January 2026, two bugs in two different workers wrote billions of rows to D1. I'm a &lt;a href="https://littlebearapps.com/about" rel="noopener noreferrer"&gt;solo developer&lt;/a&gt; on the Workers Paid plan. I don't have a billing department. I have a credit card and a vague hope that nothing goes catastrophically wrong. That hope cost me 18 days of stress, a near-suspension of my entire account, and a spam folder I should have been checking more carefully.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Two code bugs wrote 4.83 billion rows to Cloudflare D1 in January 2026, generating a ~$4,868 overage on a $5/month account. After 18 days and four escalation channels, Cloudflare waived the full $4,586.64 invoice. I then built a three-layer circuit breaker system so it can't happen again.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi22ra5px1zmvkrak32px.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi22ra5px1zmvkrak32px.webp" alt="Bar chart showing D1 write operations spiking to 1.42 billion in January 2026, with two colour-coded bug periods highlighted" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What went wrong with D1?
&lt;/h2&gt;

&lt;p&gt;Two separate bugs, two separate projects, both writing to D1 without anything to stop them.&lt;/p&gt;

&lt;h3&gt;
  
  
  The embedding worker that couldn't stop writing
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://littlebearapps.com/builds/semantic-librarian/" rel="noopener noreferrer"&gt;Semantic Librarian&lt;/a&gt; is my Australian heritage records project. 1.4 million historical records from the National Library of Australia's Trove archive, searchable via Workers AI embeddings stored in Vectorize, backed by a D1 database. The worker runs on a cron schedule, processing documents in batches: fetch a batch of records, generate embeddings through Workers AI, write the vectors and metadata to D1, move to the next batch.&lt;/p&gt;

&lt;p&gt;The bug was in the "move to the next batch" part. There was no deduplication check. The worker would process a batch of documents, write the embeddings, and on the next cron tick, process the exact same batch again. No offset tracking. No "already processed" flag. Every cycle wrote the same records. And the next cycle wrote them again.&lt;/p&gt;

&lt;p&gt;For four days, from January 11 to 14, the worker ran on autopilot while I was focused on building other things. I wasn't watching the Cloudflare dashboard. Why would I? The worker was deployed, running on a cron, no errors in the logs.&lt;/p&gt;

&lt;p&gt;3.45 billion D1 writes in four days. Here's how that breaks down:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;D1 Writes&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Jan 11&lt;/td&gt;
&lt;td&gt;479,873,853&lt;/td&gt;
&lt;td&gt;$479.91&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jan 12&lt;/td&gt;
&lt;td&gt;1,335,107,674&lt;/td&gt;
&lt;td&gt;$1,259.99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jan 13&lt;/td&gt;
&lt;td&gt;1,424,638,592&lt;/td&gt;
&lt;td&gt;$1,411.40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jan 14&lt;/td&gt;
&lt;td&gt;282,900,856&lt;/td&gt;
&lt;td&gt;$282.65&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Peak day was January 13: 1.42 billion writes in 24 hours. Storage spiked to 10 GB. After I killed the worker and cleaned up, it dropped to 2.4 GB, confirming most of it was duplicate data.&lt;/p&gt;

&lt;p&gt;I didn't notice for four days because the worker was running silently. No errors. No alerts from Cloudflare. No email saying "hey, your D1 writes are 7,000x above normal." Just a worker doing exactly what I told it to do, over and over and over.&lt;/p&gt;

&lt;h3&gt;
  
  
  The harvester without ON CONFLICT
&lt;/h3&gt;

&lt;p&gt;A second project, a GitHub data harvesting tool I was deploying for the first time, had a different version of the same problem. During the initial data seeding phase in early January (Jan 1-4), each scan cycle re-inserted existing records instead of updating them. The INSERT statements had no &lt;code&gt;ON CONFLICT&lt;/code&gt; clause. So every time the harvester ran, it tried to insert records that already existed, and D1 happily accepted every one. About 910 million redundant writes in four days.&lt;/p&gt;

&lt;p&gt;I found this one faster and fixed it on January 5 with proper &lt;code&gt;ON CONFLICT DO UPDATE&lt;/code&gt; clauses. The Semantic Librarian bug started six days later.&lt;/p&gt;

&lt;p&gt;Between the two bugs: 4.83 billion D1 writes in January. To put that in perspective, my normal usage across all 9 databases is maybe 200 writes per hour. The D1 pricing page says $1 per million rows written beyond the 50 million included. 4.83 billion rows at that rate is $4,779 in write charges alone, plus storage, requests, and AI inference costs that pushed the total to $4,868.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you fight a $4,868 bill on a $5/month account?
&lt;/h2&gt;

&lt;p&gt;Slowly. Across multiple channels. With a detailed audit document and more patience than I thought I had.&lt;/p&gt;

&lt;h3&gt;
  
  
  7 days of silence
&lt;/h3&gt;

&lt;p&gt;On February 1, I submitted support ticket #01953111. I didn't just write "please waive this." I attached a full usage audit as a PDF: daily D1 write counts broken down by project, spike period analysis with exact dates and row counts, root cause analysis for each bug, and a list of every fix and architectural improvement I'd deployed to prevent recurrence.&lt;/p&gt;

&lt;p&gt;I wanted to make it easy for whoever reviewed it. Here's exactly what happened, here's exactly why, and here's what I built to make sure it doesn't happen again. If you're going to ask a company to waive $4,868, you should come prepared.&lt;/p&gt;

&lt;p&gt;No response by February 7. Six days. I sent a follow-up asking if it had been assigned to the billing team. Nothing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding the right human
&lt;/h3&gt;

&lt;p&gt;On February 7, I posted to the &lt;a href="https://community.cloudflare.com/t/billing-ticket-01953111-d1-write-overage-from-code-bug-seeking-courtesy-credit/889957" rel="noopener noreferrer"&gt;Cloudflare Community Forum&lt;/a&gt;. A CF Community MVP called neiljay responded quickly and pointed me to a post by cherryjimbo (CF MVP '23-'26) who had shared a direct email for the Head of Billing.&lt;/p&gt;

&lt;p&gt;On February 8, I emailed Dmitry Alexeenko (Head of Billing) directly, referencing my ticket number and cherryjimbo's referral.&lt;/p&gt;

&lt;p&gt;Then I waited.&lt;/p&gt;

&lt;p&gt;Marta from support had actually replied on February 11. The case had been raised with Engineering and was on temporary hold. That should have been reassuring.&lt;/p&gt;

&lt;h3&gt;
  
  
  The spam folder that almost killed my account
&lt;/h3&gt;

&lt;p&gt;On February 18, I found an automated email in my junk folder. It was dated February 17. Cloudflare's billing system had sent a suspension warning: paid services would be disabled for the unpaid invoice. I ran a full account audit. R2 object storage and Analytics Engine were already disabled. My 34 workers were still running, the 8 D1 databases were still accessible, KV and Queues were fine. Partial suspension, not full. Not yet.&lt;/p&gt;

&lt;p&gt;The human support team had my case on hold with Engineering, actively working on it. The automated billing system operated on its own timeline and didn't check whether a human being was already handling the dispute. Two parallel systems, zero coordination between them.&lt;/p&gt;

&lt;p&gt;I sent urgent follow-ups to both Marta and Dmitry. Dmitry's autoresponder came back: the Portugal office was closed for Carnival, and he'd included his mobile number for urgent matters. I texted him. That same day, I &lt;a href="https://www.reddit.com/r/CloudFlare/comments/1r7skeq/support_said_my_48k_billing_dispute_was_on_hold/" rel="noopener noreferrer"&gt;posted to Reddit r/CloudFlare&lt;/a&gt;: "Support said my $4.8k billing dispute was on hold, but the automated system just suspended me anyway."&lt;/p&gt;

&lt;p&gt;By that evening, four escalation channels were active: the original support ticket, the community forum post, the direct email to Dmitry, and the Reddit post. Within hours, things moved. Dmitry responded despite the holiday. Akash Das, Director of Customer Support, personally took the case. He'd read my audit document and accurately identified both root causes: the infinite write loop in the heritage records worker and the missing conflict handling in the data harvesting tool. The case was upgraded to urgent priority.&lt;/p&gt;

&lt;h2&gt;
  
  
  Did Cloudflare do the right thing?
&lt;/h2&gt;

&lt;p&gt;Yes. Daniel Anselmo (Technical Support Shift Engineer) confirmed the full waiver on February 19: $4,586.64, invoice IN 56608827. Account unlocked. All services restored. I re-subscribed to the Workers Paid plan and verified everything: 34 workers running, 8 D1 databases accessible, KV, Queues, R2, Analytics Engine all back online.&lt;/p&gt;

&lt;p&gt;The $4,586.64 was the actual invoice total, slightly different from my $4,868 estimate because of how Cloudflare calculates final billing. Either way, the full amount was waived as a one-time courtesy.&lt;/p&gt;

&lt;p&gt;18 days from first ticket to resolution. That feels long when you're living it, and fair when you look back at it. I want to credit the specific people who made the resolution happen: Akash Das (Director of Customer Support) for personally reviewing the case and identifying both technical root causes accurately from my audit. Dmitry Alexeenko (Head of Billing) for responding and escalating despite a public holiday in Portugal. neiljay and cherryjimbo on the Cloudflare Community Forum for pointing me to the right contact when the ticket queue was silent. And Daniel Anselmo for closing it out cleanly.&lt;/p&gt;

&lt;p&gt;My critique isn't of the people. The people were good. It's of the gap between the human support process (which was thorough once it engaged) and the automated billing system (which nearly suspended my entire account while that support was actively investigating my case). Those two systems don't talk to each other fast enough. A billing dispute that's actively being reviewed by the Director of Support should probably not trigger an automated suspension at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why doesn't D1 have write rate limits?
&lt;/h2&gt;

&lt;p&gt;Mine isn't an isolated case.&lt;/p&gt;

&lt;p&gt;In 2025, &lt;a href="https://www.ofsecman.io/post/postmortem-5-000-incident-in-10-seconds-due-to-cloudflare-d1" rel="noopener noreferrer"&gt;ofsecman.io documented a $5,000+ D1 overage&lt;/a&gt; caused by a missing &lt;code&gt;WHERE&lt;/code&gt; clause in an update statement. A single-row update became a full-table update on every incoming request. Over $5,000 in under 10 seconds. Their conclusion was blunt: "Don't ever use Cloudflare D1 as a Database."&lt;/p&gt;

&lt;p&gt;On the Cloudflare Community Forum, &lt;a href="https://community.cloudflare.com/t/d1-database-index-problem-cost-3200/780753" rel="noopener noreferrer"&gt;a first-time database user reported a $3,200 bill&lt;/a&gt; because they didn't set up an index. Their credit card was overdrawn before they noticed anything. "Cloudflare did not give me any notice or reminder."&lt;/p&gt;

&lt;p&gt;Three different bugs, three different accounts, same outcome. D1 charges per row written with no caps, no write rate limits, and no billing alerts granular enough for D1 write operations specifically. Cloudflare's billing notifications exist, but they're not designed to catch a worker writing a billion rows in 24 hours.&lt;/p&gt;

&lt;p&gt;Scale-to-zero billing is D1's selling point. You pay nothing when your database is idle. That's genuinely great for solo developers and small projects, and it's why I chose Cloudflare's stack in the first place. Scale-to-zero also means scale-to-infinity when a bug amplifies, because the same billing model that charges you nothing at rest charges you per operation at scale with no ceiling.&lt;/p&gt;

&lt;p&gt;D1 hit general availability in &lt;a href="https://blog.cloudflare.com/d1-ga-update/" rel="noopener noreferrer"&gt;September 2024&lt;/a&gt;. The billing model shipped before the billing safeguards did. This is a platform maturity gap, not malice, and I expect Cloudflare will address it. It's a gap that has cost at least three people documented real money though, and probably more who paid the bill without writing about it.&lt;/p&gt;

&lt;p&gt;I'm not saying don't use D1. I still use it across &lt;a href="https://littlebearapps.com/projects" rel="noopener noreferrer"&gt;9 databases for multiple projects&lt;/a&gt;. I'm saying don't use it without your own circuit breakers, because the platform doesn't have them yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What did I build to prevent this from happening again?
&lt;/h2&gt;

&lt;p&gt;After the invoice was waived, I spent a week doing nothing except cost safety. I had been building features. Now I was building guardrails. Nine improvements across three tiers of priority, all shipped and deployed. Tier 1 was critical one-line fixes I could push immediately. Tier 2 was the anomaly detection that would have caught the January incident within an hour. Tier 3 was the longer-term monitoring improvements.&lt;/p&gt;

&lt;p&gt;Everything described here lives in an infrastructure SDK I built after the incident. Two TypeScript packages: a consumer SDK that goes into each worker, and an admin backend with a monitoring dashboard and the telemetry pipeline that feeds it. The admin side runs on Cloudflare Pages, so I can check budget state from my phone - a meaningful upgrade from my previous approach of noticing the invoice a month later.&lt;/p&gt;

&lt;p&gt;I open-sourced it because three people hitting the same $4,000+ wall suggests this isn't just my problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update (March 2026):&lt;/strong&gt; The original circuit breaker infrastructure described above (Platform SDKs) worked but was too complex - 10+ workers, 61 D1 migrations, cross-account HMAC forwarding. I've since replaced it with &lt;a href="https://littlebearapps.com/builds/cf-monitor/" rel="noopener noreferrer"&gt;CF Monitor&lt;/a&gt;, a much simpler rewrite: one worker per account, Analytics Engine + KV only, zero D1. It's open source and available as an npm package (&lt;code&gt;@littlebearapps/cf-monitor&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Each worker imports the consumer SDK, wraps its environment bindings on startup, and the tracking happens automatically. No per-project instrumentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three layers of circuit breakers
&lt;/h3&gt;

&lt;p&gt;The circuit breakers work at three levels of granularity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Feature-level&lt;/strong&gt; is the most precise. Each distinct function in each project gets its own budget. A GitHub scanner, a document embedder, an API endpoint - each has a defined daily limit for D1 writes, KV operations, Workers AI neurons, whatever resources it consumes. If the document embedder goes haywire, it gets disabled. The GitHub scanner keeps running.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project-level&lt;/strong&gt; aggregates all features for a project. Individual features might stay within their budgets while the project total is too high.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Global emergency stop&lt;/strong&gt; is the nuclear option. It kills everything across all projects immediately. I haven't had to use it. I hope I never do.&lt;/p&gt;

&lt;p&gt;Each level enforces progressively: 70% triggers a Slack warning, 90% triggers a critical alert, 100% auto-disables the feature. The breakers auto-reset after 1 hour via KV TTL, so a tripped feature doesn't sit dead until I manually re-enable it at 3am.&lt;/p&gt;

&lt;h3&gt;
  
  
  Counting writes before they become a bill
&lt;/h3&gt;

&lt;p&gt;The tracking can't use D1 writes to count D1 writes. That would be self-defeating. If my monitoring system writes usage data to D1, it's consuming the exact resource it's trying to protect. The January incident itself proved this: my original monitoring infrastructure was writing ~200 rows per hour to D1 just to track usage across all projects. That's not a lot in isolation, but the principle is wrong.&lt;/p&gt;

&lt;p&gt;The consumer SDK wraps your Cloudflare environment bindings with proxies that automatically count every operation. D1 reads and writes, KV gets and puts, R2 uploads, Workers AI inference calls, Vectorize queries - all tracked transparently. When your worker calls &lt;code&gt;env.DB.prepare(...).run()&lt;/code&gt;, the proxy intercepts it, increments a counter, and forwards the call. Your code doesn't change. You call &lt;code&gt;createTrackedEnv(env)&lt;/code&gt; at startup and the counting happens behind the scenes.&lt;/p&gt;

&lt;p&gt;The counters get flushed to &lt;a href="https://developers.cloudflare.com/analytics/analytics-engine/" rel="noopener noreferrer"&gt;Analytics Engine&lt;/a&gt; via a Cloudflare Queue. Analytics Engine is free for the first 25 million data points per month and it's designed for exactly this kind of high-volume telemetry. Zero D1 write overhead for the tracking itself.&lt;/p&gt;

&lt;p&gt;The budget checker queries Analytics Engine roughly every 30 seconds, sums up recent writes per feature, and compares them against the budget defined in a YAML config file. If a feature crosses 70%, Slack warning. 90%, critical alert. 100%, the feature's circuit breaker trips and the SDK starts rejecting operations for that feature until the breaker resets.&lt;/p&gt;

&lt;p&gt;Detection latency: about 30 seconds from a write happening to the circuit breaker evaluating it. In January, my runaway worker ran for four days. Now it would run for about 30 seconds before getting shut down automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  The monitoring that ties it together
&lt;/h3&gt;

&lt;p&gt;On top of the circuit breakers, the nine hardening improvements across three tiers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Auto-reset circuit breakers via KV TTL (1-hour expiry, no manual intervention needed)&lt;/li&gt;
&lt;li&gt;Workers AI cost monitoring added to the sentinel (previously untracked)&lt;/li&gt;
&lt;li&gt;Investigation SQL column fix (the monitoring was querying the wrong column name)&lt;/li&gt;
&lt;li&gt;Hourly D1 write anomaly detection using a 168-hour rolling window with 3-sigma threshold&lt;/li&gt;
&lt;li&gt;Per-project anomaly detection, not just account-wide (previously only checked totals)&lt;/li&gt;
&lt;li&gt;Budget warning thresholds at 70% and 90% with Slack alerts and 1-hour deduplication&lt;/li&gt;
&lt;li&gt;Monthly budget tracking with progressive alerts at 70%, 90%, and exceeded&lt;/li&gt;
&lt;li&gt;Batch resource snapshot inserts, reduced from ~200 individual D1 writes per hour to ~8 batch transactions&lt;/li&gt;
&lt;li&gt;Six missing budget overrides for features that were falling back to overly generous defaults&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All nine shipped and deployed within a week. The batch insert change alone (item 8) cut monitoring D1 write overhead by 96%, from ~200 individual writes per hour down to ~8 batch transactions. The hourly anomaly detection (item 4) would have caught the January spike within its first hour: 480 million writes in a single day is roughly 15,000 standard deviations above my normal baseline of ~200 writes per hour. The 3-sigma threshold would have tripped before the first hour was up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What pattern do serverless billing failures share?
&lt;/h2&gt;

&lt;p&gt;Three cases. Same billing model. Same outcome.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;My infinite loop&lt;/th&gt;
&lt;th&gt;ofsecman.io&lt;/th&gt;
&lt;th&gt;CF Community&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Root cause&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Missing deduplication&lt;/td&gt;
&lt;td&gt;Missing WHERE clause&lt;/td&gt;
&lt;td&gt;Missing index&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Time to cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4 days&lt;/td&gt;
&lt;td&gt;10 seconds&lt;/td&gt;
&lt;td&gt;Days (unclear)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bill&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$4,868&lt;/td&gt;
&lt;td&gt;$5,000+&lt;/td&gt;
&lt;td&gt;$3,200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Warning from CF&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The common denominator isn't the code. Bugs happen. I wrote about this in &lt;a href="https://littlebearapps.com/blog/dogfooding-bugs-tests-cant-find" rel="noopener noreferrer"&gt;my last post on dogfooding&lt;/a&gt;: the bugs that matter most are the ones that live in the gaps between states, not in the states themselves. A worker that runs correctly once will also run correctly a billion times. The bug isn't in the execution; it's in the assumption that anything would stop it.&lt;/p&gt;

&lt;p&gt;The common denominator is that D1's billing model has no safety net between "working correctly" and "catastrophic overage." No write rate limit. No anomaly detection. No automatic pause when usage spikes 10,000x above normal. The billing system faithfully counts every row, generates an invoice, and sends it to your credit card.&lt;/p&gt;

&lt;p&gt;Every serverless database with per-operation billing has this exposure. D1 isn't unique in charging per write. Most managed databases give you some combination of connection pools, query timeouts, billing caps, or at minimum a usage alert that fires before you hit four figures. D1 currently offers none of those for write operations.&lt;/p&gt;

&lt;p&gt;If you're building on D1 in production, build your own circuit breakers. I did. The infrastructure described in this post took about a week to build and deploy. The January invoice would have taken me considerably longer to pay off. That's a pretty clear cost-benefit calculation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common questions about D1 billing and cost protection
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can you set a billing cap on Cloudflare D1?
&lt;/h3&gt;

&lt;p&gt;No. As of March 2026, Cloudflare doesn't offer a hard billing cap for D1 write operations. You can set up billing notifications, but they're not granular enough to catch a worker writing a billion rows overnight. Application-level circuit breakers are currently the only option.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you detect a runaway worker before the bill arrives?
&lt;/h3&gt;

&lt;p&gt;Monitor D1 write counts at sub-hourly intervals. I use Analytics Engine (free tier, 25 million events per month) to track every D1 write via proxied environment bindings, with a budget checker that evaluates every 30 seconds. Anomaly detection with a 168-hour rolling window catches spikes that exceed 3 standard deviations from normal. The whole system adds zero D1 write overhead because telemetry goes through Analytics Engine, not D1.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is this just a D1 problem?
&lt;/h3&gt;

&lt;p&gt;The billing exposure exists on any serverless platform with per-operation pricing and no rate limits. D1 is the most visible example right now because it's relatively new (GA 2024) and write-heavy workloads can accumulate cost fast. DynamoDB, Firestore, and PlanetScale all have their own versions of this risk, though most offer billing alerts or auto-scaling limits that D1 currently lacks.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The timeline, dollar amounts, and technical details in this post are reconstructed from &lt;a href="https://community.cloudflare.com/t/billing-ticket-01953111-d1-write-overage-from-code-bug-seeking-courtesy-credit/889957" rel="noopener noreferrer"&gt;support ticket #01953111&lt;/a&gt;, &lt;a href="https://www.reddit.com/r/CloudFlare/comments/1r7skeq/" rel="noopener noreferrer"&gt;Reddit r/CloudFlare&lt;/a&gt;, Cloudflare dashboard data, and internal audit documents.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>serverless</category>
      <category>buildinpublic</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Dogfooding found 22 bugs my 1,548 tests missed</title>
      <dc:creator>Nathan Schram</dc:creator>
      <pubDate>Thu, 19 Mar 2026 04:28:20 +0000</pubDate>
      <link>https://dev.to/nathanschram/dogfooding-found-22-bugs-my-1548-tests-missed-31m4</link>
      <guid>https://dev.to/nathanschram/dogfooding-found-22-bugs-my-1548-tests-missed-31m4</guid>
      <description>&lt;p&gt;Last week I found 86 orphaned processes eating 10.3 GB of RAM on my VPS. The week before that, my stall monitor fired because I went for a walk. And my own documentation tool told me my docs were stale.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real use of three open-source tools found 22 bugs that 1,548 automated tests missed.&lt;/li&gt;
&lt;li&gt;Bugs cluster in two categories: resource accumulation over time, and gaps between "works" and "works for me".&lt;/li&gt;
&lt;li&gt;Test suites check states. Dogfooding finds the transitions between them.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;None of these would show up in a test suite. I found them because I actually use my own tools - not as a testing practice, just because they solve problems I have. Test suites tell you if something works. Using your own product tells you if it's any good. Those are different questions with different answers. Joel Spolsky &lt;a href="https://www.joelonsoftware.com/2001/05/05/what-is-the-work-of-dogs-in-this-country/" rel="noopener noreferrer"&gt;described this gap&lt;/a&gt; twenty-five years ago - he found 45 bugs in one Sunday afternoon of actually using CityDesk to run his blog. "All the testing we did, meticulously pulling down every menu and seeing if it worked right, didn't uncover the showstoppers."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Eating_your_own_dog_food" rel="noopener noreferrer"&gt;Dogfooding&lt;/a&gt;&lt;/strong&gt; is the practice of using your own products as your primary tools - not as a scheduled testing exercise, but as part of how you work. &lt;strong&gt;&lt;a href="https://github.com/littlebearapps/untether" rel="noopener noreferrer"&gt;Untether&lt;/a&gt;&lt;/strong&gt; is a Telegram bridge for AI coding agents. &lt;strong&gt;&lt;a href="https://github.com/littlebearapps/pitchdocs" rel="noopener noreferrer"&gt;PitchDocs&lt;/a&gt;&lt;/strong&gt; is a documentation generator for code repositories. &lt;strong&gt;&lt;a href="https://github.com/littlebearapps/outlook-assistant" rel="noopener noreferrer"&gt;Outlook Assistant&lt;/a&gt;&lt;/strong&gt; is an &lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;MCP server&lt;/a&gt; that gives AI assistants access to Outlook email, calendar, and contacts.&lt;/p&gt;

&lt;p&gt;I run three open-source tools that I built for myself. Untether and PitchDocs I use every day. Outlook Assistant I pull out when the job calls for it - digging through inbox, sent, archived, and deleted folders to find invoices and receipts for tax time, or trawling through calendar events across linked calendars. Not daily, but when I do use it, I use it hard. And honestly, the bugs I find through real use are the ones that matter most - the ones your users would hit first.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fskpdpeihwdev2d0bfk6d.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fskpdpeihwdev2d0bfk6d.webp" alt="Terminal listing MCP server processes across Claude Code sessions - each session spawns about 14 servers, consuming over 100 MB each" width="800" height="643"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What does daily Untether use actually find?
&lt;/h2&gt;

&lt;p&gt;In 3 days of daily use, I shipped 8 releases and found bugs that 1,548 automated tests missed. The bugs that matter live in the transitions between states - sleeping and waking, busy and stuck, present and away.&lt;/p&gt;

&lt;p&gt;I use &lt;a href="https://github.com/littlebearapps/untether" rel="noopener noreferrer"&gt;Untether&lt;/a&gt; for basically everything. Voice notes from the couch, approving file changes while making coffee, kicking off test runs from my phone. It's not a tool I built and then test occasionally - it's how I do my job.&lt;/p&gt;

&lt;p&gt;Last week I noticed a 41-minute stall in one of my chats had gone completely undetected. A wrangler tail command got stuck, no events were flowing, and Untether just sat there silently. No warning, nothing. I only caught it because I was actually waiting for a result and it never came.&lt;/p&gt;

&lt;p&gt;So I &lt;a href="https://github.com/littlebearapps/untether/issues/92" rel="noopener noreferrer"&gt;built a stall monitor&lt;/a&gt;. Seemed simple enough - if no events arrive for 5 minutes, send me a Telegram warning. v0.34.0, shipped, done.&lt;/p&gt;

&lt;p&gt;Then the real education started.&lt;/p&gt;

&lt;h3&gt;
  
  
  The stall monitor that couldn't tell stuck from busy
&lt;/h3&gt;

&lt;p&gt;I ran pytest through Untether and the stall monitor fired 3 times in 10 minutes. The process was alive and working fine, but it just wasn't emitting progress events during tool execution. From the monitor's perspective, silence meant "stuck". In reality, silence meant "busy running your tests".&lt;/p&gt;

&lt;p&gt;I had to add /proc diagnostics - CPU usage, memory, TCP connections, file descriptors, child processes - so the monitor could tell the difference between "stuck" and "busy doing something useful." That became v0.34.1. Along with a liveness watchdog, progressive warnings, and a JsonlStreamState tracker that remembers recent events in a ring buffer. The first version couldn't tell silence from activity.&lt;/p&gt;

&lt;p&gt;Then I closed my laptop overnight. Came back the next morning to find the &lt;a href="https://github.com/littlebearapps/untether/issues/99" rel="noopener noreferrer"&gt;stall monitor stuck in an infinite loop&lt;/a&gt;. The subprocess had died when the laptop went to sleep, but the monitor kept firing "No progress" warnings every 3 minutes - 7 of them stacked up by the time I opened the lid. Each one showing &lt;code&gt;pid=None, process_alive=None&lt;/code&gt; because it couldn't even find the process. It just kept warning about a ghost.&lt;/p&gt;

&lt;p&gt;So I built dead process detection, a zombie warning cap (3 warnings before auto-cancel, absolute cap at 10), and early PID threading so the monitor knows about the subprocess from spawn, not from the first event. I also made /cancel work as a standalone command without having to reply to the progress message - because on mobile, finding a specific message to reply to when your screen is full of stall warnings is not fun. v0.34.2.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnke4seos74b55utjggh2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnke4seos74b55utjggh2.webp" alt="Telegram chat showing 6 stacked stall monitor warnings escalating from 5 to 20 minutes, with a failed /cancel attempt" width="590" height="1280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  When "idle" means walking Normi
&lt;/h3&gt;

&lt;p&gt;Then I went for a walk.&lt;/p&gt;

&lt;p&gt;Claude was waiting for approval on a file change. I had the inline keyboard showing on my phone - Approve, Deny, Skip - but I was out walking Normi and didn't reply for about 6 minutes. Stall monitor fired. "No progress for 6 min."&lt;/p&gt;

&lt;p&gt;That's not a stall. I was just away. The difference between "the process is stuck" and "the human hasn't replied yet" is obvious to a person but invisible to a monitor that only watches event timestamps. Added approval-aware thresholds - 30 minutes when there's an inline keyboard showing, 5 minutes normally.&lt;/p&gt;

&lt;p&gt;A long pytest run triggered it again. A 10-minute test suite is not a stall. Built a &lt;a href="https://github.com/littlebearapps/untether/issues/105" rel="noopener noreferrer"&gt;three-tier threshold system&lt;/a&gt;: 5 minutes for normal operation, 10 minutes during active tool execution, 30 minutes during approval waits. v0.34.3.&lt;/p&gt;

&lt;p&gt;Four releases. One "simple" feature. Each release driven by a real moment where I was actually using the tool and it got something wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  86 orphaned processes and 10.3 GB of RAM
&lt;/h3&gt;

&lt;p&gt;While chasing all of this down, I noticed my VPS was getting sluggish. Telegram messages were slow, progress updates felt laggy. I found &lt;a href="https://github.com/littlebearapps/untether/issues/88" rel="noopener noreferrer"&gt;86 orphaned MCP server processes&lt;/a&gt; eating 10.3 GB of RAM.&lt;/p&gt;

&lt;p&gt;Here's what happened: each Claude Code session spawns about 14 MCP server processes - brave-search, context7, apify, jina, github, trello, pal, and so on. My systemd unit file was using &lt;a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.kill.html" rel="noopener noreferrer"&gt;&lt;code&gt;KillMode=process&lt;/code&gt;&lt;/a&gt;, which means when Untether restarts, systemd kills the main Python process but leaves all the children alive. They get reparented to systemd and just sit there, holding memory, doing nothing. I'd been iterating fast - 64 service restarts in one day during the v0.30-v0.33 development cycle. Each restart leaked another 14 processes. They accumulated silently.&lt;/p&gt;

&lt;p&gt;One config change to &lt;code&gt;KillMode=control-group&lt;/code&gt; and all 10.3 GB came back.&lt;/p&gt;

&lt;p&gt;Then I built a &lt;a href="https://github.com/littlebearapps/untether/issues/91" rel="noopener noreferrer"&gt;subprocess watchdog&lt;/a&gt; to catch a related problem: when a runner subprocess exits but its MCP server children keep stdout pipes open, &lt;code&gt;proc.wait()&lt;/code&gt; blocks forever because anyio waits for both process exit and pipe drain. The session just hangs with no completion event. The watchdog polls process liveness with &lt;code&gt;os.kill(pid, 0)&lt;/code&gt; instead, gives a 5-second grace period, then kills the orphan process group.&lt;/p&gt;

&lt;p&gt;None of this shows up in a test suite. The laptop sleep bug requires an actual laptop going to actual sleep. The "went for a walk" edge case requires a human being away from their phone. The orphaned process leak requires 64 restarts in one day of real development. The subprocess pipe deadlock requires actual MCP servers holding actual file descriptors. You can't mock this stuff. You can only find it by living with the tool.&lt;/p&gt;

&lt;p&gt;Eight releases in 3 days. 545 new tests (1003 to 1548 total). And a stall monitor that actually works now, because it got tested by my life, not just my test suite. Michael Bolton &lt;a href="https://developsense.com/blog/2009/08/testing-vs-checking" rel="noopener noreferrer"&gt;calls this the difference&lt;/a&gt; between &lt;em&gt;testing&lt;/em&gt; and &lt;em&gt;checking&lt;/em&gt; - automated tests check what you already know to look for, but they can't discover the things you never thought to test.&lt;/p&gt;

&lt;p&gt;Every one of these bugs lived in a gap between states. Sleeping and waking. Busy and stuck. Present and away. Tests verify that individual states work. Dogfooding finds the transitions between them - the seams where things actually break.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when your docs tool says your docs are bad?
&lt;/h2&gt;

&lt;p&gt;Running my own documentation tool on its own repo exposed context drift, content filter blocks, and stale docs that test fixtures would never catch. The most embarrassing moment was when PitchDocs told me my own docs were stale.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/littlebearapps/pitchdocs" rel="noopener noreferrer"&gt;PitchDocs&lt;/a&gt; generates documentation for repos. READMEs, changelogs, roadmaps, security policies, user guides, AI context files. I use it on all my repos. Including PitchDocs itself.&lt;/p&gt;

&lt;p&gt;That's where it gets interesting.&lt;/p&gt;

&lt;p&gt;I ran /docs-audit on PitchDocs one morning and got a score of... not great. My own documentation tool was telling me my docs were stale. The irony was not lost on me. But that's the point - I wouldn't have noticed without actually running the tool on my own work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Context drift across 7 files
&lt;/h3&gt;

&lt;p&gt;The bigger discovery came from context file drift. PitchDocs generates AI context files for 7 different tools: CLAUDE.md, AGENTS.md, .cursorrules, copilot-instructions.md, .windsurfrules, .clinerules, and GEMINI.md. When I added the platform-profiles skill and the /pitchdocs:platform command, I had to manually update counts in all 6 of those files plus llms.txt. "15 skills" became "16 skills", "12 commands" became "13 commands", across 7 files.&lt;/p&gt;

&lt;p&gt;I did it. Then I added another feature and had to do it again. And again.&lt;/p&gt;

&lt;p&gt;That friction became Context Guard. First version was a post-commit hook that warns you when AI context files have drifted from the codebase. Then I upgraded it to a two-tier system - a gentle nudge after commits, plus a pre-commit guard that blocks the commit entirely if context files are stale. The whole thing exists because I kept getting bitten by my own documentation going out of sync while I was actively building the tool that's supposed to prevent exactly that problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  When the content filter blocks your own docs
&lt;/h3&gt;

&lt;p&gt;Then Claude Code's &lt;a href="https://github.com/littlebearapps/pitchdocs/pull/13" rel="noopener noreferrer"&gt;content filter blocked me&lt;/a&gt; from generating a CODE_OF_CONDUCT file. PitchDocs was trying to write standard open-source community documents, and the API returned HTTP 400 errors because the content triggered safety filters. The same thing happened with SECURITY.md. I had to build chunked writing workarounds and add a content-filter.md rule with risk levels and mitigations. This only surfaced because I was actually generating these files for real repos, not test fixtures.&lt;/p&gt;

&lt;p&gt;The cross-tool compatibility matrix came from real testing. PitchDocs claims to work with 9 AI tools. That claim exists because I actually installed it in Cursor, Windsurf, Codex CLI, and Gemini CLI and watched what happened. Each tool had its own quirks. The compatibility docs aren't theoretical - they're field notes from running the same skill files across different environments and documenting where they broke.&lt;/p&gt;

&lt;p&gt;The README went through 11 revisions in 11 days. I kept applying PitchDocs to its own README, reading the output, and thinking "no, that's not right". The 4-question test, the lobby principle, the feature benefits extraction with persona inference - all of it came from repeatedly failing to describe my own product well and building features to fix the specific ways it failed.&lt;/p&gt;

&lt;p&gt;Look, a documentation tool that doesn't use itself to generate its own docs is just a theory. Running /docs-audit on PitchDocs and getting a mediocre score was embarrassing, but it showed me exactly what to fix. I'd rather be embarrassed than wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when you let AI manage your email?
&lt;/h2&gt;

&lt;p&gt;Using AI for intensive email tasks uncovered 12 bugs in one release cycle, plus two real security vulnerabilities. The safety controls - dry-run mode, rate limiting, recipient allowlist - all came from production failures that slipped through every other layer, not threat modelling.&lt;/p&gt;

&lt;p&gt;I don't manage my email through Claude Code every day. But when I need to find something specific - tax receipts scattered across inbox and sent and archived, invoices from three months ago, calendar events linked from shared calendars - that's when I pull out &lt;a href="https://github.com/littlebearapps/outlook-assistant" rel="noopener noreferrer"&gt;Outlook Assistant&lt;/a&gt;. Claude Code can programmatically search across every folder, collate the results, and export what I need. It turns a full afternoon of manual searching into a 10-minute conversation.&lt;/p&gt;

&lt;p&gt;The first version had 55 tools. That's what happens when you map every Microsoft Graph API endpoint to its own MCP tool. Read email, search email, list email, get attachment, list attachments, send email, update email, move email, flag email. Then repeat for calendar, contacts, folders, rules, and categories. It worked. The API coverage was thorough.&lt;/p&gt;

&lt;p&gt;Then I actually used it in conversation.&lt;/p&gt;

&lt;h3&gt;
  
  
  55 tools and the context window
&lt;/h3&gt;

&lt;p&gt;55 tools consume a lot of tokens. Each tool has a name, description, and parameter schema that gets loaded into context. I hit token limits in real conversations - not contrived long conversations, just normal "search for that email from last week, read it, draft a reply" workflows. The context window was getting eaten by tool definitions before I could do real work.&lt;/p&gt;

&lt;p&gt;I consolidated 55 tools down to 20 using what I called the STRAP pattern - action-parameter consolidation where one tool handles multiple operations through an action parameter. manage-emails with actions like flag, move, categorise, export. manage-calendar with create, update, delete. 64% reduction. About 11,000 tokens saved per turn. I only knew the limit was a problem because I was the one hitting it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmz3zidlew73decjre0ya.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmz3zidlew73decjre0ya.webp" alt="Diagram comparing 55 individual MCP tools consolidated to 20 using the STRAP action-parameter pattern" width="792" height="1280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Silent API failures and progressive search
&lt;/h3&gt;

&lt;p&gt;Microsoft's $search API &lt;a href="https://github.com/littlebearapps/outlook-assistant/issues/35" rel="noopener noreferrer"&gt;silently fails on personal Outlook.com accounts&lt;/a&gt;. Not an error - it just returns no results. I found out because I searched for an email I knew existed and got nothing back. Built progressive search: try $search first, fall back to $filter with subject/from matching, then try broader date-range filtering, then full scan. Four strategies, automatic fallback, with a warning message so you know which strategy actually found your email.&lt;/p&gt;

&lt;p&gt;Twelve bugs came from real use in the v3.1.0 cycle. A folder that doesn't exist would &lt;a href="https://github.com/littlebearapps/outlook-assistant/issues/46" rel="noopener noreferrer"&gt;silently fall back to the inbox&lt;/a&gt; instead of telling you it couldn't find it. Asking for count=0 emails would return everything instead of nothing. The "minimal" view mode said "No content" instead of showing a body preview. HTML email bodies weren't detected correctly for certain Content-Types. Conversation export crashed on personal accounts. Calendar events showed &lt;a href="https://github.com/littlebearapps/outlook-assistant/issues/51" rel="noopener noreferrer"&gt;UTC timestamps instead of local time&lt;/a&gt;. Inbox rule sequences showed internal IDs instead of readable order.&lt;/p&gt;

&lt;p&gt;Each of these is a small thing. None of them would fail a test that checks "does the API return a 200?" But they only matter when you're a person trying to get through your email.&lt;/p&gt;

&lt;h3&gt;
  
  
  Safety controls born from production failures, not documentation
&lt;/h3&gt;

&lt;p&gt;The safety controls tell their own story. Dry-run mode for sending emails. Session rate limiting. Recipient allowlist. None of these came from a threat model document. They came from failures that got past the test suite, past AI agent verification, and past CI checks - and only surfaced when I was actually using the tool on real email.&lt;/p&gt;

&lt;p&gt;The first time Claude drafted a reply and I realised it was one approval button away from sending it to the wrong person, I built dry-run mode that afternoon. Not the next sprint. That afternoon. Rate limiting came after a loop scenario almost happened in production - the kind of thing where the AI gets confused and tries to send 50 replies. The test suite didn't catch it because the tests mock the send endpoint. The AI agent review didn't catch it because it looked correct in isolation. CI passed. It only became obvious when I was sitting there watching it happen in real time. The allowlist lets me restrict which addresses Claude can actually send to during testing, because "oops, sent a test email to a client" is not a recoverable mistake.&lt;/p&gt;

&lt;p&gt;That's what dogfooding adds as a layer. You run the test suites. You get the AI agent to stress-test it. You run CI. And then you spend days or weeks actually using it in production, pushing it to its limits, and you find the edge cases that none of those layers caught. The guardrails in Outlook Assistant didn't come from security best practices or compliance requirements. They came from real production use where things went wrong after every automated check had passed.&lt;/p&gt;

&lt;p&gt;I also found two real security vulnerabilities through production use - an &lt;a href="https://github.com/littlebearapps/outlook-assistant/issues/60" rel="noopener noreferrer"&gt;XSS issue&lt;/a&gt; and an &lt;a href="https://github.com/littlebearapps/outlook-assistant/issues/63" rel="noopener noreferrer"&gt;information exposure bug&lt;/a&gt; - that I fixed and then added &lt;a href="https://github.com/littlebearapps/outlook-assistant/issues/65" rel="noopener noreferrer"&gt;CodeQL SAST scanning&lt;/a&gt; to catch that class of problem earlier. Those bugs shouldn't have made it as far as they did, and they wouldn't have been found as quickly without me actually using the tool on real email.&lt;/p&gt;

&lt;h2&gt;
  
  
  What pattern do these bugs share?
&lt;/h2&gt;

&lt;p&gt;Across all three products, the bugs I find through real use cluster into two categories that tests can't reach.&lt;/p&gt;

&lt;p&gt;Resource accumulation over time. 86 orphaned processes eating 10.3 GB of RAM. Documentation counts going stale across 7 files. Token budgets getting consumed by tool definitions before the real work starts. These problems are invisible in short test runs. They only appear after hours or days of real use.&lt;/p&gt;

&lt;p&gt;The gap between "works" and "works for me." A stall monitor that can't tell the difference between a stuck process and a person walking their dog. An email search that returns nothing because Microsoft's API silently fails on personal accounts. A documentation tool that can't generate a CODE_OF_CONDUCT because the content filter blocks it. These aren't bugs in the traditional sense. They're mismatches between what the code does and what the person using it needs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bug type&lt;/th&gt;
&lt;th&gt;What tests check&lt;/th&gt;
&lt;th&gt;What dogfooding found&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Resource leaks&lt;/td&gt;
&lt;td&gt;Memory per isolated test run&lt;/td&gt;
&lt;td&gt;86 processes accumulating over 64 restarts.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State transitions&lt;/td&gt;
&lt;td&gt;Each state in isolation&lt;/td&gt;
&lt;td&gt;Gaps between sleeping/waking, busy/stuck, present/away.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API quirks&lt;/td&gt;
&lt;td&gt;Mocked API returns 200 OK&lt;/td&gt;
&lt;td&gt;Microsoft search silently returns nothing on personal accounts.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UX friction&lt;/td&gt;
&lt;td&gt;Feature exists and works&lt;/td&gt;
&lt;td&gt;Finding a reply button while stall warnings fill your screen.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Safety gaps&lt;/td&gt;
&lt;td&gt;Permissions check passes&lt;/td&gt;
&lt;td&gt;Nearly sending email to the wrong person in production.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The common thread across all of these bugs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They only show up after sustained real use, not isolated test runs.&lt;/li&gt;
&lt;li&gt;They live in the transitions between states, not in the states themselves. (Nancy Leveson &lt;a href="http://sunnyday.mit.edu/papers/jsr.pdf" rel="noopener noreferrer"&gt;found the same pattern&lt;/a&gt; in spacecraft accidents - the software worked per spec, but failed at state transitions under real conditions.)&lt;/li&gt;
&lt;li&gt;They're invisible to automated tests because you can't mock a human walking their dog.&lt;/li&gt;
&lt;li&gt;They matter more to users than the logic errors your test suite catches.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think test suites give you a kind of false confidence. You get to 90% coverage and you feel good about it. Martin Fowler &lt;a href="https://martinfowler.com/bliki/TestCoverage.html" rel="noopener noreferrer"&gt;made this point years ago&lt;/a&gt; - high coverage is useful for finding untested code, but it's "of little use as a numeric statement of how good your tests are." The bugs that actually matter - the ones your users would hit on day one - don't live in test cases. They live in the space between your code and someone's actual life.&lt;/p&gt;

&lt;p&gt;That said, I don't dogfood as a deliberate practice. I don't schedule "dogfooding sessions" or maintain a testing protocol. I use these tools because they solve problems I have. The bugs get found as a side effect of genuine use, not deliberate testing. The stall monitor saga happened because I actually rely on Untether to work. The context drift problem surfaced because I actually use PitchDocs on my repos. The dry-run mode exists because I actually use Claude to handle real email tasks.&lt;/p&gt;

&lt;p&gt;If you're building something and you don't use it yourself, you're shipping based on hope rather than experience. Jason Fried &lt;a href="https://37signals.com/podcast/eat-your-own-dogfood/" rel="noopener noreferrer"&gt;put it simply&lt;/a&gt;: "A good chef is tasting their food as they go." And experience, it turns out, is a much better debugger than pytest.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common questions about dogfooding
&lt;/h2&gt;

&lt;p&gt;Some things I get asked when I talk about this approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between dogfooding and automated testing?
&lt;/h3&gt;

&lt;p&gt;Testing checks that code produces expected outputs from known inputs. Dogfooding exposes code to conditions you can't mock - laptop sleep, distracted humans, resources accumulating over days. 8 of the bugs I found in Untether exist in the gaps between states that tests treat as isolated. Both matter, but they catch different classes of problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  How many bugs does real use catch that tests miss?
&lt;/h3&gt;

&lt;p&gt;Across three products in one month, real use surfaced 22 bugs that automated tests missed entirely. They split into resource accumulation (86 orphaned processes, 10.3 GB of leaked RAM) and works-vs-works-for-me gaps. Tests catch logic errors. Dogfooding catches experience errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do you schedule dedicated dogfooding sessions?
&lt;/h3&gt;

&lt;p&gt;No. I use these tools because they solve real problems - Untether for mobile coding, PitchDocs for repo docs, Outlook Assistant for email. The bugs surface as a side effect of genuine use. Scheduled testing would never reproduce "walked Normi for 6 minutes" as an edge case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can dogfooding replace automated testing?
&lt;/h3&gt;

&lt;p&gt;No. Untether has 1,548 automated tests and I run them constantly. Automated tests catch regressions and logic errors reliably. Dogfooding catches a different category - state transitions, resource leaks, UX friction that only appears in real workflows. You need both.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does dogfooding work differently for solo developers?
&lt;/h3&gt;

&lt;p&gt;Solo developers are their own most demanding user. I put Untether through 64 service restarts in a single day, which revealed 86 orphaned processes. Real development patterns create edge cases that no QA team testing "normal usage" would ever reproduce.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;All numbers in this post are verified from GitHub issues, PRs, and commit history. &lt;a href="https://github.com/littlebearapps/untether" rel="noopener noreferrer"&gt;Untether&lt;/a&gt;, &lt;a href="https://github.com/littlebearapps/pitchdocs" rel="noopener noreferrer"&gt;PitchDocs&lt;/a&gt;, and &lt;a href="https://github.com/littlebearapps/outlook-assistant" rel="noopener noreferrer"&gt;Outlook Assistant&lt;/a&gt; are all open source.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>testing</category>
      <category>opensource</category>
      <category>devlog</category>
    </item>
  </channel>
</rss>
