<?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: Emma Schmidt</title>
    <description>The latest articles on DEV Community by Emma Schmidt (@emma_schmidt_).</description>
    <link>https://dev.to/emma_schmidt_</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%2F3450005%2Fcd01195d-0cef-48c0-9d0b-8033f6246f7e.jpeg</url>
      <title>DEV Community: Emma Schmidt</title>
      <link>https://dev.to/emma_schmidt_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/emma_schmidt_"/>
    <language>en</language>
    <item>
      <title>Your App Has AI Features. That's Not the Same Thing as Being an AI Company Anymore.</title>
      <dc:creator>Emma Schmidt</dc:creator>
      <pubDate>Fri, 31 Jul 2026 10:48:07 +0000</pubDate>
      <link>https://dev.to/emma_schmidt_/your-app-has-ai-features-thats-not-the-same-thing-as-being-an-ai-company-anymore-5a0n</link>
      <guid>https://dev.to/emma_schmidt_/your-app-has-ai-features-thats-not-the-same-thing-as-being-an-ai-company-anymore-5a0n</guid>
      <description>&lt;p&gt;There's a quiet but important distinction reshaping how serious software gets built in 2026, and most teams haven't caught up to it yet. Bolting a chatbot onto an existing app and designing an application where AI is the actual architecture are two completely different engineering disciplines, and the gap between them is becoming one of the clearest competitive differentiators in software today. This is exactly the shift driving demand for real &lt;strong&gt;&lt;a href="https://zignuts.com/ai-services/custom-ai-development?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;custom AI development&lt;/a&gt;&lt;/strong&gt;, not a plugin added after the fact, but AI woven into how a system makes decisions, processes data, and adapts from the very first architectural diagram.&lt;/p&gt;

&lt;p&gt;Here's what actually separates AI-native software from AI-enhanced software, why the distinction matters more than it sounds, and how to think about it if you're planning a build.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-Enhanced vs AI-Native: The Real Difference
&lt;/h2&gt;

&lt;p&gt;Most existing "AI-powered" products today are AI-enhanced. A traditional application, built the same way it always would have been, with a recommendation widget, a search-with-AI feature, or a support &lt;strong&gt;&lt;a href="https://zignuts.com/chatbot-development-services?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;chatbot&lt;/a&gt;&lt;/strong&gt; added on top. The underlying system, its data flow, its decision logic, its architecture, remains essentially unchanged.&lt;/p&gt;

&lt;p&gt;An AI-native application is architected differently from day one. Intelligence isn't a feature sitting on top, it's part of how the system actually functions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;AI-Enhanced&lt;/th&gt;
&lt;th&gt;AI-Native&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where AI sits&lt;/td&gt;
&lt;td&gt;Bolted on as a feature or module&lt;/td&gt;
&lt;td&gt;Built into the core architecture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data flow&lt;/td&gt;
&lt;td&gt;Static, mostly predefined&lt;/td&gt;
&lt;td&gt;Continuous, feeding real-time model decisions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adaptability&lt;/td&gt;
&lt;td&gt;Fixed behavior between releases&lt;/td&gt;
&lt;td&gt;Learns and adjusts from ongoing data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical origin&lt;/td&gt;
&lt;td&gt;Retrofit of an existing product&lt;/td&gt;
&lt;td&gt;Designed around AI from the first architecture decision&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure mode when done poorly&lt;/td&gt;
&lt;td&gt;AI feature feels bolted on, inconsistent with the rest of the app&lt;/td&gt;
&lt;td&gt;Poor governance and evaluation gaps compound across the whole system&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Neither approach is inherently wrong. A lot of genuinely good products are AI-enhanced and don't need to be anything else. But for products where intelligence is meant to be the actual value proposition, retrofitting an existing architecture tends to be far more expensive and painful than building AI-native from the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Shift Is Happening Now
&lt;/h2&gt;

&lt;p&gt;A few forces are converging to push serious teams toward AI-native thinking rather than incremental AI feature additions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Retrofitting is proving genuinely costly.&lt;/strong&gt; Teams that added AI as an afterthought are running into architectural walls, data pipelines that weren't built for continuous model input, decision logic that was never designed to be adaptive, and it's turning into expensive rework rather than a quick feature addition&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluation and observability are now considered table stakes, not extras.&lt;/strong&gt; Serious AI development in 2026 increasingly means having real evaluation pipelines that systematically test outputs against known-good examples, not just shipping a prompt and hoping it behaves&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security expectations have caught up with adoption.&lt;/strong&gt; Prompt injection, data exfiltration through AI interfaces, and output manipulation are now treated as real attack vectors, which means the AI layer needs the same security discipline as any other system boundary, not a bolted-on afterthought&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-model orchestration is becoming a standard architectural component.&lt;/strong&gt; Rather than routing everything through one model, AI-native systems increasingly route different tasks to different models based on cost, latency, and capability, which only works cleanly if the architecture was designed with that flexibility in mind&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What AI-Native Architecture Actually Requires
&lt;/h2&gt;

&lt;p&gt;Building this way touches more of the stack than a typical feature addition would.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data pipelines designed for continuous flow&lt;/strong&gt;, not periodic batch updates, since adaptive systems need fresh data to actually adapt&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluation infrastructure built in from the start&lt;/strong&gt;, tracking output quality, catching model regressions, and validating behavior against real, known-good examples&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear governance around AI decision-making&lt;/strong&gt;, especially for anything touching regulated data or high-stakes outcomes, where explainability and audit trails matter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture flexible enough for multi-model routing&lt;/strong&gt;, so the system isn't locked into a single model provider or a single point of failure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security treated as a first-class concern for the AI layer specifically&lt;/strong&gt;, not assumed to be covered by the same protections as the rest of the application&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Real-World Scenarios Where This Distinction Actually Matters
&lt;/h2&gt;

&lt;p&gt;To make this concrete, here's where the AI-native versus AI-enhanced choice genuinely changes the outcome.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A support platform&lt;/strong&gt; adding a chatbot to an existing ticketing system is AI-enhanced. A support platform where routing, prioritization, and resolution suggestions are all driven continuously by live data from the start is AI-native, and it behaves fundamentally differently as ticket volume and complexity grow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A financial analytics tool&lt;/strong&gt; with an AI summary feature bolted onto existing dashboards is AI-enhanced. One where risk scoring and forecasting are core to how the system processes every transaction is AI-native&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An internal knowledge tool&lt;/strong&gt; with search-with-AI added to an existing wiki is AI-enhanced. One built around continuously updated embeddings and adaptive retrieval from the ground up is AI-native&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A logistics platform&lt;/strong&gt; with a recommendation widget for routing suggestions is AI-enhanced. One where routing decisions are made continuously by an adaptive model integrated into the core dispatch logic is AI-native&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where Teams Get This Wrong
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Assuming AI-native means "use more AI features."&lt;/strong&gt; It's an architectural distinction, not a feature count. A product can have fewer visible AI features and still be genuinely AI-native if intelligence is core to how it functions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skipping the evaluation and governance layer to move faster.&lt;/strong&gt; This is the corner that gets cut most often, and it's the one that causes the most expensive problems later, once the system is handling real production decisions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treating security as something to add after the AI features work.&lt;/strong&gt; By the time an AI-native system is live, retrofitting proper security around prompt handling and data access is far harder than designing it in from the start&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Underestimating the data infrastructure work.&lt;/strong&gt; Teams often plan for the model and the interface, but underestimate how much of the actual effort lives in the data pipelines that feed continuous, adaptive behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Quick Framework for Deciding What You Actually Need
&lt;/h2&gt;

&lt;p&gt;Before committing to a full AI-native rebuild, it's worth being honest about which category your product actually falls into.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is intelligence the core value proposition, or a helpful addition to a product that works fine without it?&lt;/li&gt;
&lt;li&gt;Does your system need to adapt continuously based on new data, or would periodic updates genuinely be sufficient?&lt;/li&gt;
&lt;li&gt;Do you have, or are you willing to build, the evaluation and governance infrastructure this approach requires?&lt;/li&gt;
&lt;li&gt;Is your team prepared for the security implications of treating the AI layer as a first-class system boundary?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the honest answer to the first two questions is "yes, genuinely," AI-native architecture is worth the upfront investment. If AI is a nice complement to an otherwise solid, deterministic product, a well-built AI-enhanced approach is often the more practical, lower-risk choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Is Genuinely Hard to Get Right Alone
&lt;/h2&gt;

&lt;p&gt;This isn't a criticism of any team, it's just an honest reflection of how new and fast-moving this discipline still is. The specific technical profile this requires, deep experience across data pipeline design, model evaluation, multi-model orchestration, and AI-specific security, remains genuinely scarce in most hiring markets right now. That's exactly why a lot of serious &lt;strong&gt;&lt;a href="https://zignuts.com/ai-services/custom-ai-development/ai-software-development-services?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;AI software development&lt;/a&gt;&lt;/strong&gt; work today involves custom AI development support and &lt;strong&gt;&lt;a href="https://zignuts.com/ai-services/ai-consulting/ai-readiness-assessment-services?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;AI readiness assessment&lt;/a&gt;&lt;/strong&gt; brought in from outside, specifically to architect the foundation correctly before a team commits to a direction that's expensive to unwind later. Getting an experienced second opinion on whether a product genuinely needs full AI-native architecture, or whether a well-built AI-enhanced approach would serve it just as well, is often the highest-leverage conversation to have before writing a single line of the core architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Future Outlook
&lt;/h2&gt;

&lt;p&gt;The gap between AI-enhanced and AI-native products is likely to keep widening over the next year or two, not narrow. As evaluation tooling matures and multi-model orchestration becomes more standardized, AI-native architecture is expected to shift from a differentiator into a baseline expectation, particularly for products where intelligence genuinely is the value proposition rather than a nice-to-have layered on top.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;Having AI features and being architected as an AI company are not the same thing, and that distinction is only going to matter more as the products competing for the same users diverge further along these two paths. The teams thinking carefully now about whether they genuinely need AI-native architecture, versus a solid, well-built AI-enhanced product, are the ones avoiding the expensive retrofit conversation a year or two from now.&lt;/p&gt;

&lt;p&gt;Is your product built AI-native from the ground up, or is it AI-enhanced with intelligence layered on top? Curious where most teams here actually land on that distinction once they think it through honestly.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>machinelearning</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Build Your First MCP Server: A Practical Guide to Connecting AI Agents to Real Tools</title>
      <dc:creator>Emma Schmidt</dc:creator>
      <pubDate>Thu, 30 Jul 2026 06:43:25 +0000</pubDate>
      <link>https://dev.to/emma_schmidt_/build-your-first-mcp-server-a-practical-guide-to-connecting-ai-agents-to-real-tools-2o43</link>
      <guid>https://dev.to/emma_schmidt_/build-your-first-mcp-server-a-practical-guide-to-connecting-ai-agents-to-real-tools-2o43</guid>
      <description>&lt;p&gt;Learn how to build a working MCP server from scratch and connect it to an AI client, step by step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If you've ever needed an &lt;strong&gt;&lt;a href="https://zignuts.com/llm-genai-services/ai-agent-development?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;AI agent&lt;/a&gt;&lt;/strong&gt; to check a database, call an internal API, or search a knowledge base, you've probably hit the same wall: every tool integration ends up as custom, one-off code that doesn't transfer to your next project. MCP server development and &lt;strong&gt;&lt;a href="https://zignuts.com/llm-genai-services/multi-agent-systems/mcp-integration-services?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;MCP integration &lt;/a&gt;&lt;/strong&gt;solve this by giving you one standardized way to expose any tool or data source to any compatible AI client, instead of writing bespoke glue code for every combination.&lt;/p&gt;

&lt;p&gt;This tutorial walks through building an actual working&lt;a href="https://zignuts.com/llm-genai-services/mcp-development/mcp-server-development-services?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt; &lt;strong&gt;MCP server&lt;/strong&gt;&lt;/a&gt;, connecting it to a client, and testing that it works end to end. By the end, you'll have a functioning server exposing a real tool, and you'll understand exactly what's happening at each layer instead of just copying a snippet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Comfortable reading and writing basic Python&lt;/li&gt;
&lt;li&gt;Python 3.10 or later installed&lt;/li&gt;
&lt;li&gt;A terminal and a code editor&lt;/li&gt;
&lt;li&gt;Roughly 30-45 minutes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;p&gt;We're building a small MCP server that exposes one practical tool: looking up the weather for a given city. It's simple enough to fully understand in one sitting, but it demonstrates every core piece of setting up MCP tool integration.&lt;/p&gt;

&lt;p&gt;The finished server will be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Advertise its available tools to any connecting MCP client&lt;/li&gt;
&lt;li&gt;Accept a tool call with a specific input (a city name)&lt;/li&gt;
&lt;li&gt;Return a structured response back to the calling AI agent&lt;/li&gt;
&lt;li&gt;Run locally so you can test it directly from your terminal&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Set Up Your Environment
&lt;/h2&gt;

&lt;p&gt;Create a project folder and install the MCP SDK.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;mcp-weather-server
&lt;span class="nb"&gt;cd &lt;/span&gt;mcp-weather-server
python &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv
&lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate  &lt;span class="c"&gt;# on Windows use venv\Scripts\activate&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;mcp requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you the core MCP library plus &lt;code&gt;requests&lt;/code&gt;, which we'll use to call a public weather API inside our tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Define the Server and Register Your Tool
&lt;/h2&gt;

&lt;p&gt;Create a file called &lt;code&gt;server.py&lt;/code&gt;. This is illustrative, simplified code meant for learning, not a full production implementation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# server.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.server&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.types&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TextContent&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weather-server&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.list_tools&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_tools&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get_weather&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Get the current weather for a given city&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;inputSchema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;properties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Name of the city&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;code&gt;list_tools&lt;/code&gt; function is how a client discovers what your server can do. When an AI agent connects, it asks "what tools do you have," and this is the answer it gets back, in a structured format it can reason about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Implement What the Tool Actually Does
&lt;/h2&gt;

&lt;p&gt;Now add the logic that runs when the tool is actually called.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# server.py (continued)
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="nd"&gt;@app.call_tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get_weather&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="c1"&gt;# Simplified example call, replace with a real weather API and key
&lt;/span&gt;        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example-weather-api.test/v1/current?city=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The weather in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;condition&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;unknown&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;temp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;?&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;°&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;TextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the execution layer. The AI model never talks to the weather API directly, it calls your tool through the protocol, and your code decides exactly what happens and what comes back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Run the Server Locally
&lt;/h2&gt;

&lt;p&gt;Add a simple entry point so the server can actually start.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# server.py (continued)
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.server.stdio&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;stdio_server&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;stdio_server&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nf"&gt;as &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;write_stream&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;write_stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_initialization_options&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python server.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, the server is running and waiting for a client to connect over standard input and output, which is the simplest transport for local development and testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Connect a Client and Test It
&lt;/h2&gt;

&lt;p&gt;To actually test this, you need an MCP-compatible client configured to launch your server. Most MCP client configurations use a simple JSON entry pointing to your script.&lt;br&gt;
&lt;/p&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;"mcpServers"&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;"weather"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"python"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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;"/full/path/to/server.py"&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;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;Once your client is configured and connected, ask it something like "what's the weather in Lisbon." If everything is wired correctly, the client will discover your &lt;code&gt;get_weather&lt;/code&gt; tool through the &lt;code&gt;list_tools&lt;/code&gt; call, invoke it with the city argument, and return the result from your &lt;code&gt;call_tool&lt;/code&gt; function back through the model's response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing / Verifying It Works
&lt;/h2&gt;

&lt;p&gt;A few checks to confirm your MCP server integration is actually functioning correctly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Confirm the client's tool list shows &lt;code&gt;get_weather&lt;/code&gt; as an available tool after connecting&lt;/li&gt;
&lt;li&gt;Manually call the tool with a test city and check that the response text looks correct&lt;/li&gt;
&lt;li&gt;Try an invalid or missing argument and confirm your server doesn't crash silently&lt;/li&gt;
&lt;li&gt;Restart the client and reconnect to make sure the server initializes cleanly every time, not just on the first run&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common Pitfalls and How to Avoid Them
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting to validate input.&lt;/strong&gt; A missing or malformed &lt;code&gt;city&lt;/code&gt; argument will crash a naive implementation. Add basic validation before making the external call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blocking calls inside async functions.&lt;/strong&gt; Using a purely synchronous HTTP library inside an async tool handler can freeze your server under load. Consider an async HTTP client for anything beyond a simple learning project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not handling API failures gracefully.&lt;/strong&gt; If the weather API is down, your tool should return a clear error message, not throw an unhandled exception that breaks the whole session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overloading one server with too many unrelated tools.&lt;/strong&gt; Keeping a server focused on one coherent set of capabilities makes it easier for both you and the connecting AI agent to reason about.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Best Practices for Production Use
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Add proper authentication and scoped permissions before exposing any server beyond local testing&lt;/li&gt;
&lt;li&gt;Log every tool call, including inputs and outcomes, so failures are debuggable after the fact&lt;/li&gt;
&lt;li&gt;Set reasonable timeouts on any external API calls inside your tools&lt;/li&gt;
&lt;li&gt;Validate all inputs against your declared schema before executing any logic&lt;/li&gt;
&lt;li&gt;Version your tool definitions so you can evolve them without silently breaking existing clients&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What to Explore Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Add a second, related tool to the same server, like a multi-day forecast, and see how tool discovery scales&lt;/li&gt;
&lt;li&gt;Swap the stdio transport for Streamable HTTP to run your server as a proper remote service&lt;/li&gt;
&lt;li&gt;Explore adding authentication middleware so only verified callers can invoke your tools&lt;/li&gt;
&lt;li&gt;Look into structured output schemas so responses can be validated programmatically instead of just returned as plain text&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building your first MCP server strips away a lot of the mystery around MCP integration once you've actually wired up discovery, execution, and a real client connection yourself. The pattern you just built scales directly to far more complex tools, databases, internal APIs, search systems, without changing the underlying shape of what you learned here. Once this clicks, adding your next tool is a lot faster than the first one was.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>There's a New Universal Plug for AI, and Most Developers Haven't Wired It In Yet</title>
      <dc:creator>Emma Schmidt</dc:creator>
      <pubDate>Wed, 29 Jul 2026 12:26:39 +0000</pubDate>
      <link>https://dev.to/emma_schmidt_/theres-a-new-universal-plug-for-ai-and-most-developers-havent-wired-it-in-yet-1fc8</link>
      <guid>https://dev.to/emma_schmidt_/theres-a-new-universal-plug-for-ai-and-most-developers-havent-wired-it-in-yet-1fc8</guid>
      <description>&lt;p&gt;Picture this. You've built an AI feature that needs to check a customer's order status, pull data from your CRM, and search internal documentation, three separate systems, three separate custom integrations, each with its own auth, its own data format, its own maintenance burden. Now imagine adding a fourth tool next quarter. And a fifth. This exact integration sprawl is precisely the problem the Model Context Protocol was built to solve, and it's become one of the fastest-adopted standards in recent memory across &lt;a href="https://zignuts.com/llm-genai-services/mcp-development/mcp-server-development-services?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;&lt;strong&gt;LLM &amp;amp; GenAI&lt;/strong&gt;&lt;/a&gt; development. Understanding how to actually build with it, not just read about it, is quickly becoming a core skill for anyone shipping AI features that need to touch real data and real tools.&lt;/p&gt;

&lt;p&gt;Here's what MCP actually is, why it's spreading this fast, and how to build your first working MCP server step by step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Protocol Is Spreading Faster Than Almost Anything in AI Tooling
&lt;/h2&gt;

&lt;p&gt;The adoption numbers here are genuinely unusual, even for an industry used to fast-moving trends.&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;Figure&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Monthly SDK downloads (March 2026)&lt;/td&gt;
&lt;td&gt;97 million, up from roughly 100,000 at launch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to reach 100M+ monthly downloads&lt;/td&gt;
&lt;td&gt;About 16 months&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Comparable milestone for React's npm package&lt;/td&gt;
&lt;td&gt;Roughly 3 years&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public MCP servers in the official registry&lt;/td&gt;
&lt;td&gt;Around 9,600 as of a May 2026 snapshot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fortune 500 companies with MCP deployed&lt;/td&gt;
&lt;td&gt;Roughly 28% within 18 months of launch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Major platforms with shipped MCP support&lt;/td&gt;
&lt;td&gt;OpenAI, Google, Microsoft, and others within about 13 months of launch&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last row is the one worth sitting with. A protocol originally released by Anthropic in November 2024 has since been adopted broadly enough across the industry that it now functions as shared infrastructure rather than one company's proprietary approach, which is exactly the kind of cross-platform momentum that turns a promising idea into a genuine standard.&lt;/p&gt;

&lt;h2&gt;
  
  
  What MCP Actually Solves
&lt;/h2&gt;

&lt;p&gt;Before MCP, connecting an AI application to external tools meant building a custom integration for every single combination of model and tool. Ten AI applications and a hundred tools meant a potential one thousand separate integrations, each one bespoke, each one a maintenance burden of its own.&lt;/p&gt;

&lt;p&gt;MCP replaces that with one standardized interface. Think of it the way USB-C replaced a drawer full of proprietary charging cables. Any MCP-compatible client can talk to any MCP-compatible server through the same protocol, regardless of which model or which tool is on either end.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Core Pieces You Need to Understand
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP Host.&lt;/strong&gt; The AI application itself, the thing the end user actually interacts with&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP Client.&lt;/strong&gt; Lives inside the host and manages the connection to one or more &lt;a href="https://zignuts.com/llm-genai-services/mcp-development/mcp-server-development-services?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;&lt;strong&gt;MCP servers&lt;/strong&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP Server.&lt;/strong&gt; Exposes specific tools, data sources, or capabilities in the standardized format the protocol expects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most developers building on top of MCP will spend their time on the server side, exposing their own systems and tools so any compatible AI application can use them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building Your First MCP Server, Step by Step
&lt;/h2&gt;

&lt;p&gt;Let's build a simple MCP server that exposes a single tool, checking an order status, using the official &lt;a href="https://zignuts.com/python-development-company?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;/a&gt; SDK.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step one: install the SDK&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step two: define the server and register a tool&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.server&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.types&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TextContent&lt;/span&gt;

&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order-status-server&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@server.list_tools&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_tools&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get_order_status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Look up the current status of a customer order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;inputSchema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;properties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the discovery step. Any MCP client connecting to this server can now ask what tools are available and get back a structured description it can reason about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step three: implement what the tool actually does&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@server.call_tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get_order_status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lookup_order_in_database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;TextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Order &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; status: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the actual execution layer. The AI model never touches your database directly, it calls the tool through the standardized protocol, and your server controls exactly what happens and what gets returned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step four: run the server with the appropriate transport&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.server.stdio&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;stdio_server&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;stdio_server&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nf"&gt;as &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;write_stream&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;write_stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_initialization_options&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Local development typically uses stdio transport. Production deployments increasingly use Streamable HTTP, which lets the server run as a proper remote service rather than a local process, and it's the transport most enterprise MCP rollouts are standardizing around this year.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step five: secure it before it goes anywhere near production&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.server.auth&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RequireAuth&lt;/span&gt;

&lt;span class="nd"&gt;@server.call_tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nd"&gt;@RequireAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scopes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;orders:read&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# verified caller identity is available in context
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every tool call should carry proper identity and scope verification. A recent industry analysis noted that in the MCP era, trust isn't established once at login, it's re-earned with every single tool call and data access an agent makes, which is a meaningfully different security posture than traditional API authentication.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Changing Right Now, as of This Week
&lt;/h2&gt;

&lt;p&gt;This is genuinely current, not stale reporting. The next major MCP specification, dated 2026-07-28, has its release candidate locked and is shipping as the final spec this week. It introduces a stateless protocol core, an Extensions framework for adding capabilities without breaking existing implementations, a formal deprecation policy, and hardened authorization. If you're starting a new MCP implementation right now, building against this version rather than the November 2025 spec is the right call, since SDK maintainers are expected to support it within a defined validation window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes Teams Are Making With Early MCP Adoption
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool overexposure.&lt;/strong&gt; Registering far more tools than a given workflow actually needs, which bloats the context an AI model has to reason over and increases the chance of it calling the wrong tool&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treating authentication as an afterthought.&lt;/strong&gt; Standing up a functional MCP server quickly and only addressing proper scoped auth after it's already been connected to something sensitive&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ignoring observability.&lt;/strong&gt; Deploying MCP servers into production workflows without tracking tool call success rates, latency, or error patterns, which makes debugging a failed agent workflow nearly impossible after the fact&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skipping the governance conversation.&lt;/strong&gt; Rolling out MCP servers across a team or organization without agreeing on security, compliance, and identity controls up front, then retrofitting governance after adoption has already sprawled&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Matters Beyond the Hype Cycle
&lt;/h2&gt;

&lt;p&gt;MCP exhibits genuine network effects. Every new MCP server makes every existing MCP client more capable, and every new client makes building an MCP server more worthwhile. That's a structurally different growth pattern than most developer tools, and it's part of why the ecosystem has grown as fast as it has.&lt;/p&gt;

&lt;p&gt;For teams evaluating whether to invest in this now, the practical answer is usually yes, but scoped carefully. Building custom MCP connectors and servers for your own internal tools and data sources, wired into &lt;strong&gt;&lt;a href="https://zignuts.com/blog/rag-vs-fine-tuning" rel="noopener noreferrer"&gt;RAG&lt;/a&gt;&lt;/strong&gt; development and existing LLM and GenAI workflows, is exactly the kind of foundational integration work that compounds in value as your AI features grow, rather than something that needs rebuilding from scratch every time a new tool gets added to the stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;The integration sprawl that used to make connecting AI to real business tools a slow, custom, one-off project each time is exactly what MCP was built to eliminate. With cross-vendor support, a maturing specification, and genuine network effects driving adoption, this has moved well past experimental territory into shared infrastructure that's shaping how serious AI applications get built this year.&lt;/p&gt;

&lt;p&gt;Has your team started building or adopting MCP servers yet, or are you still wiring up custom integrations one tool at a time? Curious how far along everyone actually is with this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; #ai #llm #genai #tutorial&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>tutorial</category>
      <category>genai</category>
    </item>
    <item>
      <title>Your AI Feature Isn't Slow. Your Architecture Is Making It Feel Slow.</title>
      <dc:creator>Emma Schmidt</dc:creator>
      <pubDate>Tue, 28 Jul 2026 10:30:54 +0000</pubDate>
      <link>https://dev.to/emma_schmidt_/your-ai-feature-isnt-slow-your-architecture-is-making-it-feel-slow-150p</link>
      <guid>https://dev.to/emma_schmidt_/your-ai-feature-isnt-slow-your-architecture-is-making-it-feel-slow-150p</guid>
      <description>&lt;p&gt;You've seen it a hundred times. A user types a question into an AI feature, hits submit, and then just stares at a blank screen for eight seconds before a wall of text dumps onto the page all at once. That gap between "I asked something" and "something happened" is exactly where users lose patience, and it's rarely the model's fault. It's almost always a streaming problem, not a speed problem. This is one of the core patterns behind modern &lt;a href="https://zignuts.com/ai-services?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;&lt;strong&gt;AI services&lt;/strong&gt;&lt;/a&gt; and &lt;a href="https://zignuts.com/ai-services/custom-ai-development/ai-web-application-development-services?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;&lt;strong&gt;AI web application development&lt;/strong&gt;&lt;/a&gt;, and getting it right in Next.js is simpler than most developers expect, especially now that the framework's App Router and Server Actions map almost perfectly onto how real-time AI responses actually need to flow.&lt;/p&gt;

&lt;p&gt;Here's how to build a genuinely responsive, streaming AI feature in &lt;strong&gt;&lt;a href="https://zignuts.com/hire-nextjs-developers?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;Next.js &lt;/a&gt;&lt;/strong&gt;from the ground up, understanding what's actually happening under the hood instead of just wiring up a library and hoping.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Streaming Changed What "Fast" Means for AI Features
&lt;/h2&gt;

&lt;p&gt;A non-streaming AI feature waits for the entire response to generate before showing anything. For short answers that's tolerable. For anything longer, a paragraph, a summary, a generated document, it means staring at a static loading spinner for several seconds with zero feedback that anything is actually happening.&lt;/p&gt;

&lt;p&gt;Streaming flips this. Tokens appear as the model generates them, the same way ChatGPT-style interfaces work. The perceived speed difference is enormous even when the total generation time is identical, because the user sees progress immediately instead of waiting in silence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real-World Difference This Makes
&lt;/h2&gt;

&lt;p&gt;Picture a support tool generating a written summary of a long customer thread. The generation itself takes about six seconds either way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without streaming:&lt;/strong&gt; the user submits, sees a static spinner, and waits the full six seconds staring at nothing. To someone who isn't sure the request even registered, six seconds of silence feels much longer than it actually is, and a meaningful number of users will refresh or click again out of doubt, sometimes triggering a duplicate request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With streaming:&lt;/strong&gt; the user sees the first words appear within a few hundred milliseconds, and text keeps flowing steadily until the summary is complete. The total time is identical, but it reads as fast because there's constant, visible progress instead of dead air.&lt;/p&gt;

&lt;p&gt;Same backend, same model, same six seconds of actual compute. The only difference is architecture, and it's the difference between a feature that feels broken and one that feels instant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing Your Transport Mechanism
&lt;/h2&gt;

&lt;p&gt;Before writing code, it's worth understanding the three real options for getting streaming data from server to client, since picking the wrong one for your use case creates real problems later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Server-Sent Events (SSE)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One-directional, server to client only&lt;/li&gt;
&lt;li&gt;Works over standard HTTP, no special infrastructure required&lt;/li&gt;
&lt;li&gt;The natural fit for AI text generation, since the client only needs to receive tokens, not send data mid-stream&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;WebSockets&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bidirectional, full duplex communication&lt;/li&gt;
&lt;li&gt;Better suited for genuinely interactive real-time features like collaborative editing or live multiplayer state&lt;/li&gt;
&lt;li&gt;Overkill for most AI chat features, and adds real infrastructure complexity you don't need for one-way token streaming&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Web Streams API (ReadableStream)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The lowest-level option, giving direct control over how chunks are read and processed&lt;/li&gt;
&lt;li&gt;What the AI SDK and most Route Handler implementations are actually built on under the hood&lt;/li&gt;
&lt;li&gt;Best when you need fine-grained control over parsing structured data mid-stream, like tool calls or JSON fragments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the vast majority of AI chat and generation features, SSE built on top of the Web Streams API is the right choice. Reach for WebSockets only when you genuinely need bidirectional communication during generation, which is rarer than most implementations assume.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-Step: Building a Streaming AI Route Handler
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step one: set up the project&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-next-app@latest ai-streaming-app
&lt;span class="nb"&gt;cd &lt;/span&gt;ai-streaming-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Select the App Router when prompted. This pattern depends on Route Handlers, which live specifically in the App Router architecture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step two: create the streaming Route Handler&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/api/chat/route.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ReadableStream&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;callModelProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/event-stream&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key detail here: the API key and the call to the model provider stay entirely server-side. Nothing sensitive ever ships to the client bundle, which matters both for security and for keeping your provider costs from being exposed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step three: consume the stream on the client&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/components/ChatInput.tsx&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ChatInput&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setResponse&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/chat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;getReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextDecoder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nf"&gt;setResponse&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;decoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the piece that actually creates the typing effect. Each chunk arrives and appends to state as it comes in, instead of waiting for one giant response.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step four: add proper error and cancellation handling&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AbortController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/chat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="c1"&gt;// stream reading logic here&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AbortError&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Generation cancelled by user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Something went wrong, please try again&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleCancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A stop button that actually cancels an in-flight generation is a small detail users notice immediately when it's missing, and it costs very little to implement properly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step five: validate structured output when you need more than plain text&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responseSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;responseSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of hoping a model returns well-formed JSON, defining a schema and validating against it catches malformed output before it ever reaches your UI, which matters a lot once an AI feature is doing more than just displaying conversational text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step six: add rate limiting before this ever reaches production&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/api/chat/route.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestCounts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getUserIdFromSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;requestCounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Rate limit exceeded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;requestCounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentCount&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// proceed with streaming logic&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a simplified example, a real implementation would use a proper store like Redis rather than an in-memory map, but the principle holds regardless of scale. Without rate limiting, a single user or a scripted abuse pattern can run your model provider costs up fast, and this is one of the most commonly skipped steps in early AI feature builds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step seven: track cost per feature, not just in aggregate&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;logUsage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tokenCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tokenCount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;getCostPerToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ai_usage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tokenCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breaking cost down by feature rather than watching one aggregate monthly number lets you catch a specific feature quietly ballooning in usage long before it shows up as a surprise on the bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes Worth Avoiding
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Forgetting to handle stream errors mid-response, leaving the UI stuck in a permanent loading state if the connection drops partway through&lt;/li&gt;
&lt;li&gt;Skipping the cancellation handling entirely, which leaves users stuck watching a response they no longer want finish generating&lt;/li&gt;
&lt;li&gt;Calling the model provider directly from client-side code, which exposes API keys and lets costs run up uncontrolled&lt;/li&gt;
&lt;li&gt;Not validating structured output, and discovering malformed data only when it breaks something downstream in production&lt;/li&gt;
&lt;li&gt;Shipping without rate limiting and finding out the hard way when a single user or bot script drives up provider costs overnight&lt;/li&gt;
&lt;li&gt;Choosing WebSockets by default out of habit when SSE would be simpler, cheaper to run, and perfectly sufficient for one-way token streaming&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Quick Self-Check Before You Ship
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Does your UI show visible progress within the first second, or does it sit on a static spinner while waiting for the full response?&lt;/li&gt;
&lt;li&gt;Can a user actually cancel a generation mid-stream, or does clicking away just leave the request running in the background?&lt;/li&gt;
&lt;li&gt;Is there a rate limit in place, or is the endpoint currently wide open to unlimited requests per user?&lt;/li&gt;
&lt;li&gt;Are you tracking cost per feature, or would a sudden spike in usage only show up once the monthly bill arrives?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Is Worth Building Properly, Not Just Quickly
&lt;/h2&gt;

&lt;p&gt;Getting a demo chat working in an afternoon is genuinely easy now. Getting it production-ready, proper error handling, rate limiting, cost monitoring, structured output validation, and a UI that gracefully degrades when something goes wrong, is where most teams underestimate the actual scope of the work. Custom AI development and API integration work focused specifically on shipping these features at production scale tends to close that gap far faster than assembling it piece by piece under a deadline, especially for teams integrating multiple providers or building on top of an existing enterprise codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;The gap between an AI feature that feels instant and one that feels sluggish usually isn't the model's raw speed, it's whether the response streams token by token or dumps all at once after a long, silent wait. Next.js's architecture, particularly Server Actions and Route Handlers, maps naturally onto this pattern, which is exactly why it's become the default framework for AI-powered web applications this year.&lt;/p&gt;

&lt;p&gt;Has your team already shipped a streaming AI feature in production, or are you still working through the plumbing on this one? Curious what's tripped people up most.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>ai</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Your ML Model Doesn't Need to Be Bigger. It Needs to Be Smaller and Smarter.</title>
      <dc:creator>Emma Schmidt</dc:creator>
      <pubDate>Mon, 27 Jul 2026 06:37:54 +0000</pubDate>
      <link>https://dev.to/emma_schmidt_/your-ml-model-doesnt-need-to-be-bigger-it-needs-to-be-smaller-and-smarter-3gao</link>
      <guid>https://dev.to/emma_schmidt_/your-ml-model-doesnt-need-to-be-bigger-it-needs-to-be-smaller-and-smarter-3gao</guid>
      <description>&lt;p&gt;For the last few years, the assumption in machine learning was simple: bigger models mean better results, so scale up and worry about the bill later. That assumption is quietly breaking down in 2026. The real shift happening across serious ML teams right now isn't about building bigger models, it's about deployment efficiency, running smaller, specialized models that cost a fraction as much to run while matching or beating general-purpose performance on the specific task they're actually built for. This is exactly where a lot of &lt;strong&gt;&lt;a href="https://zignuts.com/ml-services" rel="noopener noreferrer"&gt;ML services&lt;/a&gt;&lt;/strong&gt; work is heading this year, helping teams figure out which tasks genuinely need a massive general model and which ones a smaller, purpose-tuned one handles just as well for a fraction of the cost.&lt;/p&gt;

&lt;p&gt;This shift has a name, small language models, and understanding how to actually build and deploy one is quickly becoming a core ML skill rather than a niche specialty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Bigger Stopped Automatically Meaning Better
&lt;/h2&gt;

&lt;p&gt;For a specific, well-defined task, a massive general-purpose model is often carrying enormous amounts of capability you never actually use. Ask a giant model to classify customer support tickets into five categories, and it's spending compute capacity it could use to write poetry or debug &lt;a href="https://zignuts.com/hire-python-developers?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;/a&gt;, none of which the task needs.&lt;/p&gt;

&lt;p&gt;A few forces are pushing the industry toward smaller, specialized models instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Inference cost adds up fast at real scale.&lt;/strong&gt; A model that's cheap to query once becomes expensive very quickly across millions of daily requests, and a smaller model tuned for the specific task can cut that cost dramatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency matters more as AI moves into real-time features.&lt;/strong&gt; A smaller model responds faster, which matters enormously for anything user-facing where a slow response actively hurts the experience&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On-device and edge deployment is only realistic with smaller models.&lt;/strong&gt; A model that needs to run on a phone, a browser, or an embedded device simply can't be enormous&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Specialized accuracy often beats general capability.&lt;/strong&gt; A model trained specifically on your domain's data and terminology frequently outperforms a general model on that exact task, even at a fraction of the parameter count&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Two Core Techniques Worth Actually Understanding
&lt;/h2&gt;

&lt;p&gt;Two techniques make this shift practical, and both are worth understanding at a working level even if you're not building the tooling yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quantization&lt;/strong&gt; reduces the precision of a model's internal numbers, typically from 32-bit floating point down to 8-bit or even 4-bit representations. This shrinks the model's memory footprint and speeds up inference, often with only a small, carefully managed loss in accuracy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distillation&lt;/strong&gt; trains a smaller "student" model to mimic the behavior of a larger "teacher" model. Instead of learning from raw data alone, the student learns from the teacher's outputs, effectively compressing what the larger model learned into a much smaller package.&lt;/p&gt;

&lt;p&gt;Together, these two techniques are why a well-tuned small model can now handle tasks that would have required a massive general model just a couple of years ago.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Walkthrough: Quantizing a Model for Deployment
&lt;/h2&gt;

&lt;p&gt;Let's walk through a basic quantization workflow using a common open-source library, the kind of step that turns a model too large for practical deployment into one that runs efficiently in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step one: load your baseline model&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AutoModelForSequenceClassification&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AutoTokenizer&lt;/span&gt;

&lt;span class="n"&gt;model_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-base-model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoModelForSequenceClassification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tokenizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoTokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step two: apply post-training quantization&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;

&lt;span class="n"&gt;quantized_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quantization&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;quantize_dynamic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;qint8&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This converts the model's linear layers to 8-bit integer precision, typically shrinking the model size significantly and speeding up inference on CPU, often with minimal accuracy loss for well-suited tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step three: benchmark before trusting the result&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;runs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;runs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;no_grad&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;runs&lt;/span&gt;

&lt;span class="n"&gt;original_latency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sample_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;quantized_latency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quantized_model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sample_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Original: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;original_latency&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s, Quantized: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;quantized_latency&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Never skip this step. The whole point of quantization is a measurable efficiency gain, and you need real numbers on your actual hardware and actual data, not an assumption borrowed from a blog post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step four: validate accuracy didn't quietly degrade&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;evaluate_accuracy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test_dataset&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;correct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;test_dataset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;no_grad&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;prediction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;logits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;argmax&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;correct&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prediction&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;correct&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_dataset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Original accuracy: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;evaluate_accuracy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test_set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Quantized accuracy: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;evaluate_accuracy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quantized_model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test_set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A quantized model that's faster but meaningfully less accurate isn't actually a win. This comparison step is what turns quantization from a guess into a real, measured engineering decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Small Language Models Are Already Winning
&lt;/h2&gt;

&lt;p&gt;This isn't theoretical. Real deployments are already showing the pattern clearly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Customer support ticket routing&lt;/strong&gt; using a small, domain-tuned classifier instead of a general-purpose model call for every incoming ticket, cutting both latency and per-request cost significantly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On-device features in mobile apps&lt;/strong&gt;, where a compact model handles tasks like text summarization or intent detection locally, without a round trip to a server at all&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manufacturing and IoT anomaly detection&lt;/strong&gt;, where small, efficient models run directly on edge hardware near the sensors generating the data, since sending everything to the cloud for a giant model to process isn't fast enough for real-time decisions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-volume, narrow classification tasks&lt;/strong&gt; like spam detection or content moderation, where a small specialized model handles the bulk of routine cases and only escalates genuinely ambiguous ones to a larger model&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Quick Framework for Deciding When to Go Small
&lt;/h2&gt;

&lt;p&gt;Before defaulting to the largest available model, ask these questions honestly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this task narrow and well-defined, or does it genuinely require broad, general reasoning across many domains?&lt;/li&gt;
&lt;li&gt;Do we have enough quality training or distillation data specific to this task to teach a smaller model well?&lt;/li&gt;
&lt;li&gt;Does latency or on-device deployment actually matter for this specific feature?&lt;/li&gt;
&lt;li&gt;What's the real cost difference at our expected production volume, not just at demo scale?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a task is narrow, well-defined, and running at real volume, a small, specialized model is very often the better engineering choice, not a compromise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes Worth Avoiding
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Quantizing a model and shipping it without actually benchmarking the accuracy tradeoff on real evaluation data&lt;/li&gt;
&lt;li&gt;Assuming distillation is a one-time step rather than an ongoing process that needs revisiting as the underlying task or data shifts over time&lt;/li&gt;
&lt;li&gt;Defaulting to the largest general model out of convenience for tasks that a much smaller, cheaper model would handle just as well&lt;/li&gt;
&lt;li&gt;Skipping proper monitoring after deployment, since a smaller model can drift or degrade in production just like any other model, and needs the same kind of ongoing evaluation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Is Worth Doing Properly, Not Just Quickly
&lt;/h2&gt;

&lt;p&gt;Getting the quantization, distillation, and evaluation pipeline right takes real ML engineering discipline, not just running a library function once and hoping for the best. For teams without a dedicated &lt;a href="https://zignuts.com/ml-services/mlops?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;&lt;strong&gt;MLOps&lt;/strong&gt;&lt;/a&gt; function already in place, building this properly alongside shipping product features is a genuinely heavy lift. Custom model development, deployment pipeline setup, and ongoing MLOps support built around this exact efficiency-first approach tend to get a team to a well-benchmarked, production-ready small model far faster and more reliably than assembling it piecemeal under deadline pressure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;The biggest available model was never automatically the right choice, it just looked that way while nobody was measuring the actual cost and latency tradeoffs closely. As ML moves deeper into real-time, on-device, and high-volume production use cases, the teams pulling ahead are the ones treating model size as a deliberate engineering decision, matched carefully to the task, rather than a default setting left on maximum.&lt;/p&gt;

&lt;p&gt;Is your team still defaulting to the largest available model for every task, or have you already started matching model size to the actual job? Curious how far along different teams are with this shift.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>ai</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Your Chatbot Answered Perfectly Yesterday. Today It's Confidently Making Things Up. Nobody Noticed.</title>
      <dc:creator>Emma Schmidt</dc:creator>
      <pubDate>Fri, 24 Jul 2026 06:54:44 +0000</pubDate>
      <link>https://dev.to/emma_schmidt_/your-chatbot-answered-perfectly-yesterday-today-its-confidently-making-things-up-nobody-noticed-2o43</link>
      <guid>https://dev.to/emma_schmidt_/your-chatbot-answered-perfectly-yesterday-today-its-confidently-making-things-up-nobody-noticed-2o43</guid>
      <description>&lt;p&gt;Your uptime dashboard is green. Response times look normal. Error rates are near zero. And yet, somewhere in production right now, your AI feature might be giving out subtly wrong answers, hallucinated policy details, a drifted tone, an answer that's technically valid but wrong for your actual use case, and none of your existing monitoring would ever catch it. This is exactly the blind spot pushing &lt;strong&gt;&lt;a href="https://zignuts.com/ai-services?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;AI services&lt;/a&gt;&lt;/strong&gt; from a bolted-on feature toward something that needs the same rigor as any other production infrastructure, with dedicated observability, evaluation, and monitoring built in from the start rather than added after something goes wrong.&lt;/p&gt;

&lt;p&gt;This gap has a name now: &lt;strong&gt;&lt;a href="https://zignuts.com/llm-genai-services?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;LLM &lt;/a&gt;&lt;/strong&gt;observability, and it's becoming one of the fastest-growing corners of AI infrastructure this year for a very simple reason. Traditional monitoring was built for deterministic software. &lt;strong&gt;&lt;a href="https://zignuts.com/ai-models?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;AI models&lt;/a&gt;&lt;/strong&gt; aren't deterministic, and the old playbook doesn't catch the failures that actually matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Your Existing Monitoring Can't See This
&lt;/h2&gt;

&lt;p&gt;Traditional application monitoring answers one question well: did the request succeed, and how fast. That's genuinely useful, and it's also completely blind to the specific way AI systems fail.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A response can be fast, formatted correctly, and still be wrong.&lt;/strong&gt; A hallucinated fact, a policy detail that's outdated, or an answer that sounds confident but references the wrong context all return a perfectly healthy 200 status code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The same prompt can produce different quality on different days.&lt;/strong&gt; A model that answered a question correctly last week can drift into a subtly wrong pattern this week with no code change on your end at all&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-step AI workflows hide their failures in the middle.&lt;/strong&gt; When a system retrieves documents, reasons over them, and calls tools before producing a final answer, a failure in any middle step can still produce an output that looks fine on the surface&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost and token usage can balloon silently.&lt;/strong&gt; A workflow that used to cost a few cents per request can quietly triple in cost as prompts grow or retrieval pulls in more context than intended, with nothing in a standard dashboard flagging it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these show up as an outage. They show up as a slow, invisible erosion of quality that nobody notices until a customer complains, or worse, until nobody notices at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Four Things Real AI Observability Actually Tracks
&lt;/h2&gt;

&lt;p&gt;Getting real visibility into an AI system means watching more than the usual infrastructure signals.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;System health.&lt;/strong&gt; The traditional layer, latency, error rates, uptime, still matters and still needs watching&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model performance and quality.&lt;/strong&gt; Accuracy against known good answers, hallucination rates, and semantic drift over time, not just whether a response was returned&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost tracking.&lt;/strong&gt; Token usage and per-request cost broken down by feature, so a quiet cost creep gets caught before it shows up as a surprise on the monthly bill&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Behavioral tracing.&lt;/strong&gt; Full visibility into multi-step workflows, what was retrieved, what reasoning path was taken, what tools were called, so a wrong answer can actually be debugged instead of shrugged at&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Practical Way to Start Building This
&lt;/h2&gt;

&lt;p&gt;You don't need a massive platform overhaul to start closing this gap. Here's a reasonable starting sequence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step one: log the full trace, not just the final output&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;log_llm_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retrieved_docs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;trace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retrieved_context&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;retrieved_docs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;token_count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latency_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latency&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model_version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;trace_store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without the full trace, a bad output down the line is nearly impossible to debug. Was it a bad retrieval, a bad prompt, or a bad model response? You can't tell from the final answer alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step two: run automated quality scoring on a sample of live traffic&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;score_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retrieved_docs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;faithfulness&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;check_grounded_in_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retrieved_docs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;relevance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;check_relevance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hallucination_risk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;detect_unsupported_claims&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retrieved_docs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scoring every single production response can be expensive, but sampling a meaningful percentage catches drift long before it becomes a visible pattern to users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step three: set alerts on quality signals, not just infrastructure signals&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;quality_scores&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;faithfulness&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;alert_team&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Faithfulness score dropped: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;quality_scores&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A latency alert tells you something broke. A faithfulness or drift alert tells you something is quietly getting worse, which is the failure mode that actually damages user trust over time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step four: track cost per feature, not just cost in aggregate&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cost_by_feature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cost_by_feature&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;feature_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;token_count&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cost_per_token&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A single feature quietly ballooning in token usage is easy to miss in an aggregate monthly bill and very easy to catch when broken down by feature and tracked over time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step five: close the loop by feeding failures back into evaluation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every flagged failure should become part of an ongoing evaluation dataset, not a one-off fire drill. This is what actually improves the system over time instead of just reacting to the same category of failure repeatedly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Numbers Worth Knowing
&lt;/h2&gt;

&lt;p&gt;The scale of this gap is bigger than most teams assume. A large share of enterprises now require AI monitoring in production, yet a majority cite a lack of adequate observability tooling as a major barrier to actually having it. That gap between what teams know they need and what they've actually built is exactly where the invisible failures described above tend to live.&lt;/p&gt;

&lt;p&gt;Evaluation overhead itself is a real cost too. Adding a full evaluation pass to a live request can meaningfully increase response latency, which is part of why the emerging best practice for 2026 is combining lightweight in-line scoring on every request with deeper evaluation sampling run asynchronously, rather than trying to fully evaluate every single response in the live path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes Teams Make Getting Started
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Treating LLM observability as identical to traditional APM and only tracking latency and error rates, missing quality entirely&lt;/li&gt;
&lt;li&gt;Scoring 100 percent of production traffic with an expensive evaluation model, which quietly doubles response latency and cost&lt;/li&gt;
&lt;li&gt;Building alerts only for hard failures and missing the slow, gradual drift that's actually the more common failure pattern&lt;/li&gt;
&lt;li&gt;Never closing the loop between a caught failure and an updated evaluation dataset, so the same category of mistake keeps recurring&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Is Worth Building Properly
&lt;/h2&gt;

&lt;p&gt;Getting this right touches more than one layer, evaluation design, tracing infrastructure, alerting thresholds, and cost monitoring all need to work together rather than existing as disconnected tools. For teams without dedicated MLOps or AI infrastructure specialists on staff already, building this from scratch while also shipping product features is a genuinely heavy lift. &lt;strong&gt;&lt;a href="https://zignuts.com/ai-services/ai-integration?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;Custom AI integration&lt;/a&gt;&lt;/strong&gt; and observability setup, done by people who've built this exact monitoring layer before, tends to get a team from flying blind to genuinely production-grade visibility far faster than assembling it piecemeal under deadline pressure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;A green dashboard doesn't mean your AI feature is actually working well, it means the parts of it that traditional monitoring knows how to measure are working. The parts that actually matter to users, whether the answer was true, relevant, and useful, live in a layer most teams still aren't watching. That gap is exactly what LLM observability exists to close, and closing it early is a lot cheaper than discovering the failure the way most teams still do, from a customer complaint.&lt;/p&gt;

&lt;p&gt;Is your team actually tracking AI output quality in production, or is your monitoring stack still only watching the infrastructure layer? Curious how many of us have actually closed this gap yet.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>devops</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Your App Is Fast Enough. It's Also Quietly Burning More Energy Than It Needs To.</title>
      <dc:creator>Emma Schmidt</dc:creator>
      <pubDate>Thu, 23 Jul 2026 12:12:35 +0000</pubDate>
      <link>https://dev.to/emma_schmidt_/your-app-is-fast-enough-its-also-quietly-burning-more-energy-than-it-needs-to-1ehp</link>
      <guid>https://dev.to/emma_schmidt_/your-app-is-fast-enough-its-also-quietly-burning-more-energy-than-it-needs-to-1ehp</guid>
      <description>&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; #sustainability #cloudcomputing #webdev #discuss&lt;br&gt;
Somewhere in your cloud bill this month, there's a number nobody on your team has ever really looked at: how much energy your application actually consumes to do its job. Not cost, energy. Every unnecessary database query, every bloated Docker image, every inefficient loop running at scale is drawing real power from a real data center, and most engineering teams have never once measured it. This gets even more pressing once &lt;strong&gt;&lt;a href="https://zignuts.com/ai-services?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;AI services&lt;/a&gt;&lt;/strong&gt; enter the picture, since AI inference is one of the most energy-hungry workloads a modern application can run, and it's exactly where a lot of this invisible waste tends to hide first.&lt;/p&gt;

&lt;p&gt;That's starting to change fast, and not because of idealism. Green coding, writing and architecting software specifically to minimize energy consumption, has moved from a nice-to-have sustainability talking point to a genuine engineering discipline businesses are being pushed toward from multiple directions at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Is Suddenly Everyone's Problem
&lt;/h2&gt;

&lt;p&gt;A few forces are converging here, and none of them are going away.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cloud costs and energy consumption are now directly linked&lt;/strong&gt; in a way they weren't a few years ago, as providers increasingly price compute in ways that reflect actual power draw, not just raw usage time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regulatory pressure is rising&lt;/strong&gt; in multiple regions requiring companies to report on and reduce their digital carbon footprint, and software energy use is a growing line item in those reports&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI workloads specifically have made this urgent.&lt;/strong&gt; Training and running &lt;strong&gt;&lt;a href="https://zignuts.com/ai-models?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;AI models&lt;/a&gt;&lt;/strong&gt; is extraordinarily energy intensive, and companies bolting AI features onto every product are watching their infrastructure energy costs climb in ways that are hard to ignore&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customers and investors are starting to ask.&lt;/strong&gt; Sustainability reporting increasingly includes technology stack efficiency, not just the usual categories like travel and office energy use&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data center capacity itself is becoming a real constraint&lt;/strong&gt;, with some regions seeing power grid strain directly tied to the growth of compute-heavy workloads, which puts pressure back on the software layer to use less of it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this requires anyone to care personally about the planet to take it seriously. It's becoming a real line item, a real compliance risk, and a real cost center.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Inefficient Code Actually Looks Like at Scale
&lt;/h2&gt;

&lt;p&gt;Here's the part that surprises people. None of this requires exotic new practices. It's mostly ordinary engineering habits that were never a priority because energy cost was invisible.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;N+1 queries running millions of times a day&lt;/strong&gt; across a large user base, each one drawing real compute and real power for work that could've been a single efficient join&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Polling instead of event-driven updates&lt;/strong&gt;, where a client checks for new data every few seconds indefinitely, burning compute around the clock for data that changes rarely&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Oversized container images&lt;/strong&gt; that take longer to build, deploy, and run than they need to, multiplied across every deployment and every autoscaling event&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unoptimized AI inference calls&lt;/strong&gt;, sending far more tokens or running a larger model than the task actually requires, when a smaller, purpose-built model would do the job with a fraction of the energy draw&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idle infrastructure left running&lt;/strong&gt; in staging and development environments around the clock, consuming power for workloads nobody's actually using outside business hours&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unbounded background jobs&lt;/strong&gt; that retry endlessly or run on overly aggressive schedules, quietly consuming compute for work that could run a fraction as often&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Individually, each of these looks like a minor inefficiency. At the scale most production systems run at, they add up to a genuinely significant amount of wasted energy and wasted money, quietly, every single day.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Real-World Example of How This Adds Up
&lt;/h2&gt;

&lt;p&gt;Take a mid-sized ecommerce platform serving a few million requests a day. Picture a single product page that fires an N+1 query pattern loading reviews, one that polls every three seconds for inventory updates that actually change a few times an hour, and a recommendation feature calling a large general-purpose AI model when a lightweight, purpose-built model would return the same quality of result.&lt;/p&gt;

&lt;p&gt;None of those three issues, on their own, would ever show up as an incident. No outage, no page, no angry customer email. But multiplied across millions of page loads a day, that's a meaningful chunk of unnecessary compute running continuously, month after month, for work that could've been a fraction of the size with no visible change to the user experience at all. That's the actual shape of this problem in most real production systems: not one dramatic failure, but a thousand small, invisible ones running constantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Starting Point
&lt;/h2&gt;

&lt;p&gt;You don't need a sustainability team to start improving this. A few concrete places to look first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit your database query patterns&lt;/strong&gt;&lt;br&gt;
Look specifically for N+1 patterns and queries running far more often than the data actually changes. This is usually the single highest-impact place to start, because database load scales directly with both cost and energy draw.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Replace polling with event-driven architecture where you can&lt;/strong&gt;&lt;br&gt;
A webhook or a push notification that fires only when something actually changes uses a fraction of the compute of a client checking in every few seconds forever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Right-size your AI model usage&lt;/strong&gt;&lt;br&gt;
Not every task needs the largest, most capable model available. Matching model size to task complexity is one of the fastest-growing cost and energy savings levers teams are finding this year, precisely because so many teams default to the biggest model out of convenience rather than necessity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shut down idle environments automatically&lt;/strong&gt;&lt;br&gt;
Staging and dev environments running 24/7 for workloads only used during business hours are pure waste. Automated shutdown schedules are a low-effort, high-impact fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Batch and schedule background work intelligently&lt;/strong&gt;&lt;br&gt;
Jobs that run every minute out of habit rather than necessity are an easy, low-risk place to cut unnecessary compute cycles without touching anything user-facing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measure before you optimize&lt;/strong&gt;&lt;br&gt;
Most teams have never actually measured their application's energy footprint because the tooling to do it wasn't mainstream until recently. You can't meaningfully improve what you've never measured.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Quick Self-Check
&lt;/h2&gt;

&lt;p&gt;Before deciding this doesn't apply to your team, answer these honestly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do you know which endpoints in your application generate the most database load relative to how often that data actually changes?&lt;/li&gt;
&lt;li&gt;Are any of your staging or dev environments running continuously, with nobody checking whether they need to be?&lt;/li&gt;
&lt;li&gt;Is your team defaulting to the largest available AI model for every task, regardless of how simple the task actually is?&lt;/li&gt;
&lt;li&gt;Has anyone ever pulled a report on your cloud provider's energy or carbon metrics, or does that dashboard exist unopened?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If more than one of these gave you pause, there's likely real, low-risk savings sitting in your stack right now, unmeasured and unaddressed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Questions Teams Ask When Starting This
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does this require rewriting our whole application?&lt;/strong&gt;&lt;br&gt;
Almost never. Most of the highest-impact fixes, like resolving N+1 queries or shutting down idle environments, are targeted, low-risk changes rather than architectural rewrites.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will this slow down feature development?&lt;/strong&gt;&lt;br&gt;
An initial audit takes focused time, but the fixes that come out of it are usually incremental and can be folded into normal sprint work rather than requiring a dedicated multi-month initiative.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this only relevant for very large-scale applications?&lt;/strong&gt;&lt;br&gt;
The savings scale with traffic, but the underlying inefficiencies exist at any scale. Catching them early is actually easier and cheaper than untangling them after years of accumulated scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters Beyond the Obvious
&lt;/h2&gt;

&lt;p&gt;There's a business case here independent of any environmental motivation. Energy-efficient code tends to correlate strongly with genuinely well-architected code. The same discipline that reduces unnecessary compute cycles also reduces latency, reduces cloud spend, and reduces the blast radius of scaling problems under real traffic. Teams chasing energy efficiency usually end up with a faster, cheaper, more resilient system as a side effect, not a tradeoff.&lt;/p&gt;

&lt;p&gt;This is also exactly the kind of work that benefits from a proper audit rather than scattered fixes. Untangling inefficient query patterns, right-sizing infrastructure, and rearchitecting around event-driven patterns across an existing production system touches database design, cloud architecture, and AI services integration all at once, which is a lot to take on internally while also shipping new features on the usual roadmap. A dedicated cloud architecture and performance audit, done by people who've mapped this exact kind of waste before, tends to surface savings that are easy to miss when you're deep inside your own codebase every day.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;The energy your code consumes was always real, it just wasn't visible on a dashboard anyone was watching. As regulation tightens, AI workloads grow, and cloud providers price compute closer to its real power draw, that invisibility is disappearing fast. The teams getting ahead of this now aren't doing it out of idealism, they're doing it because efficient code was always going to be cheaper, faster, and more resilient too, the environmental benefit just happens to come along for free.&lt;/p&gt;

&lt;p&gt;Has your team ever actually measured your application's energy footprint, or is it still an invisible number buried somewhere in a cloud bill nobody's dug into? Curious how many of us are actually tracking this yet.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Prompt Engineering Is Dead. Context Engineering Is What Actually Fixes Your AI's Wrong Answers.</title>
      <dc:creator>Emma Schmidt</dc:creator>
      <pubDate>Wed, 22 Jul 2026 10:54:05 +0000</pubDate>
      <link>https://dev.to/emma_schmidt_/prompt-engineering-is-dead-context-engineering-is-what-actually-fixes-your-ais-wrong-answers-2ejg</link>
      <guid>https://dev.to/emma_schmidt_/prompt-engineering-is-dead-context-engineering-is-what-actually-fixes-your-ais-wrong-answers-2ejg</guid>
      <description>&lt;p&gt;You've tuned the prompt a dozen times. Added examples, adjusted the tone, tried three different phrasings of the same instruction. The AI still hallucinates a fact, misses context from earlier in the conversation, or confidently answers a question using the wrong data. If this sounds familiar, the problem was never the prompt. It's what's called context engineering, and it's quietly become one of the most important skills in &lt;strong&gt;&lt;a href="https://zignuts.com/ai-services?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;AI development&lt;/a&gt;&lt;/strong&gt; this year. Teams that get this right often lean on structured AI development and integration services specifically because getting the architecture right the first time avoids months of trial-and-error prompt tweaking that never actually fixes the root cause.&lt;/p&gt;

&lt;p&gt;Here's what context engineering actually is, why it matters more than clever prompting, and how to build it into a real application step by step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Prompt Engineering Alone Stops Working
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://zignuts.com/hire-prompt-engineer?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;Prompt engineering&lt;/a&gt;&lt;/strong&gt; treats the model like a black box you talk to more cleverly. Context engineering treats the entire system around the model, what data it sees, in what order, and how it's structured, as the thing you actually design.&lt;/p&gt;

&lt;p&gt;Here's the distinction that matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prompt engineering&lt;/strong&gt; optimizes the instruction you give the model in a single message&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context engineering&lt;/strong&gt; optimizes everything the model has access to when it generates that response: retrieved documents, conversation history, system instructions, tool outputs, and how all of that is assembled and prioritized&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A perfectly worded prompt pointed at the wrong or poorly organized context still produces a wrong answer. This is why teams that only tune prompts eventually hit a wall, no phrasing fixes a model that's missing the right information entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Problem You're Actually Solving
&lt;/h2&gt;

&lt;p&gt;Every AI application built on top of a language model faces the same limitation: a fixed context window and no persistent memory between calls. Context engineering is the discipline of deciding what goes into that limited space and how it's organized so the model can actually use it well.&lt;/p&gt;

&lt;p&gt;Get this wrong and you'll see familiar symptoms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The model confidently answers using outdated or irrelevant retrieved data&lt;/li&gt;
&lt;li&gt;Earlier parts of a long conversation get silently dropped or ignored&lt;/li&gt;
&lt;li&gt;Retrieved documents get stuffed in with no clear structure, and the model can't tell what's actually relevant&lt;/li&gt;
&lt;li&gt;Adding more context makes answers worse, not better, because signal gets buried in noise&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Building a Real Context Pipeline, Step by Step
&lt;/h2&gt;

&lt;p&gt;Let's walk through building this properly, using a support chatbot pulling from a knowledge base as the example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step one: separate your context into clear categories&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system_instructions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are a support assistant for Acme SaaS...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retrieved_docs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;       &lt;span class="c1"&gt;# from your knowledge base
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;conversation_history&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="c1"&gt;# prior turns in this session
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_profile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;         &lt;span class="c1"&gt;# account tier, past tickets, etc
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Treating these as separate, labeled components instead of one giant blob of text is the single biggest shift from prompt engineering to context engineering. It lets you control, debug, and prioritize each piece independently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step two: retrieve only what's actually relevant&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;retrieve_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;query_embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vector_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the relevance threshold. Pulling in five loosely related documents to "give the model more to work with" usually makes answers worse, not better. Fewer, highly relevant documents consistently outperform a large pile of mediocre ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step three: manage conversation history deliberately, not by just appending everything&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_history&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_turns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;max_turns&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;max_turns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;summarize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;max_turns&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Earlier context: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Long conversations without this kind of management silently push early, important context out of the window entirely. Summarizing older turns instead of dropping them keeps continuity without blowing the token budget.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step four: assemble everything with clear structure, not a wall of text&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
System: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;system_instructions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

Relevant documentation:
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;format_docs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;retrieved_docs&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

Conversation so far:
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;format_history&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;conversation_history&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

User profile: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user_profile&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

Current question: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;current_query&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clear section labels matter more than they seem like they should. Models handle structured, clearly delineated context noticeably better than the same information dumped in as one undifferentiated paragraph.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step five: test with adversarial and edge-case queries, not just the happy path&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;test_cases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;question referencing something from 10 turns ago&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;question where retrieved docs are slightly outdated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;question with no relevant docs in the knowledge base at all&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the step most teams skip. A context pipeline that works great on clean, simple test queries often falls apart the moment a real user asks something messy, which is exactly when it matters most.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes Worth Knowing Before You Build This
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Context window stuffing.&lt;/strong&gt; Cramming in every possibly-relevant document instead of the few genuinely relevant ones, which drowns out the signal&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No relevance filtering on retrieval.&lt;/strong&gt; Returning the top results regardless of how weak the match actually is&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flat, unstructured context.&lt;/strong&gt; Pasting everything into one block of text with no labeled sections for the model to anchor on&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ignoring token budget until it breaks.&lt;/strong&gt; Not tracking how much of the context window is consumed by each component until responses start silently truncating&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No evaluation loop.&lt;/strong&gt; Shipping the pipeline without a way to systematically test whether context changes actually improved or hurt answer quality&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;If your AI feature is giving inconsistent or wrong answers and you've already spent real time tuning the prompt, the prompt was probably never the actual problem. Look at what the model can see when it answers, how it's retrieved, how it's structured, and how much of it is genuinely relevant. That's where the real fix usually lives.&lt;/p&gt;

&lt;p&gt;Have you run into this wall yet, prompt tuning stops helping and the real fix turns out to be the context pipeline underneath it? Curious what broke it open for you.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>Your AI Code Review Passed. That Doesn't Mean Your Code Is Safe.</title>
      <dc:creator>Emma Schmidt</dc:creator>
      <pubDate>Mon, 20 Jul 2026 05:49:39 +0000</pubDate>
      <link>https://dev.to/emma_schmidt_/your-ai-code-review-passed-that-doesnt-mean-your-code-is-safe-4e13</link>
      <guid>https://dev.to/emma_schmidt_/your-ai-code-review-passed-that-doesnt-mean-your-code-is-safe-4e13</guid>
      <description>&lt;p&gt;You push a PR. The automated review runs, comes back green, no issues flagged. You merge with confidence because the tool said it was clean. Two days later a teammate finds a broken auth check sitting in that exact file, something the review tool should have caught and simply didn't.&lt;/p&gt;

&lt;p&gt;This isn't a rare, unlucky edge case. Right now it's one of the fastest-growing complaint clusters showing up across GitHub issues, Stack Overflow, and developer forums: AI code review tools silently failing to execute properly, then reporting a clean pass anyway. Not flagging false positives. Not being too strict. Actually not running the check at all, and telling you everything's fine regardless.&lt;/p&gt;

&lt;p&gt;That gap between "the tool said pass" and "the code is actually safe" is exactly where teams are getting burned this year.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Keeps Happening
&lt;/h2&gt;

&lt;p&gt;The pattern behind it is almost always the same, and it's less exciting than a model being "wrong." It's plumbing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The review tool hits an environment misconfiguration mid-run, fails to execute the actual check, and reports success anyway instead of erroring loudly&lt;/li&gt;
&lt;li&gt;A dependency version mismatch between the CI environment and what the review tool expects causes it to silently skip files instead of flagging the mismatch&lt;/li&gt;
&lt;li&gt;Permissions or API scope issues cause partial runs that only cover some of the changed files, but the summary reads as if everything was reviewed&lt;/li&gt;
&lt;li&gt;Rate limiting or timeout issues cut a review short, and the tool defaults to "no issues found" instead of "review incomplete"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are the AI being fooled by clever code. The AI never actually looked. The failure happened one layer below it, in configuration, and the interface just didn't have a way to say "I couldn't finish this" instead of "this looks fine."&lt;/p&gt;

&lt;p&gt;According to recent industry analysis tracking developer complaint patterns across GitHub, Stack Overflow, Hacker News, and Bluesky, this exact failure mode is currently one of the highest-scoring pain clusters being reported, and it's still climbing rather than leveling off. That's a strong signal this isn't one unlucky team's config mistake, it's a systemic gap showing up across a large number of independent pipelines right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Examples of How This Shows Up
&lt;/h2&gt;

&lt;p&gt;To make this less abstract, here are the actual shapes this bug tends to take in production.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A review bot configured with an outdated API key silently returns an empty result set instead of an authentication error, and the pipeline reads that empty result as "nothing to flag"&lt;/li&gt;
&lt;li&gt;A large PR gets split across multiple review batches, one batch times out, and only the completed batches get reported, with no indication that a batch was dropped&lt;/li&gt;
&lt;li&gt;A newly added file type isn't covered by the reviewer's configuration, so it's skipped entirely, but the summary still says "all files reviewed"&lt;/li&gt;
&lt;li&gt;A flaky network call to the review service fails on retry, the pipeline catches the exception, and defaults to a pass state instead of surfacing the failure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of these looks identical from the outside. Green checkmark, no comments, merge button unlocked.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Know If You're Already Exposed
&lt;/h2&gt;

&lt;p&gt;Ask yourself these honestly, right now, about your current setup.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does your review tool distinguish clearly between "no issues found" and "review didn't complete," or do both look identical in your PR checks?&lt;/li&gt;
&lt;li&gt;Have you ever actually tested what happens when the review tool hits a timeout or a permissions error mid-run?&lt;/li&gt;
&lt;li&gt;Do you know exactly which files were covered in the last ten "passed" reviews, or are you trusting the green checkmark on faith?&lt;/li&gt;
&lt;li&gt;Has anyone on your team ever manually re-reviewed a "passed" PR and found something the tool missed?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can't answer the first two confidently, you likely have a blind spot exactly like the one that let that auth bug through.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Way to Catch This
&lt;/h2&gt;

&lt;p&gt;You don't need to abandon AI-assisted review, you just need to stop treating a green checkmark as proof of anything on its own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step one: log what was actually reviewed, not just the verdict&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/review-check.yml&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Verify review completeness&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;echo "Files changed: $(git diff --name-only origin/main | wc -l)"&lt;/span&gt;
    &lt;span class="s"&gt;echo "Files covered by review: ${{ steps.review.outputs.files_reviewed }}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Comparing these two numbers directly is often the single fastest way to catch a partial run before it merges.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step two: fail loudly instead of failing silently&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Fail if review coverage is incomplete&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.review.outputs.files_reviewed &amp;lt; steps.diff.outputs.files_changed&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exit &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A tool that can't complete its job should block the merge, not quietly wave it through. This one change closes most of the actual gap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step three: spot-check with a real second pass&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pick a random sample of "passed" PRs each week and have a human actually read the diff. Not every PR, just enough to catch drift before it becomes a pattern nobody noticed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step four: track the metric that actually matters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Review coverage percentage over time tells you far more than pass/fail counts ever will. A tool passing 100% of PRs while silently covering 60% of the files in them is worse than no automated review at all, because it's actively creating false confidence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step five: alert on silence, not just on failure&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Alert if reviewer produced no output at all&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.review.outcome == 'success' &amp;amp;&amp;amp; steps.review.outputs.comment_count == &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "::warning::Review completed with zero output, verify manually"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A review with literally zero comments on a large, complex PR is itself a signal worth flagging, not just quietly accepting as a clean bill of health.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters More Than It Looks Like It Does
&lt;/h2&gt;

&lt;p&gt;This isn't a minor tooling annoyance. It's currently one of the highest-volume, fastest-growing pain point clusters developers are reporting across multiple independent platforms this year, which means it's not one team's bad luck, it's a systemic gap in how a lot of review pipelines are configured right now.&lt;/p&gt;

&lt;p&gt;Closing this gap usually touches more than one layer of the stack, and the kind of support that actually helps includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auditing existing CI and code review pipelines to find where failures are getting swallowed instead of surfaced&lt;/li&gt;
&lt;li&gt;Setting up proper CI/CD workflows with explicit failure states, so "couldn't complete" never gets reported as "passed"&lt;/li&gt;
&lt;li&gt;QA and testing automation that adds a real second layer of verification instead of relying on a single tool's word&lt;/li&gt;
&lt;li&gt;Ongoing &lt;strong&gt;&lt;a href="https://zignuts.com/hire-devops-engineers?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;DevOps&lt;/a&gt;&lt;/strong&gt; support to keep the pipeline configuration from silently drifting out of sync as dependencies update&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Auditing and hardening a CI and code review pipeline so failures surface loudly instead of hiding behind a green checkmark is exactly the kind of infrastructure work that's easy to deprioritize until it costs you a production incident. This kind of pipeline auditing and DevOps support, focused specifically on catching these silent failure modes, is often the fastest way to close this gap without your team losing weeks untangling it mid-sprint.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Takeaway
&lt;/h2&gt;

&lt;p&gt;A passing check is not the same thing as a completed review, and right now a lot of pipelines are treating those two things as identical when they're not. The fix isn't more trust in the tool, it's less blind trust and a system that fails loudly when it can't actually do its job.&lt;/p&gt;

&lt;p&gt;Has your team ever caught a "passed" review that actually missed something? What tipped you off? Genuinely curious how widespread this is once people start comparing notes.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>cicd</category>
      <category>webdev</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Your Next.js Caching Strategy Is Probably Broken and You Won't Know Until Production</title>
      <dc:creator>Emma Schmidt</dc:creator>
      <pubDate>Fri, 17 Jul 2026 06:37:05 +0000</pubDate>
      <link>https://dev.to/emma_schmidt_/your-nextjs-caching-strategy-is-probably-broken-and-you-wont-know-until-production-3kd8</link>
      <guid>https://dev.to/emma_schmidt_/your-nextjs-caching-strategy-is-probably-broken-and-you-wont-know-until-production-3kd8</guid>
      <description>&lt;p&gt;Picture this. You ship a feature on a Friday. Everything works perfectly in every test you ran. Monday morning, support tickets start piling up. Users are seeing stale prices, old inventory counts, yesterday's data on pages that are supposed to update in real time. Nobody touched the code over the weekend. The caching layer just quietly decided to serve old data, and nobody noticed until customers did.&lt;/p&gt;

&lt;p&gt;If that story makes your stomach drop a little, you've lived some version of it. Caching in &lt;strong&gt;&lt;a href="https://zignuts.com/hire-nextjs-developers?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt;&lt;/strong&gt; has always been powerful and also, frankly, confusing. Too many overlapping mechanisms, unclear boundaries between what's cached and what isn't, and a debugging experience that mostly involves guessing. That's exactly the problem Cache Components were built to solve, and they're one of the biggest shifts in how Next.js apps are built this year.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem Developers Keep Running Into
&lt;/h2&gt;

&lt;p&gt;Before Cache Components, caching in Next.js meant juggling several different systems at once, each with its own rules.&lt;/p&gt;

&lt;p&gt;Here's what that actually looked like in practice.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetch requests had their own caching behavior, controlled through options that were easy to forget or misconfigure&lt;/li&gt;
&lt;li&gt;Route segments had a separate caching model tied to how the route itself was structured&lt;/li&gt;
&lt;li&gt;Revalidation logic lived in yet another place, disconnected from the component actually rendering the data&lt;/li&gt;
&lt;li&gt;One misconfigured fetch call could silently cache data that should have been dynamic, and the bug wouldn't show up until real traffic hit it&lt;/li&gt;
&lt;li&gt;Debugging meant tracing through multiple files and configuration layers just to answer one simple question: why is this page showing old data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result was a system that worked fine in a demo and quietly broke in ways that only showed up under real production conditions, which is exactly the worst time to discover a caching bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Cache Components Actually Change
&lt;/h2&gt;

&lt;p&gt;Cache Components flip the caching model on its head. Instead of caching being an implicit side effect scattered across fetch calls and route configs, caching becomes an explicit, visible part of the component itself.&lt;/p&gt;

&lt;p&gt;The core idea is simple. You mark exactly which parts of your UI are cacheable, right at the component level, and everything else is dynamic by default. No more guessing which layer decided to cache something. No more hunting through config files. The caching boundary lives right next to the code it affects.&lt;/p&gt;

&lt;p&gt;This matters because it turns a debugging nightmare into something you can actually reason about by reading the component tree.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building It Step by Step
&lt;/h2&gt;

&lt;p&gt;Let's walk through what this looks like in a real project, starting with the problem from the intro: a product page that needs live inventory data but was accidentally serving stale numbers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step one: enable Cache Components in your project&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// next.config.js&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;cacheComponents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This flips the default behavior so components are dynamic unless you explicitly say otherwise, which is the opposite of the old implicit caching model and immediately removes an entire category of accidental staleness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step two: mark what should actually be cached&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// components/ProductDescription.jsx&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductDescription&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getProductDetails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;'use cache'&lt;/code&gt; directive right at the top of the component is the whole point. Anyone reading this file instantly knows this data is cached, without needing to trace through fetch options or route configuration somewhere else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step three: keep the volatile data dynamic, explicitly&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// components/InventoryStatus.jsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;InventoryStatus&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inventory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getLiveInventory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; in stock&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No cache directive here on purpose. This component always fetches fresh, and it's obvious at a glance why, because there's no &lt;code&gt;'use cache'&lt;/code&gt; line sitting at the top of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step four: compose them together on the same page&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/products/[id]/page.jsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ProductDescription&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/ProductDescription&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;InventoryStatus&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/InventoryStatus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProductDescription&lt;/span&gt; &lt;span class="na"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;InventoryStatus&lt;/span&gt; &lt;span class="na"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the part that actually solves the Friday-to-Monday problem from the intro. The description, which rarely changes, gets cached and served fast. The inventory count, which changes constantly, stays dynamic every single time. Both live on the same page, and the boundary between them is sitting right there in the code, not buried in a config file three folders away.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step five: verify it under real conditions, not just locally&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://your-app.com/products/123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the cache headers directly instead of trusting that it's working because it looked fine in dev mode. This is the step most teams skip, and it's exactly the step that would have caught the stale pricing bug before real customers did.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Is the Right Moment to Care
&lt;/h2&gt;

&lt;p&gt;Cache Components landed as part of Next.js 16, and the framework team has been actively rolling out tooling specifically to help existing projects adopt the pattern gradually rather than forcing a painful rewrite. That's a strong signal this isn't an experimental side feature, it's the direction the framework is committing to going forward.&lt;/p&gt;

&lt;p&gt;There's also a security angle worth knowing about. Next.js recently moved to a formal monthly security release schedule, with several advisories this year involving middleware and proxy bypass issues in App Router applications. Caching and routing bugs aren't just a performance annoyance anymore, they can quietly become a security gap too, which makes getting this right even more important than it used to be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Teams Actually Get Stuck
&lt;/h2&gt;

&lt;p&gt;Migrating an existing app to Cache Components isn't a find-and-replace job. A few things trip people up consistently.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assuming a component is cache-safe just because it looks static, when it actually depends on request-specific data like cookies or headers&lt;/li&gt;
&lt;li&gt;Forgetting that nested components inherit caching behavior from their parents in ways that aren't always obvious at first glance&lt;/li&gt;
&lt;li&gt;Rolling out the change across an entire large app at once instead of migrating one route at a time and verifying behavior as you go&lt;/li&gt;
&lt;li&gt;Skipping the production-level verification step and trusting that local dev behavior matches what actually happens under real traffic and real cache layers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Getting this migration right on a production app, especially one with a large existing codebase, real traffic patterns, and revenue riding on pages not showing stale data, is exactly the kind of work that benefits from experienced hands. &lt;strong&gt;&lt;a href="https://zignuts.com/hire-web-developers?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;Custom web application development&lt;/a&gt;&lt;/strong&gt; and performance auditing support built specifically around Next.js architecture can be the difference between a smooth, gradual rollout and a repeat of that Friday-to-Monday nightmare from the top of this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;Caching bugs are uniquely painful because they're invisible until real traffic exposes them. Cache Components won't make caching disappear as a concept, but they make the boundaries visible, explicit, and sitting right next to the code that depends on them, instead of hidden three layers deep in configuration nobody remembers writing.&lt;/p&gt;

&lt;p&gt;Have you started migrating to Cache Components yet, or are you still untangling the old fetch-level caching rules? Even if you haven't touched it yet, I'd love to hear what's held you back, drop it in the comments.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>react</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Your Deploy Process Is Held Together By One Person's Memory, and Everyone Knows It</title>
      <dc:creator>Emma Schmidt</dc:creator>
      <pubDate>Thu, 16 Jul 2026 05:27:46 +0000</pubDate>
      <link>https://dev.to/emma_schmidt_/your-deploy-process-is-held-together-by-one-persons-memory-and-everyone-knows-it-3a98</link>
      <guid>https://dev.to/emma_schmidt_/your-deploy-process-is-held-together-by-one-persons-memory-and-everyone-knows-it-3a98</guid>
      <description>&lt;p&gt;Ask your team what happens when someone needs to spin up a new service. If the honest answer involves a Slack message to "the person who knows infra," a half-documented runbook from eighteen months ago, and at least one manual step nobody's automated because "it's fine, it only takes five minutes," you're not alone. You're just describing most engineering teams in 2026, whether they admit it publicly or not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem That's Quietly Slowing Everyone Down
&lt;/h2&gt;

&lt;p&gt;Here's the pattern I keep seeing, on my own team and everyone else's:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New service setup takes days, not because the code is hard, but because provisioning, secrets, and environment config all live in someone's head&lt;/li&gt;
&lt;li&gt;The same Terraform module gets copy-pasted and slightly modified across a dozen repos, each one now its own snowflake&lt;/li&gt;
&lt;li&gt;Onboarding a new engineer means shadowing someone for two weeks just to learn where things live and who to ask&lt;/li&gt;
&lt;li&gt;Every "quick fix" in production touches infra that only one or two people actually understand end to end&lt;/li&gt;
&lt;li&gt;Nobody remembers why a particular staging environment exists, but nobody's brave enough to delete it either&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this shows up in a burndown chart. It shows up as a quiet tax on every single feature, paid in Slack messages, tribal knowledge, and one overworked person who can never actually take a real vacation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Got Worse, Not Better
&lt;/h2&gt;

&lt;p&gt;You'd think faster tooling would fix this. Instead it made the gap more obvious. Teams are shipping code faster than ever, but the infrastructure and provisioning layer underneath it hasn't kept pace, so the bottleneck just moved. It used to be "how fast can we write the code." Now it's "how fast can we actually get this thing deployed, secured, and observable without bothering the one person who understands the pipeline."&lt;/p&gt;

&lt;p&gt;Check how many of these sound familiar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your CI pipeline has grown into a script nobody fully understands anymore, edited in place for years&lt;/li&gt;
&lt;li&gt;Getting a new environment takes a support ticket instead of a self-service click&lt;/li&gt;
&lt;li&gt;Security and compliance checks happen at the very end, right before a deadline, instead of automatically along the way&lt;/li&gt;
&lt;li&gt;"Works on staging" and "works in production" are two different, unrelated statements&lt;/li&gt;
&lt;li&gt;Rolling back a bad deploy involves someone remembering the exact right sequence of commands, not clicking a button&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If three or more of those are true, the actual problem isn't your code quality. It's that nobody's ever sat down and built a real, self-service platform layer between your engineers and your infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Real Fix Looks Like
&lt;/h2&gt;

&lt;p&gt;This is exactly what platform engineering is meant to solve, and it's picking up serious momentum this year for a simple reason: it turns infrastructure from a bottleneck into a self-service layer.&lt;/p&gt;

&lt;p&gt;A working internal developer platform generally means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A paved road, not a maze.&lt;/strong&gt; New services get provisioned through a standard template, not by copying whatever repo looked closest last time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-service environments.&lt;/strong&gt; A developer can spin up a sandboxed environment without filing a ticket and waiting two days for a reply.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security baked in, not bolted on.&lt;/strong&gt; Compliance and security scans run automatically as part of the pipeline, not as a manual gate right before launch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation that isn't a person.&lt;/strong&gt; If your platform's tribal knowledge lives entirely in one engineer's head, that's not documentation, that's a single point of failure with a name and a Slack handle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability by default.&lt;/strong&gt; Logs, metrics, and alerts get wired up automatically for every new service, instead of being an afterthought someone adds after the first incident.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What This Actually Looks Like Day to Day
&lt;/h2&gt;

&lt;p&gt;To make this concrete, here's the difference in practice, side by side:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spinning up a new service, the old way:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Message the one person who knows the Terraform setup&lt;/li&gt;
&lt;li&gt;Wait for a reply, then wait again for review&lt;/li&gt;
&lt;li&gt;Copy an existing repo and manually strip out what doesn't apply&lt;/li&gt;
&lt;li&gt;Manually request secrets access and hope you didn't miss one&lt;/li&gt;
&lt;li&gt;Discover a week later that monitoring was never actually wired up&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Spinning up a new service, with a real platform layer:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run a single command or click a template in an internal portal&lt;/li&gt;
&lt;li&gt;Environment, secrets, and networking are provisioned automatically&lt;/li&gt;
&lt;li&gt;Security and compliance scans run as part of the pipeline, not after&lt;/li&gt;
&lt;li&gt;Logging and alerting exist from the first deploy, not after the first incident&lt;/li&gt;
&lt;li&gt;A new hire can do this alone on day one, without pinging anyone&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Quick Gut Check
&lt;/h2&gt;

&lt;p&gt;Before you convince yourself this doesn't apply to your team, answer honestly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If your most senior infra person left tomorrow, how long before something breaks that nobody else can fix?&lt;/li&gt;
&lt;li&gt;How many manual steps happen between "code merged" and "code running in production"?&lt;/li&gt;
&lt;li&gt;Can a new hire spin up a working environment on day one, or day ten?&lt;/li&gt;
&lt;li&gt;Do you know, right now, how many staging environments are running that nobody actually uses anymore?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If those answers make you a little uncomfortable, that discomfort is the actual signal, not the org chart or the sprint velocity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Most Teams Never Actually Fix This
&lt;/h2&gt;

&lt;p&gt;Not because it's not worth fixing. Because it's genuinely hard to prioritize. It never feels as urgent as the next feature deadline, right up until the day it becomes an outage. And building a proper platform layer, one with real self-service provisioning, security automation, and documentation that survives someone leaving, is its own significant engineering project, one that a team already underwater on feature work rarely has the bandwidth to take on internally.&lt;/p&gt;

&lt;p&gt;That's usually the point where bringing in outside expertise makes more sense than trying to squeeze it into an already packed roadmap. The kind of help that actually moves the needle here tends to cover things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud architecture design and infrastructure audits to find where the snowflakes and single points of failure actually live&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://zignuts.com/hire-devops-engineers?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;DevOps&lt;/a&gt;&lt;/strong&gt; and CI/CD pipeline setup that replaces the script nobody fully understands with something documented and repeatable&lt;/li&gt;
&lt;li&gt;Custom internal tooling and self-service platform builds tailored to how your team actually works, not a generic template&lt;/li&gt;
&lt;li&gt;Security and compliance automation baked into the pipeline instead of bolted on before a deadline&lt;/li&gt;
&lt;li&gt;Ongoing support so the platform stays maintained instead of quietly rotting the way the last one did&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A team that's built this exact kind of platform layer before can usually get you from "one person's memory" to "documented, self-service, secure by default" a lot faster than doing it in the margins of a sprint that was already full.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Cost of Waiting
&lt;/h2&gt;

&lt;p&gt;Every month this stays unfixed, the tax gets a little heavier. More snowflake environments. More tribal knowledge locked in one person's head. More engineers quietly frustrated that setting up a simple service takes three days instead of three hours.&lt;/p&gt;

&lt;p&gt;Is your team still running on institutional memory, or have you actually built the paved road? Genuinely curious how far along everyone else is, because I suspect most of us are further behind than we'd like to admit.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>webdev</category>
      <category>programming</category>
      <category>discuss</category>
    </item>
    <item>
      <title>I Spent Two Weeks Replacing REST With tRPC. Here's What Nobody Tells You.</title>
      <dc:creator>Emma Schmidt</dc:creator>
      <pubDate>Wed, 15 Jul 2026 13:51:35 +0000</pubDate>
      <link>https://dev.to/emma_schmidt_/i-spent-two-weeks-replacing-rest-with-trpc-heres-what-nobody-tells-you-2cnj</link>
      <guid>https://dev.to/emma_schmidt_/i-spent-two-weeks-replacing-rest-with-trpc-heres-what-nobody-tells-you-2cnj</guid>
      <description>&lt;p&gt;I've written more REST endpoints than I can count. Request comes in, I write a DTO, I write a validator, I write matching types on the frontend, I pray the two never drift apart. Then last month I finally gave in and rebuilt a mid-sized internal tool using tRPC instead, mostly because half of my dev.to feed wouldn't shut up about it.&lt;/p&gt;

&lt;p&gt;Two weeks later I get it. But not for the reasons everyone's tweeting about.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem That Started This
&lt;/h2&gt;

&lt;p&gt;Here's the exact bug that pushed me over the edge. Someone renamed a field on the backend from &lt;code&gt;userId&lt;/code&gt; to &lt;code&gt;accountId&lt;/code&gt;. The API still returned 200. The frontend still rendered. It just quietly showed &lt;code&gt;undefined&lt;/code&gt; everywhere that field was used, and nobody noticed until a customer emailed asking why their dashboard looked broken.&lt;/p&gt;

&lt;p&gt;That's the failure mode REST allows by default: a contract that only exists in your head and in a documentation page nobody reads. Nothing crashes, nothing throws, it just silently lies to you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pitch Everyone Makes
&lt;/h2&gt;

&lt;p&gt;The usual sales pitch goes like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No more manually syncing types between frontend and backend&lt;/li&gt;
&lt;li&gt;No more API documentation going stale the week after you write it&lt;/li&gt;
&lt;li&gt;No more runtime surprises because your types are shared end to end&lt;/li&gt;
&lt;li&gt;Change a field on the server, &lt;strong&gt;&lt;a href="https://zignuts.com/hire-typescript-developers?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt;&lt;/strong&gt; yells at you on the client before you even save the file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All true. None of it is the actual reason I'd recommend it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Changed My Mind
&lt;/h2&gt;

&lt;p&gt;The real shift wasn't the type safety itself. It was how much dead weight quietly disappeared from the codebase once I stopped needing it.&lt;/p&gt;

&lt;p&gt;Here's what I deleted from the project by the end of week two:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The OpenAPI spec file, which was already three fields behind reality&lt;/li&gt;
&lt;li&gt;The separate client SDK generation step that ran in CI and occasionally just failed for no reason&lt;/li&gt;
&lt;li&gt;The Postman collection that three people updated and forty people ignored&lt;/li&gt;
&lt;li&gt;A folder of hand-written TypeScript interfaces that existed only to describe what the API returned
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server/router.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appRouter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;router&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;publicProcedure&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AppRouter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;appRouter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// client/api.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createTRPCClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@trpc/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../server/router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createTRPCClient&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppRouter&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/trpc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// user is fully typed, no codegen step, no .d.ts file to regenerate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. That's the whole integration. No schema file to keep in sync, no build step to remember to run, no "did anyone regenerate the types after that last deploy" Slack message.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before and After, Side by Side
&lt;/h2&gt;

&lt;p&gt;This is the part that actually sold the rest of my team, so I'll lay it out plainly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Renaming a field on the backend, REST version:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Update the field in the database model&lt;/li&gt;
&lt;li&gt;Update the endpoint response shape&lt;/li&gt;
&lt;li&gt;Update the OpenAPI spec, if anyone remembers&lt;/li&gt;
&lt;li&gt;Regenerate the client SDK, if the CI step doesn't silently fail&lt;/li&gt;
&lt;li&gt;Manually update every frontend type that referenced the old field&lt;/li&gt;
&lt;li&gt;Find out three weeks later that one component was missed&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Renaming a field on the backend, tRPC version:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Update the field in the database model&lt;/li&gt;
&lt;li&gt;Save the file&lt;/li&gt;
&lt;li&gt;Every frontend usage that references the old field name turns red in your editor immediately&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That second list isn't shorter because I'm exaggerating for effect. It's shorter because two entire categories of human error just stopped being possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where It Actually Falls Apart
&lt;/h2&gt;

&lt;p&gt;Here's the part the hype posts conveniently skip.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;tRPC only works when both sides speak TypeScript.&lt;/strong&gt; The moment any of these show up, the whole pitch changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A mobile team building in &lt;strong&gt;&lt;a href="https://zignuts.com/hire-swift-developers?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;Swift&lt;/a&gt;&lt;/strong&gt; or Kotlin&lt;/li&gt;
&lt;li&gt;A partner integration that needs a documented, language-agnostic contract&lt;/li&gt;
&lt;li&gt;A public API with external developers who've never seen your codebase&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In every one of those cases you're back to needing REST or GraphQL with real documentation, because "just import the types" doesn't work across a language boundary or a company boundary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The migration cost is real, and it's not a weekend project.&lt;/strong&gt; Here's what actually ate my two weeks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every existing fetch call needed rewriting, not just the obvious ones&lt;/li&gt;
&lt;li&gt;Every error handling pattern had to be redone since tRPC errors don't look like REST errors&lt;/li&gt;
&lt;li&gt;Every loading and retry state in the UI needed a second look&lt;/li&gt;
&lt;li&gt;One legacy endpoint that three other internal tools depended on had to keep working in REST form the entire time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're planning this migration, budget for the boring rewrite work, not just the exciting type-safety demo.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Quick Gut Check Before You Migrate
&lt;/h2&gt;

&lt;p&gt;Ask yourself these before touching a single endpoint:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does my frontend and backend both live in TypeScript, ideally the same repo?&lt;/li&gt;
&lt;li&gt;Does one team own both sides, or will a separate mobile or partner team need this API too?&lt;/li&gt;
&lt;li&gt;Am I trying to fix a real, recurring bug pattern, or just chasing what's trending this month?&lt;/li&gt;
&lt;li&gt;Can I migrate one feature at a time instead of doing a big bang rewrite?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you answered yes to the first two and no to the third, you're the exact use case tRPC was built for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Actual Verdict
&lt;/h2&gt;

&lt;p&gt;For an internal dashboard, an admin panel, or any &lt;strong&gt;&lt;a href="https://zignuts.com/full-stack-development-services?utm_source=seo&amp;amp;utm_medium=backlinks&amp;amp;utm_campaign=seo_referral&amp;amp;utm_id=7" rel="noopener noreferrer"&gt;full stack&lt;/a&gt;&lt;/strong&gt; app where one team owns both ends, it's genuinely worth the switch. For anything with external consumers, keep your REST or GraphQL layer and don't let a trend talk you out of a decision that was already correct.&lt;/p&gt;

&lt;p&gt;The pattern that seems to be winning in 2026 isn't picking one paradigm forever, it's running both side by side on purpose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;tRPC&lt;/strong&gt; for the internal application layer, where your own frontend talks to your own backend&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REST&lt;/strong&gt; for webhooks, third-party integrations, and anything public facing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two systems, each doing the job it's actually good at, instead of forcing one tool to cover every use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Would I Do It Again
&lt;/h2&gt;

&lt;p&gt;Yes, but only on projects where I'd draw the same boundary I just described. The type safety is nice. The disappearance of an entire category of "the docs are out of date again" bugs is the part that actually saved me time, and saved me from that one silent &lt;code&gt;undefined&lt;/code&gt; bug that started this whole thing.&lt;/p&gt;

&lt;p&gt;Have you made the jump yet, or are you still holding the line with REST? Curious where everyone's landing on this one, drop your setup in the comments.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>webdev</category>
      <category>api</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
