<?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: Amrishkhan Sheik Abdullah</title>
    <description>The latest articles on DEV Community by Amrishkhan Sheik Abdullah (@amrishkhan05).</description>
    <link>https://dev.to/amrishkhan05</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%2F3548760%2Fbcded5d3-5ff4-4681-b616-b4a5a81a0064.png</url>
      <title>DEV Community: Amrishkhan Sheik Abdullah</title>
      <link>https://dev.to/amrishkhan05</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/amrishkhan05"/>
    <language>en</language>
    <item>
      <title>200 Broken Watches Couldn't Solve It. A Bouncing Ball Did.</title>
      <dc:creator>Amrishkhan Sheik Abdullah</dc:creator>
      <pubDate>Sat, 05 Sep 2026 07:58:15 +0000</pubDate>
      <link>https://dev.to/amrishkhan05/200-broken-watches-couldnt-solve-it-a-bouncing-ball-did-2ae9</link>
      <guid>https://dev.to/amrishkhan05/200-broken-watches-couldnt-solve-it-a-bouncing-ball-did-2ae9</guid>
      <description>&lt;h3&gt;
  
  
  Who Saw the Connection? #05 — G-SHOCK × A Bouncing Ball × Engineering Breakthroughs
&lt;/h3&gt;

&lt;p&gt;In the early 1980s, Kikuo Ibe and a small team at Casio spent roughly two years trying to make a watch survive something watches weren't supposed to survive.&lt;/p&gt;

&lt;p&gt;They built more than 200 prototypes. Ibe dropped them from a third-floor bathroom window at Casio's research facility. Components broke, designs changed, more prototypes followed, and eventually the team reached that particularly unpleasant stage of engineering where you're no longer sure whether the next iteration is progress or simply a more expensive way of confirming that the previous one failed.&lt;/p&gt;

&lt;p&gt;The breakthrough didn't come from another watch.&lt;/p&gt;

&lt;p&gt;It came when Ibe saw children playing with a rubber ball.&lt;/p&gt;

&lt;p&gt;That observation helped produce G-SHOCK.&lt;/p&gt;

&lt;p&gt;And strangely enough, solving the engineering problem wasn't even the end of the story. Casio still had to convince people that they wanted the thing.&lt;/p&gt;




&lt;h2&gt;
  
  
  It Started With a Broken Watch
&lt;/h2&gt;

&lt;p&gt;The story begins in 1981 with an accident that was completely ordinary at the time.&lt;/p&gt;

&lt;p&gt;Ibe owned a watch given to him by his father. One day, it fell from his wrist and broke.&lt;/p&gt;

&lt;p&gt;Today, that sounds like a product failure. At the time, it wasn't necessarily seen that way. Wristwatches were precision instruments. You were expected to treat them carefully. Thinness and elegance were desirable; surviving serious physical punishment wasn't generally part of the bargain.&lt;/p&gt;

&lt;p&gt;There is an interesting habit in engineering where, if a limitation survives long enough, we eventually stop describing it as a limitation.&lt;/p&gt;

&lt;p&gt;We start describing it as &lt;strong&gt;proper usage&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Don't drop the watch. Don't send requests that quickly. Don't run that query during peak hours. Don't let the payload get that large. Don't deploy on Friday.&lt;/p&gt;

&lt;p&gt;Some of those rules exist for excellent reasons. Others are historical compromises that quietly acquired the status of natural law.&lt;/p&gt;

&lt;p&gt;Ibe questioned one of them.&lt;/p&gt;

&lt;p&gt;He submitted a proposal internally at Casio with a remarkably simple objective: &lt;strong&gt;a watch that wouldn't break when dropped&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Casio approved the idea, and Ibe joined a small development group called &lt;strong&gt;Project Team Tough&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Then they made the requirement considerably less reasonable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Triple 10 Was an Almost Rude Engineering Requirement
&lt;/h2&gt;

&lt;p&gt;The development philosophy became known as &lt;strong&gt;Triple 10&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;survive a &lt;strong&gt;10-metre drop&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;withstand &lt;strong&gt;10 bar of water pressure&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;target a &lt;strong&gt;10-year battery life&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That combination matters.&lt;/p&gt;

&lt;p&gt;Casio wasn't merely asking engineers to improve one specification. A wristwatch contains a quartz timing mechanism, electronics, an LCD, a battery and multiple connections, all packed into something small enough to wear comfortably.&lt;/p&gt;

&lt;p&gt;Shock resistance alone is difficult.&lt;/p&gt;

&lt;p&gt;Water resistance alone is difficult.&lt;/p&gt;

&lt;p&gt;Long battery life imposes another set of constraints.&lt;/p&gt;

&lt;p&gt;Now solve all of them without producing something approximately the size of a microwave.&lt;/p&gt;

&lt;p&gt;The original production G-SHOCK wouldn't literally achieve every Triple 10 number. The DW-5000C actually exceeded the water target with 20-bar resistance, while its specified battery life was seven years. Triple 10 was the development ambition that shaped the project rather than a literal specification sheet for the first model.&lt;/p&gt;

&lt;p&gt;That distinction is important because the mythology around successful products tends to become cleaner than the engineering that created them.&lt;/p&gt;

&lt;p&gt;The real story is better.&lt;/p&gt;

&lt;p&gt;The requirement was aggressive enough that the existing idea of a wristwatch stopped being particularly useful.&lt;/p&gt;




&lt;h2&gt;
  
  
  Then They Started Throwing Watches Out of a Window
&lt;/h2&gt;

&lt;p&gt;Casio says Project Team Tough produced &lt;strong&gt;more than 200 prototypes over roughly two years&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Testing shock resistance initially involved a beautifully direct piece of engineering equipment:&lt;/p&gt;

&lt;p&gt;a third-floor bathroom window.&lt;/p&gt;

&lt;p&gt;Ibe repeatedly dropped prototypes from roughly ten metres above the ground at Casio's research facility.&lt;/p&gt;

&lt;p&gt;Something would break. The team would examine it, reinforce it and build another version. That prototype would survive one failure mode and reveal another.&lt;/p&gt;

&lt;p&gt;They experimented with cushioning. They added rubber. They strengthened vulnerable components. At one stage, the protective structure became so large that Casio describes a prototype approaching the size of a softball.&lt;/p&gt;

&lt;p&gt;This is where the number “200 prototypes” is often turned into a motivational story.&lt;/p&gt;

&lt;p&gt;Keep trying.&lt;/p&gt;

&lt;p&gt;Never give up.&lt;/p&gt;

&lt;p&gt;Prototype number 201 might change your life.&lt;/p&gt;

&lt;p&gt;That's pleasant advice. It also misses the most interesting part of the engineering.&lt;/p&gt;

&lt;p&gt;The prototypes weren't valuable because there were 200 of them.&lt;/p&gt;

&lt;p&gt;They were valuable because &lt;strong&gt;the failures were accumulating information&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Strengthen one component and the impact finds another path.&lt;/p&gt;

&lt;p&gt;Protect that component and something else becomes the weak point.&lt;/p&gt;

&lt;p&gt;Add enough protection and you've technically saved the watch by turning it into an object nobody wants to wear.&lt;/p&gt;

&lt;p&gt;Eventually, the failures were telling Ibe that he might be solving the wrong problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  Then He Saw a Child Bouncing a Ball
&lt;/h2&gt;

&lt;p&gt;According to Casio's account, development eventually reached the point where Ibe gave himself one final week to find a solution.&lt;/p&gt;

&lt;p&gt;On the final day, a Sunday, he went to a park near the research facility.&lt;/p&gt;

&lt;p&gt;Children were playing with a rubber ball.&lt;/p&gt;

&lt;p&gt;Nothing about that scene had anything obvious to do with watches. Nobody was conducting an experiment. The children were not unknowingly participating in Casio's R&amp;amp;D program.&lt;/p&gt;

&lt;p&gt;But Ibe noticed something.&lt;/p&gt;

&lt;p&gt;When the ball hit the ground, its exterior absorbed and deformed around the impact while its centre remained comparatively isolated from the collision.&lt;/p&gt;

&lt;p&gt;That changed the question.&lt;/p&gt;

&lt;p&gt;Until then, much of the effort had been directed toward making the watch's individual components capable of surviving shock.&lt;/p&gt;

&lt;p&gt;The ball suggested another approach:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if the important components didn't have to take the impact directly at all?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of trying to make the mechanism indestructible, isolate it.&lt;/p&gt;

&lt;p&gt;That idea contributed to the hollow, shock-resistant structure behind G-SHOCK. The watch's critical module could effectively float inside the protective case, reducing the amount of impact energy transmitted directly into the electronics.&lt;/p&gt;

&lt;p&gt;The breakthrough wasn't stronger components.&lt;/p&gt;

&lt;p&gt;It was a different relationship between the components and the force trying to destroy them.&lt;/p&gt;

&lt;p&gt;Millions of people had watched children bounce rubber balls.&lt;/p&gt;

&lt;p&gt;Ibe saw &lt;strong&gt;shock isolation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is the moment that makes this a &lt;em&gt;Who Saw the Connection?&lt;/em&gt; story.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Best Debugging Sometimes Changes the Question
&lt;/h2&gt;

&lt;p&gt;There is a software version of the 200 broken watches.&lt;/p&gt;

&lt;p&gt;A service starts timing out, so we increase the timeout.&lt;/p&gt;

&lt;p&gt;Now requests survive longer but consume connections, so we increase the connection pool.&lt;/p&gt;

&lt;p&gt;That increases pressure on the database, so we scale the database.&lt;/p&gt;

&lt;p&gt;Retries begin amplifying traffic, so we add backoff.&lt;/p&gt;

&lt;p&gt;Then we add caching.&lt;/p&gt;

&lt;p&gt;Then somebody proposes another queue.&lt;/p&gt;

&lt;p&gt;Six weeks later, the architecture diagram looks like it has developed a stress disorder.&lt;/p&gt;

&lt;p&gt;Every individual change may be reasonable. The danger is that we can become extremely good at solving the latest symptom without reconsidering the model generating those symptoms.&lt;/p&gt;

&lt;p&gt;Ibe could have continued reinforcing components.&lt;/p&gt;

&lt;p&gt;Instead, the bouncing ball gave him another abstraction.&lt;/p&gt;

&lt;p&gt;The question changed from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do I make every component survive impact?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do I stop the impact reaching the components?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That distinction appears everywhere in serious engineering.&lt;/p&gt;

&lt;p&gt;Don't make the downstream service infinitely tolerant of duplicate requests if you can make the operation idempotent.&lt;/p&gt;

&lt;p&gt;Don't keep increasing worker capacity if the real problem is uncontrolled upstream demand.&lt;/p&gt;

&lt;p&gt;Don't repeatedly repair corrupted state if the architecture allows invalid state to exist unnecessarily.&lt;/p&gt;

&lt;p&gt;Sometimes the breakthrough isn't a better answer.&lt;/p&gt;

&lt;p&gt;It's discovering that you've been asking the wrong question.&lt;/p&gt;




&lt;h2&gt;
  
  
  In 1983, They Finally Built It
&lt;/h2&gt;

&lt;p&gt;The first G-SHOCK, the &lt;strong&gt;DW-5000C&lt;/strong&gt;, launched in April 1983.&lt;/p&gt;

&lt;p&gt;It looked like the engineering problem that had created it: thick, protected, functional and unapologetically different from the slimmer watches popular at the time.&lt;/p&gt;

&lt;p&gt;Two years of work had produced something genuinely unusual. The watch could tolerate punishment that would have destroyed conventional watches, and the structural thinking behind it would become foundational to the G-SHOCK family.&lt;/p&gt;

&lt;p&gt;This is where the movie version ends.&lt;/p&gt;

&lt;p&gt;The engineer refuses to give up. The impossible product finally works. Customers immediately recognize its brilliance. Inspirational music arrives precisely on schedule.&lt;/p&gt;

&lt;p&gt;Actual markets have a crueler sense of humour.&lt;/p&gt;

&lt;p&gt;The G-SHOCK initially struggled in Japan.&lt;/p&gt;

&lt;p&gt;Casio's own retrospective describes a market where thin watches were mainstream and the bulky G-SHOCK wasn't immediately embraced. One person involved with the project later recalled that there was &lt;strong&gt;“almost no reaction at all”&lt;/strong&gt; in Japan initially.&lt;/p&gt;

&lt;p&gt;Think about that.&lt;/p&gt;

&lt;p&gt;Two years.&lt;/p&gt;

&lt;p&gt;More than 200 prototypes.&lt;/p&gt;

&lt;p&gt;A genuinely different architecture.&lt;/p&gt;

&lt;p&gt;A product that solved something people had largely accepted as unavoidable.&lt;/p&gt;

&lt;p&gt;And the market essentially responded:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;That's quite a large watch.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Technical achievement and market acceptance, it turns out, are completely different engineering problems.&lt;/p&gt;




&lt;h2&gt;
  
  
  America Saw Something Japan Didn't
&lt;/h2&gt;

&lt;p&gt;The G-SHOCK story became stranger when the watch reached the United States.&lt;/p&gt;

&lt;p&gt;Its ruggedness made immediate sense to people whose jobs or lifestyles were hard on equipment. Casio later identified early adoption among firefighters, police officers, military personnel and other professionals working in demanding environments.&lt;/p&gt;

&lt;p&gt;The same characteristic that made the watch seem unnecessarily bulky to one audience made it useful to another.&lt;/p&gt;

&lt;p&gt;Then Casio's American marketing operation found an exceptionally efficient way of explaining the product.&lt;/p&gt;

&lt;p&gt;They used a G-SHOCK as an &lt;strong&gt;ice-hockey puck&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A 1984 television advertisement showed a player taking a shot with the watch. The goalkeeper caught it. The G-SHOCK survived.&lt;/p&gt;

&lt;p&gt;This was a fantastic advertisement partly because some viewers thought it was nonsense.&lt;/p&gt;

&lt;p&gt;A precision electronic instrument surviving a hockey slapshot sounded suspiciously convenient for a commercial.&lt;/p&gt;

&lt;p&gt;Questions about the claim reached an American television program that investigated potentially misleading advertising. The program recreated the test.&lt;/p&gt;

&lt;p&gt;The watch survived.&lt;/p&gt;

&lt;p&gt;Casio's account says they went further and placed a watch on a road before running over it with a large truck.&lt;/p&gt;

&lt;p&gt;Still working.&lt;/p&gt;

&lt;p&gt;Casio had spent years engineering a difficult-to-believe property into the product.&lt;/p&gt;

&lt;p&gt;Skeptical television producers accidentally gave them something even better than advertising:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;independent proof.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  If Your Claim Sounds Extraordinary, Stop Describing It
&lt;/h2&gt;

&lt;p&gt;There is a wonderfully practical lesson here for anyone trying to build anything.&lt;/p&gt;

&lt;p&gt;Imagine Casio advertising the G-SHOCK like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Featuring an innovative hollow-case architecture and advanced shock-resistant construction for exceptional durability.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Perfectly accurate.&lt;/p&gt;

&lt;p&gt;Completely forgettable.&lt;/p&gt;

&lt;p&gt;Now compare it with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Here's our watch. We're going to use it as a hockey puck.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You understand the product in approximately three seconds.&lt;/p&gt;

&lt;p&gt;Technology is full of products whose strongest characteristics are buried under adjectives.&lt;/p&gt;

&lt;p&gt;High-performance.&lt;/p&gt;

&lt;p&gt;Enterprise-grade.&lt;/p&gt;

&lt;p&gt;Privacy-first.&lt;/p&gt;

&lt;p&gt;AI-powered.&lt;/p&gt;

&lt;p&gt;Scalable.&lt;/p&gt;

&lt;p&gt;Resilient.&lt;/p&gt;

&lt;p&gt;After enough years in software, these words develop a remarkable ability to pass through the human brain without disturbing anything on the way.&lt;/p&gt;

&lt;p&gt;If the thing you built is genuinely unusual, demonstrate the unusual part.&lt;/p&gt;

&lt;p&gt;If you built a privacy-first developer tool that processes data locally, show the network tab while somebody processes a sensitive file.&lt;/p&gt;

&lt;p&gt;If your coding-agent plugin claims to prevent bloated AI changes, show a 600-line patch becoming 40 lines while preserving the requirement.&lt;/p&gt;

&lt;p&gt;If your architecture dramatically reduces latency, run the workload.&lt;/p&gt;

&lt;p&gt;If you're interviewing for an architecture role, don't spend fifteen minutes saying you're good at architecture. Show a difficult decision, the alternatives you rejected, the failure mode you were protecting against and what happened in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proof travels farther than adjectives.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;G-SHOCK happened to have one of the greatest product demonstrations imaginable.&lt;/p&gt;

&lt;p&gt;Hit it.&lt;/p&gt;

&lt;p&gt;Still works.&lt;/p&gt;

&lt;p&gt;No architecture diagram required.&lt;/p&gt;




&lt;h2&gt;
  
  
  Then Japan Imported Its Own Idea Back
&lt;/h2&gt;

&lt;p&gt;By the late 1980s, something unexpected was happening.&lt;/p&gt;

&lt;p&gt;G-SHOCK was gaining an identity in the United States that it hadn't originally possessed in Japan.&lt;/p&gt;

&lt;p&gt;Its toughness had practical value for people in demanding professions, but the watch also began spreading through sports and youth culture. Skateboarders, snowboarders, surfers and other communities found something appealing in the same rugged design that had once worked against it.&lt;/p&gt;

&lt;p&gt;Then that image crossed the Pacific.&lt;/p&gt;

&lt;p&gt;Casio executives later described noticing Japanese G-SHOCK sales increasing around &lt;strong&gt;1989–1990&lt;/strong&gt; and investigating what had changed.&lt;/p&gt;

&lt;p&gt;Their conclusion was unusually fascinating.&lt;/p&gt;

&lt;p&gt;It was a &lt;strong&gt;reverse import&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The watch had been designed in Japan, struggled to find enthusiasm there, acquired popularity and cultural meaning in America, and then returned to Japanese consumers through magazines and American cultural influence.&lt;/p&gt;

&lt;p&gt;Casio eventually leaned into this deliberately. Its Japanese marketing emphasized the watch's American popularity, including campaigns built around the message that &lt;strong&gt;America loved G-SHOCK&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The engineering hadn't suddenly improved during the flight back to Japan.&lt;/p&gt;

&lt;p&gt;The product was fundamentally the same.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What changed was what the product meant.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Same Product Can Be Wrong in One Room and Right in Another
&lt;/h2&gt;

&lt;p&gt;This may be the part of the G-SHOCK story I find most useful outside product development.&lt;/p&gt;

&lt;p&gt;We tend to interpret rejection as a verdict on quality.&lt;/p&gt;

&lt;p&gt;Sometimes that's exactly what it is.&lt;/p&gt;

&lt;p&gt;But rejection can also contain another message:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;wrong audience.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;G-SHOCK's toughness mattered immediately to people whose watches were exposed to physical punishment. Later, that toughness acquired another meaning among sports and youth communities. Eventually, the chunky design itself became part of the fashion.&lt;/p&gt;

&lt;p&gt;Same watch.&lt;/p&gt;

&lt;p&gt;Different people saw different value in it.&lt;/p&gt;

&lt;p&gt;That happens in careers constantly.&lt;/p&gt;

&lt;p&gt;An engineer can work somewhere that values ticket throughput above everything else. Their habit of thinking about architecture, failure modes and long-term maintainability is treated as unnecessary complication.&lt;/p&gt;

&lt;p&gt;Move that same engineer into an organization struggling with distributed systems, platform reliability or technical strategy and the trait that previously looked like overthinking may become exactly why they were hired.&lt;/p&gt;

&lt;p&gt;The engineer didn't suddenly become smarter on Monday.&lt;/p&gt;

&lt;p&gt;The room changed.&lt;/p&gt;

&lt;p&gt;The same thing happens to ideas. An open-source project can receive almost no attention because it was shown to the wrong developer community. An article can disappear on one publishing platform and spread rapidly on another. A product aimed at enterprises can fail while individual developers quietly love it.&lt;/p&gt;

&lt;p&gt;The dangerous conclusion is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nobody wants this.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sometimes the more accurate conclusion is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nobody &lt;strong&gt;here&lt;/strong&gt; wants this.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those sentences are separated by one word.&lt;/p&gt;

&lt;p&gt;Careers and companies can be separated by the same distance.&lt;/p&gt;




&lt;h2&gt;
  
  
  But “Wrong Audience” Can Become a Very Comfortable Lie
&lt;/h2&gt;

&lt;p&gt;There is an obvious trap here.&lt;/p&gt;

&lt;p&gt;If nobody likes what you've built, you cannot spend the next five years declaring that society has simply failed to recognize your genius.&lt;/p&gt;

&lt;p&gt;Sometimes the product is bad.&lt;/p&gt;

&lt;p&gt;Sometimes the feature isn't useful.&lt;/p&gt;

&lt;p&gt;Sometimes your résumé isn't communicating enough evidence.&lt;/p&gt;

&lt;p&gt;Sometimes the article is boring.&lt;/p&gt;

&lt;p&gt;Sometimes the side project solves a problem nobody actually has.&lt;/p&gt;

&lt;p&gt;The G-SHOCK lesson isn't to ignore rejection.&lt;/p&gt;

&lt;p&gt;It is to &lt;strong&gt;diagnose rejection correctly&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Ask what the evidence says.&lt;/p&gt;

&lt;p&gt;Does the underlying thing work?&lt;/p&gt;

&lt;p&gt;Can you demonstrate the value?&lt;/p&gt;

&lt;p&gt;Has anyone independently found it useful?&lt;/p&gt;

&lt;p&gt;Does another audience respond differently?&lt;/p&gt;

&lt;p&gt;Are people rejecting the capability itself, or the way you've positioned it?&lt;/p&gt;

&lt;p&gt;G-SHOCK accumulated evidence.&lt;/p&gt;

&lt;p&gt;The watch survived punishment. Professionals bought it. American sales grew. Different communities independently found reasons to wear it.&lt;/p&gt;

&lt;p&gt;That evidence justified reconsidering the market.&lt;/p&gt;

&lt;p&gt;Without evidence, “wrong audience” is merely a pleasant story we tell ourselves.&lt;/p&gt;

&lt;p&gt;This is why careers, products and engineering failures all require roughly the same uncomfortable skill:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;separating persistence from denial.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Triple 10 Was Really a Bet Against Incremental Thinking
&lt;/h2&gt;

&lt;p&gt;There is another reason I like the Triple 10 story.&lt;/p&gt;

&lt;p&gt;Imagine Casio beginning with the existing watch architecture and asking what improvement seemed reasonable.&lt;/p&gt;

&lt;p&gt;Perhaps they could make it 20% more resistant to shock.&lt;/p&gt;

&lt;p&gt;Add some cushioning.&lt;/p&gt;

&lt;p&gt;Improve the case.&lt;/p&gt;

&lt;p&gt;Strengthen a few components.&lt;/p&gt;

&lt;p&gt;That could have produced a perfectly respectable watch.&lt;/p&gt;

&lt;p&gt;It probably wouldn't have produced G-SHOCK.&lt;/p&gt;

&lt;p&gt;Triple 10 approached the problem from the other direction. It described the desired outcome first and allowed that outcome to make the existing architecture uncomfortable.&lt;/p&gt;

&lt;p&gt;Software requirements often get negotiated in reverse.&lt;/p&gt;

&lt;p&gt;We quietly shape the requirement around whatever system already exists.&lt;/p&gt;

&lt;p&gt;“The database can't support that.”&lt;/p&gt;

&lt;p&gt;“Our deployment model doesn't work that way.”&lt;/p&gt;

&lt;p&gt;“The framework makes that difficult.”&lt;/p&gt;

&lt;p&gt;Those are useful engineering constraints. Ignoring reality is not architecture.&lt;/p&gt;

&lt;p&gt;But occasionally the requirement is telling us something else:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;the architecture is answering an old question.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Good engineering judgment is knowing when a constraint should be respected and when the constraint itself deserves investigation.&lt;/p&gt;

&lt;p&gt;Ibe could have produced a stronger conventional watch.&lt;/p&gt;

&lt;p&gt;Instead, the requirement eventually forced him to reconsider what “protecting a watch” actually meant.&lt;/p&gt;




&lt;h2&gt;
  
  
  Two Hundred Attempts Are Useless If Attempt 201 Learns Nothing
&lt;/h2&gt;

&lt;p&gt;This is where I would resist turning the G-SHOCK story into motivational wallpaper.&lt;/p&gt;

&lt;p&gt;“Never give up” is incomplete advice.&lt;/p&gt;

&lt;p&gt;If prototype 198 teaches you exactly what prototype 12 taught you, you're not demonstrating resilience. You've developed a remarkably expensive hobby.&lt;/p&gt;

&lt;p&gt;The useful part of failure is the information it produces.&lt;/p&gt;

&lt;p&gt;One component breaks. Strengthening it exposes another weak point. More cushioning makes the product impractically large. Eventually enough evidence accumulates that the original model becomes difficult to defend.&lt;/p&gt;

&lt;p&gt;Then something has to change.&lt;/p&gt;

&lt;p&gt;That applies far beyond engineering.&lt;/p&gt;

&lt;p&gt;Twenty rejected job applications may mean you need to send twenty more. They may also mean your positioning isn't working.&lt;/p&gt;

&lt;p&gt;Six months of low product adoption may mean marketing needs more time. It may also mean you've misunderstood the problem.&lt;/p&gt;

&lt;p&gt;Five architectural fixes may mean the sixth needs more effort. They may also mean you've been fixing the same symptom five different ways.&lt;/p&gt;

&lt;p&gt;Persistence matters enormously.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;persistence without model correction is repetition&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The difficult skill isn't refusing to quit.&lt;/p&gt;

&lt;p&gt;It's understanding what the failure is trying to tell you before deciding what to do next.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Ball Is Still My Favourite Part
&lt;/h2&gt;

&lt;p&gt;For all the marketing, cultural and career lessons hiding inside G-SHOCK, I keep coming back to the rubber ball.&lt;/p&gt;

&lt;p&gt;Because Ibe wasn't studying shock-resistant products when the breakthrough appeared.&lt;/p&gt;

&lt;p&gt;He was watching children play.&lt;/p&gt;

&lt;p&gt;A bouncing ball and a wristwatch share almost nothing at the level of appearance.&lt;/p&gt;

&lt;p&gt;The connection exists one level deeper.&lt;/p&gt;

&lt;p&gt;The exterior experiences the collision.&lt;/p&gt;

&lt;p&gt;The centre is protected.&lt;/p&gt;

&lt;p&gt;That's transferable.&lt;/p&gt;

&lt;p&gt;This ability to recognize the &lt;strong&gt;structure of a problem&lt;/strong&gt; somewhere that uses completely different objects, language and assumptions is one of the most valuable forms of thinking I know.&lt;/p&gt;

&lt;p&gt;Toyota engineers could look at supermarkets and think about manufacturing flow.&lt;/p&gt;

&lt;p&gt;Hospital teams could look at Formula 1 pit stops and think about patient handovers.&lt;/p&gt;

&lt;p&gt;Japanese railway operators could turn a simple physical gesture into a mechanism for reducing human error.&lt;/p&gt;

&lt;p&gt;And a watch engineer could look at a child bouncing a ball and stop thinking about stronger watch components.&lt;/p&gt;

&lt;p&gt;The world is full of solved problems wearing the wrong uniform.&lt;/p&gt;

&lt;p&gt;Sometimes expertise means knowing your field extremely well.&lt;/p&gt;

&lt;p&gt;Sometimes it means looking away from your field long enough to notice that somebody else has already solved the shape of your problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  Who Saw the Connection?
&lt;/h2&gt;

&lt;p&gt;G-SHOCK eventually became one of Casio's defining products. By 2017, cumulative shipments had passed 100 million units, and Casio announced that the brand surpassed &lt;strong&gt;150 million watches shipped worldwide in 2024&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Looking backward, success makes the path appear almost inevitable.&lt;/p&gt;

&lt;p&gt;It wasn't.&lt;/p&gt;

&lt;p&gt;There was a broken watch.&lt;/p&gt;

&lt;p&gt;An unreasonable requirement.&lt;/p&gt;

&lt;p&gt;A bathroom window.&lt;/p&gt;

&lt;p&gt;More than 200 prototypes.&lt;/p&gt;

&lt;p&gt;A design that kept failing.&lt;/p&gt;

&lt;p&gt;A child bouncing a ball.&lt;/p&gt;

&lt;p&gt;A product Japan initially didn't particularly want.&lt;/p&gt;

&lt;p&gt;An American hockey commercial people thought was fake.&lt;/p&gt;

&lt;p&gt;A television program that accidentally became product validation.&lt;/p&gt;

&lt;p&gt;And eventually, Japan rediscovering a Japanese watch partly because America had taught it to look at the product differently.&lt;/p&gt;

&lt;p&gt;That isn't a clean story about perseverance.&lt;/p&gt;

&lt;p&gt;It's a much more useful story about &lt;strong&gt;seeing differently&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When the prototypes kept breaking, Ibe eventually changed how he saw the engineering problem.&lt;/p&gt;

&lt;p&gt;When the Japanese market didn't respond, America saw a different use and identity for the product.&lt;/p&gt;

&lt;p&gt;When people didn't believe the advertising, demonstration replaced description.&lt;/p&gt;

&lt;p&gt;And when the product returned to Japan, consumers saw something in it they hadn't seen the first time.&lt;/p&gt;

&lt;p&gt;That's why the rubber ball matters.&lt;/p&gt;

&lt;p&gt;Hundreds of prototypes had already told Ibe that his current answers weren't enough.&lt;/p&gt;

&lt;p&gt;The ball helped him ask a different question.&lt;/p&gt;

&lt;p&gt;The next time something you've built isn't working—whether it's software, a product, an idea or even your career—the useful question may not immediately be:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I try harder?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It might be:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What am I still looking at the same way?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because sometimes the breakthrough isn't hidden in another hundred attempts.&lt;/p&gt;

&lt;p&gt;Sometimes it's bouncing around in a park.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources &amp;amp; Further Reading
&lt;/h2&gt;

&lt;p&gt;Casio's official G-SHOCK history documents &lt;strong&gt;Project Team Tough&lt;/strong&gt;, the approximately two-year development effort, more than 200 prototypes and the shock-resistant structure that emerged from the project.&lt;/p&gt;

&lt;p&gt;Casio's history of the original 5000/5600 family describes Ibe's repeated drop tests from the third-floor bathroom window, the increasingly large prototypes and the observation of children playing with a ball that helped lead toward the floating-module structure.&lt;/p&gt;

&lt;p&gt;Casio's material on &lt;strong&gt;Triple 10&lt;/strong&gt; documents the original development ambition and helps distinguish that philosophy from the literal specifications of the first DW-5000C.&lt;/p&gt;

&lt;p&gt;The Japanese Patent Office has also documented the development of G-SHOCK, including the hollow structural concept associated with Ibe's observation of a bouncing ball.&lt;/p&gt;

&lt;p&gt;Casio's corporate retrospective and integrated reporting provide the particularly interesting second half of the story: weak initial reaction in Japan, American adoption, the hockey-puck demonstration, growth through American sports and youth culture, and the later &lt;strong&gt;reverse-import&lt;/strong&gt; effect back into Japan.&lt;/p&gt;




&lt;h2&gt;
  
  
  About the Person Who Somehow Ended Up Throwing Software Out of a Third-Floor Window
&lt;/h2&gt;

&lt;p&gt;I'm &lt;strong&gt;Amrishkhan Sheik Abdullah&lt;/strong&gt;, a Technical Lead and software architect based in Dubai. I build backend systems, microservices and distributed architectures for a living — which mostly means discovering that when the fifth patch doesn't fix something, the architecture may be trying to tell us something.&lt;/p&gt;

&lt;p&gt;I write about software architecture, distributed systems, engineering decisions, AI developer tooling and, increasingly, ordinary products and behaviours that turn out to have surprisingly serious engineering hiding underneath them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who Saw the Connection?&lt;/strong&gt; is my series about exactly that: true stories where somebody looked at an apparently unrelated problem and saw an idea everyone else was walking past.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read More of My Writing
&lt;/h3&gt;

&lt;p&gt;I publish longer engineering articles, architecture deep-dives and the occasional story that starts with somebody throwing watches out of a bathroom window and somehow ends up discussing your career.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Writing:&lt;/strong&gt; &lt;a href="https://www.amrishkhan.dev/blog" rel="noopener noreferrer"&gt;amrishkhan.dev/blog&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Elsewhere on the Internet
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Portfolio:&lt;/strong&gt; &lt;a href="https://www.amrishkhan.dev/" rel="noopener noreferrer"&gt;amrishkhan.dev&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/amrishkhan05" rel="noopener noreferrer"&gt;github.com/amrishkhan05&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;DEV Community:&lt;/strong&gt; &lt;a href="https://dev.to/amrishkhan05"&gt;dev.to/amrishkhan05&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/amrishkhan/" rel="noopener noreferrer"&gt;linkedin.com/in/amrishkhan&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you enjoyed this one, stick around. Apparently my current research strategy is to keep staring at ordinary things until somebody's strange decision from forty years ago starts explaining software engineering.&lt;/p&gt;

</description>
      <category>casio</category>
      <category>mixedreality</category>
      <category>realworld</category>
    </item>
    <item>
      <title>Why Do Japanese Train Drivers Keep Pointing at Things?</title>
      <dc:creator>Amrishkhan Sheik Abdullah</dc:creator>
      <pubDate>Sat, 05 Sep 2026 05:05:00 +0000</pubDate>
      <link>https://dev.to/amrishkhan05/why-do-japanese-train-drivers-keep-pointing-at-things-272b</link>
      <guid>https://dev.to/amrishkhan05/why-do-japanese-train-drivers-keep-pointing-at-things-272b</guid>
      <description>&lt;h3&gt;
  
  
  Who Saw the Connection? #04 — Railways × Human Factors × Software
&lt;/h3&gt;

&lt;p&gt;If you spend enough time on a railway platform in Japan, you may notice something that looks oddly theatrical.&lt;/p&gt;

&lt;p&gt;A conductor checks the platform, points down it, says something aloud, looks toward another reference point, and repeats the process. Inside the driver's cab, the same pattern appears around signals, instruments and track conditions.&lt;/p&gt;

&lt;p&gt;At first glance, it can look ceremonial. Maybe it's part of the formality people associate with Japanese railways. Maybe the employee is communicating with somebody farther down the platform.&lt;/p&gt;

&lt;p&gt;The real explanation is more interesting.&lt;/p&gt;

&lt;p&gt;They're performing a safety technique called &lt;strong&gt;shisa kanko&lt;/strong&gt;, usually translated as &lt;strong&gt;pointing and calling&lt;/strong&gt;. Instead of merely looking at an important signal or instrument, the worker points toward it and says its status aloud.&lt;/p&gt;

&lt;p&gt;The practice has existed in Japanese railways in some form for more than a century. Calling out signal information appeared in railway instruction manuals by the 1910s; pointing was added later, during the late 1920s, and Japanese National Railways formally required pointing while calling signals in its regulations in 1970. (&lt;a href="https://www.nippon.com/en/japan-topics/g02575/?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Nippon&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;That history is interesting on its own.&lt;/p&gt;

&lt;p&gt;What makes it worth writing about is why the ritual survived.&lt;/p&gt;

&lt;p&gt;Because apparently &lt;strong&gt;looking at something and actually noticing it are not the same operation&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Railway Problem That Was Really a Human Problem
&lt;/h2&gt;

&lt;p&gt;Railways are full of repetitive decisions.&lt;/p&gt;

&lt;p&gt;Signals are checked again and again. Instruments are read again and again. Doors are opened and closed hundreds of times. Routes become familiar. Stations become familiar. Procedures become familiar.&lt;/p&gt;

&lt;p&gt;That familiarity is useful because humans would be painfully inefficient if every routine action demanded our full concentration.&lt;/p&gt;

&lt;p&gt;Unfortunately, the same automaticity that makes experienced people fast also creates a peculiar failure mode: sometimes the brain becomes very good at seeing what it expects to see.&lt;/p&gt;

&lt;p&gt;Most signals behave exactly as expected.&lt;/p&gt;

&lt;p&gt;Until one doesn't.&lt;/p&gt;

&lt;p&gt;Most gauges remain within their normal range.&lt;/p&gt;

&lt;p&gt;Until one doesn't.&lt;/p&gt;

&lt;p&gt;A person can therefore perform the physical act of looking without necessarily performing the cognitive act of registering what changed.&lt;/p&gt;

&lt;p&gt;Railway operators discovered that simply telling people to “pay attention” was not a particularly sophisticated safety system.&lt;/p&gt;

&lt;p&gt;Pointing and calling forces the check out of the worker's head.&lt;/p&gt;

&lt;p&gt;The operator looks at the target, points at it, identifies it verbally and hears their own confirmation. Instead of allowing the brain to glide over a familiar object, the procedure turns observation into a small physical event.&lt;/p&gt;

&lt;p&gt;It feels almost comically simple.&lt;/p&gt;

&lt;p&gt;That simplicity turns out to be important.&lt;/p&gt;




&lt;h2&gt;
  
  
  Researchers Eventually Tested It
&lt;/h2&gt;

&lt;p&gt;In 1996, researchers from Japan's Railway Technical Research Institute published a laboratory experiment designed to measure whether pointing and calling genuinely reduced errors rather than merely looking reassuring.&lt;/p&gt;

&lt;p&gt;Participants performed a choice-reaction task under four conditions: no special confirmation, pointing only, calling only, and pointing while calling.&lt;/p&gt;

&lt;p&gt;The reported error rates were:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Confirmation method&lt;/th&gt;
&lt;th&gt;Error rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No pointing or calling&lt;/td&gt;
&lt;td&gt;2.38%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pointing only&lt;/td&gt;
&lt;td&gt;1.00%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Calling only&lt;/td&gt;
&lt;td&gt;0.75%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pointing and calling&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.38%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The combined technique reduced mistakes in that experiment to roughly &lt;strong&gt;one-sixth of the baseline rate&lt;/strong&gt;. (&lt;a href="https://www.jstage.jst.go.jp/article/jaiop/9/2/9_107/_article/-char/en?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;J-STAGE&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;There is an important caveat here.&lt;/p&gt;

&lt;p&gt;That does &lt;strong&gt;not&lt;/strong&gt; mean Japanese train drivers have 84% fewer accidents because they point at signals. The study was a controlled cognitive experiment, not a railway-wide accident trial.&lt;/p&gt;

&lt;p&gt;But it does establish something more useful: forcing a person to externalize a confirmation can materially change how reliably they perform that confirmation.&lt;/p&gt;

&lt;p&gt;That is a surprisingly deep result hiding inside what looks like a man pointing at a light.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Finger Is Not the Important Part
&lt;/h2&gt;

&lt;p&gt;It would be easy to turn this into a quirky piece of advice:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Software engineers should point at production servers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I would prefer not to be responsible for that management initiative.&lt;/p&gt;

&lt;p&gt;The gesture itself is not the transferable idea.&lt;/p&gt;

&lt;p&gt;The interesting mechanism is that &lt;strong&gt;an important internal assumption is converted into an observable action before something consequential happens&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imagine a train driver approaching a signal.&lt;/p&gt;

&lt;p&gt;A purely mental workflow might be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I saw it. Green. Continue.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pointing and calling adds another step between perception and action.&lt;/p&gt;

&lt;p&gt;The driver has to identify the actual object, direct attention toward it, verbalize what it says, and only then continue.&lt;/p&gt;

&lt;p&gt;That is essentially a tiny piece of human-factors engineering inserted between &lt;strong&gt;“I think I know”&lt;/strong&gt; and &lt;strong&gt;“execute.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Software systems desperately need more thinking in that space.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your Terminal Has the Same Problem
&lt;/h2&gt;

&lt;p&gt;Consider a production database migration.&lt;/p&gt;

&lt;p&gt;You have already tested it in development. Then staging. The pull request is reviewed, the change ticket is approved, and everything looks routine.&lt;/p&gt;

&lt;p&gt;You have also done this sort of thing hundreds of times.&lt;/p&gt;

&lt;p&gt;That sounds reassuring until you realize familiarity is precisely what makes the situation dangerous.&lt;/p&gt;

&lt;p&gt;Beginners tend to move slowly around dangerous systems. Experienced engineers become comfortable around them. Usually that is what expertise is supposed to achieve.&lt;/p&gt;

&lt;p&gt;Then one day somebody runs a perfectly valid command against the perfectly wrong database.&lt;/p&gt;

&lt;p&gt;Nothing crashes because of a software defect.&lt;/p&gt;

&lt;p&gt;Authentication works.&lt;/p&gt;

&lt;p&gt;DNS works.&lt;/p&gt;

&lt;p&gt;The database receives the command and performs it faithfully.&lt;/p&gt;

&lt;p&gt;From the database's perspective, this is excellent service.&lt;/p&gt;

&lt;p&gt;The human definition of success tends to differ.&lt;/p&gt;

&lt;p&gt;A surprising number of operational interfaces ask humans to distinguish dangerous contexts using extremely weak visual cues. Perhaps the shell prompt contains &lt;code&gt;prod&lt;/code&gt; instead of &lt;code&gt;preprod&lt;/code&gt;. Perhaps one browser tab belongs to a different AWS account. Perhaps the Kubernetes context differs by three characters.&lt;/p&gt;

&lt;p&gt;Then an engineer is expected to notice the distinction during an incident at two in the morning.&lt;/p&gt;

&lt;p&gt;When they don't, the postmortem eventually contains the phrase &lt;strong&gt;human error&lt;/strong&gt;, as though we have discovered an exotic new failure mode.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Software Can Steal From the Railway
&lt;/h2&gt;

&lt;p&gt;A better system doesn't merely ask the engineer whether they checked the environment.&lt;/p&gt;

&lt;p&gt;It makes the environment itself part of the confirmation.&lt;/p&gt;

&lt;p&gt;Suppose a migration tool displays this immediately before executing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TARGET ENVIRONMENT : PRODUCTION
ACCOUNT            : 847291
REGION             : eu-west-1
DATABASE           : payments-primary

Type the database name to continue:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is quite different from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Are you sure? [y/N]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anyone who has used computers for more than a few years has developed world-class muscle memory for defeating “Are you sure?” dialogs.&lt;/p&gt;

&lt;p&gt;The first version requires the engineer to inspect something that exists in the actual execution context and feed that information back into the action.&lt;/p&gt;

&lt;p&gt;It is a software equivalent of pointing at the signal and calling its state.&lt;/p&gt;

&lt;p&gt;This idea already appears in good operational tooling. Destructive interfaces sometimes require users to type the resource name. Production deployment systems prominently display environment, commit and target information immediately before promotion. Safety-critical workflows may require a second operator to independently verify an action.&lt;/p&gt;

&lt;p&gt;The common principle is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;don't merely ask whether someone checked; design the workflow so the check leaves evidence.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is much stronger than another sentence in a runbook.&lt;/p&gt;




&lt;h2&gt;
  
  
  We Keep Trying to Solve Human Error With Documentation
&lt;/h2&gt;

&lt;p&gt;A familiar incident-response pattern goes like this.&lt;/p&gt;

&lt;p&gt;Something goes wrong. The team fixes the immediate problem, conducts a postmortem and eventually adds a new instruction:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Engineers must verify that the production environment is selected before running migrations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There.&lt;/p&gt;

&lt;p&gt;Safety has been upgraded.&lt;/p&gt;

&lt;p&gt;Until somebody selects the wrong environment again.&lt;/p&gt;

&lt;p&gt;The problem is not that the instruction is incorrect. Obviously the engineer should verify the environment.&lt;/p&gt;

&lt;p&gt;The problem is that the instruction does nothing to change the circumstances under which the previous person failed to verify it.&lt;/p&gt;

&lt;p&gt;Japanese railway workers did not spend decades developing pointing and calling because nobody had previously realized signals were important.&lt;/p&gt;

&lt;p&gt;They developed it because &lt;strong&gt;knowing that something should be checked and reliably performing that check are different engineering problems&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Software organizations confuse those two surprisingly often.&lt;/p&gt;

&lt;p&gt;We write documentation describing ideal human behavior and then treat deviations as individual mistakes.&lt;/p&gt;

&lt;p&gt;Sometimes they are.&lt;/p&gt;

&lt;p&gt;But sometimes the interface itself quietly assumes humans will remain perfectly attentive while performing repetitive work under time pressure.&lt;/p&gt;

&lt;p&gt;That is an optimistic architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  Friction Is Not Always the Enemy
&lt;/h2&gt;

&lt;p&gt;Modern product engineering has spent years removing friction.&lt;/p&gt;

&lt;p&gt;Fewer clicks. Faster flows. Automatic defaults. Remember the previous value. Skip the confirmation. Infer the next step.&lt;/p&gt;

&lt;p&gt;For routine actions, that is usually excellent design.&lt;/p&gt;

&lt;p&gt;Nobody wants a three-person approval ceremony to change a CSS variable.&lt;/p&gt;

&lt;p&gt;But we've become so accustomed to treating friction as inherently bad that we occasionally remove it from places where &lt;strong&gt;friction was doing useful work&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A railway driver pointing at a signal takes slightly longer than glancing at it.&lt;/p&gt;

&lt;p&gt;That extra moment is not inefficiency accidentally left in the process.&lt;/p&gt;

&lt;p&gt;It is part of the process.&lt;/p&gt;

&lt;p&gt;The same distinction matters in software. A confirmation step that appears only before deleting a production database is not the same thing as an unnecessary modal appearing every time somebody saves a preference.&lt;/p&gt;

&lt;p&gt;One is bureaucracy.&lt;/p&gt;

&lt;p&gt;The other is a cognitive speed bump placed where moving too quickly is expensive.&lt;/p&gt;

&lt;p&gt;Good engineering is not about removing all friction.&lt;/p&gt;

&lt;p&gt;It is about knowing where friction costs more than it protects, and where a small inconvenience is extraordinarily cheap insurance.&lt;/p&gt;




&lt;h2&gt;
  
  
  There Is a Catch: Humans Can Automate the Ritual Too
&lt;/h2&gt;

&lt;p&gt;Pointing and calling has its own uncomfortable failure mode.&lt;/p&gt;

&lt;p&gt;Any procedure repeated often enough can become automatic.&lt;/p&gt;

&lt;p&gt;A worker can eventually point, say the required words and continue while giving the confirmation very little thought. Japanese railway safety work has therefore also examined how to keep the practice from becoming an empty ritual and how to demonstrate its actual error-prevention effect during training. (&lt;a href="https://www.nippon.com/en/japan-topics/g02575/?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Nippon&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Software engineers will recognize this immediately.&lt;/p&gt;

&lt;p&gt;We are exceptionally good at turning meaningful safeguards into ceremonial checkboxes.&lt;/p&gt;

&lt;p&gt;Did you review the logs? Yes.&lt;/p&gt;

&lt;p&gt;Rollback tested? Yes.&lt;/p&gt;

&lt;p&gt;Security impact considered? Yes.&lt;/p&gt;

&lt;p&gt;Did anybody meaningfully perform those checks?&lt;/p&gt;

&lt;p&gt;That question tends not to fit inside the form.&lt;/p&gt;

&lt;p&gt;This is why the strongest confirmations interact with evidence rather than merely requesting agreement.&lt;/p&gt;

&lt;p&gt;Don't ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is this the correct database?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Show:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;payments-primary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and require the operator to act on that identity.&lt;/p&gt;

&lt;p&gt;Don't ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Are you sure this is production?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Make the production account visually and operationally different enough that the person has to acknowledge the actual context.&lt;/p&gt;

&lt;p&gt;And wherever a machine can verify something reliably, let the machine verify it.&lt;/p&gt;

&lt;p&gt;Pointing and calling is not an excuse for poor automation.&lt;/p&gt;

&lt;p&gt;It is useful precisely where human judgment remains unavoidable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Automation Makes the Remaining Human Actions Stranger, Not Safer
&lt;/h2&gt;

&lt;p&gt;There is another reason this idea matters more as systems become increasingly automated.&lt;/p&gt;

&lt;p&gt;Automation removes routine human intervention.&lt;/p&gt;

&lt;p&gt;That is good.&lt;/p&gt;

&lt;p&gt;But it means the actions humans still perform tend to happen when something unusual has occurred.&lt;/p&gt;

&lt;p&gt;Think about an automated deployment platform that works perfectly 99.9% of the time. The operator no longer manually performs dozens of steps every day. They intervene only when the automation cannot determine what to do.&lt;/p&gt;

&lt;p&gt;So the human is now performing a rarely practised task during an abnormal situation, probably while somebody is asking for an ETA in Teams.&lt;/p&gt;

&lt;p&gt;That is exactly when memory and attention are least deserving of architectural faith.&lt;/p&gt;

&lt;p&gt;The more sophisticated our automation becomes, the more carefully we should design the remaining moments where a human must make an irreversible decision.&lt;/p&gt;

&lt;p&gt;“An experienced engineer will notice” is not a control mechanism.&lt;/p&gt;

&lt;p&gt;It is hope wearing an access badge.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Practice Escaped the Railway
&lt;/h2&gt;

&lt;p&gt;Pointing and calling did not remain confined to train operations. It spread into Japanese factories, construction sites and other industries where repetitive work and human error can combine badly. (&lt;a href="https://www.nippon.com/en/japan-topics/g02575/?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Nippon&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;That spread makes sense because the underlying problem was never really about trains.&lt;/p&gt;

&lt;p&gt;Railways simply happened to encounter it early and frequently.&lt;/p&gt;

&lt;p&gt;Any environment containing repetitive operations, dangerous exceptions and humans working under time pressure eventually faces the same question:&lt;/p&gt;

&lt;p&gt;How do you make sure somebody notices the one thing that changed?&lt;/p&gt;

&lt;p&gt;Different industries answer differently.&lt;/p&gt;

&lt;p&gt;Checklists.&lt;/p&gt;

&lt;p&gt;Two-person verification.&lt;/p&gt;

&lt;p&gt;Physical interlocks.&lt;/p&gt;

&lt;p&gt;Audible alarms.&lt;/p&gt;

&lt;p&gt;Typed confirmations.&lt;/p&gt;

&lt;p&gt;Pointing and calling.&lt;/p&gt;

&lt;p&gt;The implementations differ, but the systems thinking underneath them is remarkably similar.&lt;/p&gt;

&lt;p&gt;You do not solve human fallibility by demanding less fallible humans.&lt;/p&gt;

&lt;p&gt;You redesign the interaction around the human you actually have.&lt;/p&gt;




&lt;h2&gt;
  
  
  Maybe the Pointing Isn't the Strange Part
&lt;/h2&gt;

&lt;p&gt;Once you understand what Japanese railway workers are doing, the pointing stops looking particularly strange.&lt;/p&gt;

&lt;p&gt;What starts looking strange is how many software systems rely on much weaker safeguards.&lt;/p&gt;

&lt;p&gt;We put staging and production behind nearly identical screens.&lt;/p&gt;

&lt;p&gt;We distinguish destructive commands from harmless ones with a flag.&lt;/p&gt;

&lt;p&gt;We hide critical context inside terminal prompts that experienced engineers barely consciously read anymore.&lt;/p&gt;

&lt;p&gt;Then somebody misses something and we add another paragraph to the runbook.&lt;/p&gt;

&lt;p&gt;Railway workers arrived at a more practical conclusion more than a century ago: if a particular observation matters enough, don't leave it entirely inside somebody's head.&lt;/p&gt;

&lt;p&gt;Make them identify it.&lt;/p&gt;

&lt;p&gt;Make them interact with it.&lt;/p&gt;

&lt;p&gt;Make the confirmation part of the operation itself.&lt;/p&gt;

&lt;p&gt;The laboratory evidence suggests that this apparently simple change can substantially reduce mistakes during confirmation tasks. (&lt;a href="https://www.jstage.jst.go.jp/article/jaiop/9/2/9_107/_article/-char/en?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;J-STAGE&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;And there is something wonderfully unglamorous about that.&lt;/p&gt;

&lt;p&gt;No artificial intelligence.&lt;/p&gt;

&lt;p&gt;No predictive analytics.&lt;/p&gt;

&lt;p&gt;No billion-dollar control system.&lt;/p&gt;

&lt;p&gt;Just a small piece of behavior designed around an inconvenient fact about humans.&lt;/p&gt;

&lt;p&gt;Sometimes we look directly at things and don't really see them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Who Saw the Connection?
&lt;/h2&gt;

&lt;p&gt;This series keeps pulling me toward engineering decisions that are sitting in public view while almost everyone walks past them.&lt;/p&gt;

&lt;p&gt;A UPS truck avoids a left turn and most of us see traffic.&lt;/p&gt;

&lt;p&gt;A railway worker points at a signal and most of us see a strange gesture.&lt;/p&gt;

&lt;p&gt;But underneath both behaviors is deliberate engineering: somebody studied a mundane action closely enough to discover that changing it could make an entire system behave better.&lt;/p&gt;

&lt;p&gt;That is the part I find fascinating.&lt;/p&gt;

&lt;p&gt;Software engineers tend to search for solutions inside software. We look for a new framework, architecture pattern, database or protocol because our vocabulary makes our problems feel unusually technical.&lt;/p&gt;

&lt;p&gt;But reliability problems existed long before Kubernetes decided to give them YAML.&lt;/p&gt;

&lt;p&gt;Railway operators have spent more than a century dealing with repetitive work, incomplete attention, dangerous exceptions and humans who occasionally make mistakes while doing something they have successfully done thousands of times.&lt;/p&gt;

&lt;p&gt;Their solution to one part of that problem looks almost absurdly simple.&lt;/p&gt;

&lt;p&gt;Point at the thing.&lt;/p&gt;

&lt;p&gt;Say what the thing actually is.&lt;/p&gt;

&lt;p&gt;Then act.&lt;/p&gt;

&lt;p&gt;Perhaps the next time a production procedure depends on somebody “just checking carefully,” we should ask a slightly different question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What would make the check impossible to perform without actually noticing what you're checking?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Japanese railway workers have been answering that question with one finger for generations.&lt;/p&gt;

&lt;p&gt;Most passengers never realize why.&lt;/p&gt;

&lt;p&gt;And that's exactly what makes the story worth noticing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources &amp;amp; Further Reading
&lt;/h2&gt;

&lt;p&gt;For the history and modern use of pointing and calling, Nippon.com provides a useful overview covering its development from early call-and-response railway practices, the addition of pointing in the late 1920s, formalization by Japanese National Railways and adoption beyond railways. (&lt;a href="https://www.nippon.com/en/japan-topics/g02575/?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Nippon&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The experimental evidence comes from Shigeru Haga, Hajime Akatsuka and Hiroaki Shiroto's 1996 laboratory study, &lt;em&gt;Laboratory experiment for verifying the effectiveness of “finger-pointing and call” as a practical tool of human error prevention&lt;/em&gt;. (&lt;a href="https://www.jstage.jst.go.jp/article/jaiop/9/2/9_107/_article/-char/en?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;J-STAGE&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The older origin story around locomotive engineer Yasoichi Hori is widely repeated, but contemporary reporting notes that the exact historical origin remains uncertain, which is why I have not treated that anecdote as established fact here. (&lt;a href="https://www.japantimes.co.jp/news/2008/10/21/reference/jr-gestures/?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;The Japan Times&lt;/a&gt;)&lt;/p&gt;




&lt;h2&gt;
  
  
  About the Person Who Somehow Ended Up Pointing at Production Servers
&lt;/h2&gt;

&lt;p&gt;I'm &lt;strong&gt;Amrishkhan Sheik Abdullah&lt;/strong&gt;, a Technical Lead and software architect based in Dubai. I build backend systems, microservices and distributed architectures for a living — which mostly means trying to design systems where humans don't have to remember twelve critically important things at exactly the worst possible moment.&lt;/p&gt;

&lt;p&gt;I write about software architecture, distributed systems, engineering decisions, AI developer tooling and, increasingly, ordinary-looking things that turn out to have surprisingly serious engineering hiding underneath them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who Saw the Connection?&lt;/strong&gt; is my series about exactly that: true stories where everyday behaviors, objects or decisions reveal an engineering idea worth stealing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read More of My Writing
&lt;/h3&gt;

&lt;p&gt;I publish longer engineering articles, architecture deep-dives and the occasional story that starts with a train driver pointing at a green light and somehow ends up inside a production deployment pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Writing:&lt;/strong&gt; &lt;a href="https://www.amrishkhan.dev/#blog" rel="noopener noreferrer"&gt;amrishkhan.dev/blog&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Elsewhere on the Internet
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Portfolio:&lt;/strong&gt; &lt;a href="https://www.amrishkhan.dev/" rel="noopener noreferrer"&gt;amrishkhan.dev&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/amrishkhan05" rel="noopener noreferrer"&gt;github.com/amrishkhan05&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;DEV Community:&lt;/strong&gt; &lt;a href="https://dev.to/amrishkhan05"&gt;dev.to/amrishkhan05&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/amrishkhan/" rel="noopener noreferrer"&gt;linkedin.com/in/amrishkhan&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you enjoyed this one, stick around. Apparently my current research strategy is to stare suspiciously at ordinary things until they confess why they were designed that way.&lt;/p&gt;

</description>
      <category>software</category>
      <category>mixedreality</category>
      <category>development</category>
      <category>programming</category>
    </item>
    <item>
      <title>What Did Heart Surgeons See in a Formula 1 Pit Stop?</title>
      <dc:creator>Amrishkhan Sheik Abdullah</dc:creator>
      <pubDate>Fri, 04 Sep 2026 06:11:00 +0000</pubDate>
      <link>https://dev.to/amrishkhan05/what-did-heart-surgeons-see-in-a-formula-1-pit-stop-4pf0</link>
      <guid>https://dev.to/amrishkhan05/what-did-heart-surgeons-see-in-a-formula-1-pit-stop-4pf0</guid>
      <description>&lt;h3&gt;
  
  
  Who Saw the Connection? #03 — Formula 1 × Pediatric Heart Surgery × Software
&lt;/h3&gt;

&lt;p&gt;In 2003, a group of doctors at Great Ormond Street Hospital in London found themselves watching Formula 1.&lt;/p&gt;

&lt;p&gt;They had spent the morning performing heart surgery on children, which is an important detail because what caught their attention wasn't the racing. It was what happened when the racing stopped.&lt;/p&gt;

&lt;p&gt;A car came into the pits and was immediately surrounded by mechanics. Different people performed different jobs, some simultaneously and others in a deliberate sequence. Nobody appeared to be negotiating responsibilities. Nobody wandered over to the front-left wheel halfway through and asked whether somebody had taken care of it.&lt;/p&gt;

&lt;p&gt;Seconds later, the car was gone.&lt;/p&gt;

&lt;p&gt;The doctors had spent their morning doing something obviously much more serious, but there was something familiar in what they were watching. Their patients also had to make a stop after surgery.&lt;/p&gt;

&lt;p&gt;And that stop was causing problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Operation Was Over. The Risk Wasn't.
&lt;/h2&gt;

&lt;p&gt;A child coming out of cardiac surgery at Great Ormond Street had to be transferred from the operating theatre to intensive care.&lt;/p&gt;

&lt;p&gt;Calling this a “transfer” makes it sound considerably simpler than it was.&lt;/p&gt;

&lt;p&gt;The patient could be connected to a ventilator, monitoring equipment, intravenous lines and several drug infusions. During the move, some equipment had to be disconnected from theatre systems, transferred to portable equipment and connected again once the child reached intensive care.&lt;/p&gt;

&lt;p&gt;At roughly the same time, something less visible had to move: information.&lt;/p&gt;

&lt;p&gt;The ICU team needed to know what had happened during surgery, what medication the child was receiving, whether there had been complications, what the surgeons were concerned about and what needed particular attention during recovery. Somewhere during all of this, responsibility for the patient changed as well. The operating team had owned the case; now the intensive-care team did.&lt;/p&gt;

&lt;p&gt;So several highly trained people were moving equipment, information and responsibility around the same vulnerable patient under time pressure.&lt;/p&gt;

&lt;p&gt;The individual clinicians knew their jobs.&lt;/p&gt;

&lt;p&gt;The transition between them was less reliable.&lt;/p&gt;

&lt;p&gt;That was what the doctors recognized while watching Formula 1.&lt;/p&gt;

&lt;h2&gt;
  
  
  So They Went to See How the Pit Crew Did It
&lt;/h2&gt;

&lt;p&gt;The story is often compressed into a much better headline:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Doctors copied Ferrari pit stops.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's memorable. It also skips most of what makes the story useful.&lt;/p&gt;

&lt;p&gt;The Great Ormond Street team didn't watch a race, return to the hospital on Monday and arrange the nurses around the bed like tyre mechanics. They began investigating how Formula 1 teams actually organized pit stops.&lt;/p&gt;

&lt;p&gt;The medical team interacted with people from Formula 1, including Ferrari. Contemporary accounts describe clinicians observing pit-stop practice, and the academic paper that eventually came from the work acknowledges Ferrari technical manager Nigel Stepney for his contribution.&lt;/p&gt;

&lt;p&gt;What interested the doctors wasn't how quickly somebody could remove a wheel. They were interested in everything that made that speed possible.&lt;/p&gt;

&lt;p&gt;Responsibilities had been decided before the car arrived. People knew where they were supposed to be. Tasks followed a sequence, with some happening in parallel without turning into random parallelism. There was clear coordination, and the same transition had been practiced repeatedly. Crucially, somebody knew when the operation was complete and the car could safely leave.&lt;/p&gt;

&lt;p&gt;The doctors compared that with their own handovers.&lt;/p&gt;

&lt;p&gt;Several conversations might be happening while equipment was being moved. Information transfer and technical work could overlap. Leadership wasn't always obvious. People interrupted one another because the workflow itself required a surprising amount of live coordination.&lt;/p&gt;

&lt;p&gt;Nobody involved was incompetent.&lt;/p&gt;

&lt;p&gt;That was precisely the interesting part.&lt;/p&gt;

&lt;p&gt;The process was making competent people solve coordination problems while they were already doing critical work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ferrari Wasn't the Only Place They Looked
&lt;/h2&gt;

&lt;p&gt;This part tends to disappear from the popular version of the story.&lt;/p&gt;

&lt;p&gt;The redesigned handover didn't come purely from Formula 1. The Great Ormond Street team also worked with aviation training specialists.&lt;/p&gt;

&lt;p&gt;That makes sense once you stop thinking about industries and start thinking about failure modes.&lt;/p&gt;

&lt;p&gt;Aviation had spent decades dealing with highly trained people operating complicated systems where unclear communication, ambiguous responsibility and small human mistakes could become disproportionately expensive. Formula 1 had developed an extraordinary approach to choreographing parallel work around an expensive machine under severe time pressure.&lt;/p&gt;

&lt;p&gt;The hospital had to decide which parts of either world made sense around a child recovering from heart surgery.&lt;/p&gt;

&lt;p&gt;The result wasn't a Ferrari pit stop transplanted into an ICU. It was a hospital handover protocol informed by Formula 1, aviation and clinical experience.&lt;/p&gt;

&lt;p&gt;That distinction matters because cross-industry innovation is often described as copying an idea from somewhere else. In practice, figuring out &lt;strong&gt;what not to copy&lt;/strong&gt; may require more judgment.&lt;/p&gt;

&lt;p&gt;At one point, for example, the team considered having clinicians stand in predefined positions around the patient, similar to mechanics around a Formula 1 car.&lt;/p&gt;

&lt;p&gt;They didn't keep that part.&lt;/p&gt;

&lt;p&gt;Apparently there is a limit to how much Ferrari an ICU requires.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Changed?
&lt;/h2&gt;

&lt;p&gt;The handover became more structured, but not dramatically more complicated.&lt;/p&gt;

&lt;p&gt;Rather than allowing technical work and information exchange to occur as one overlapping activity, the redesigned process separated the transition into clearer phases. Immediate technical work around connecting and stabilizing the patient happened first. Information transfer followed once that activity had settled.&lt;/p&gt;

&lt;p&gt;Roles became more explicit, as did leadership. The anesthetist took a coordinating role during the transfer, giving the process a clearer point of control instead of leaving several specialists to independently decide when the transition had reached a safe state.&lt;/p&gt;

&lt;p&gt;This is an easy detail to overlook.&lt;/p&gt;

&lt;p&gt;The intervention wasn't trying to make clinicians work harder or faster. It was removing decisions that didn't need to be made in the middle of the handover.&lt;/p&gt;

&lt;p&gt;Then the team did something that separates this story from the large pile of business anecdotes that survive primarily because they make good conference slides.&lt;/p&gt;

&lt;p&gt;They measured it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fifty Handovers
&lt;/h2&gt;

&lt;p&gt;Researchers observed 50 postoperative handovers: 23 before the new protocol and 27 after it was introduced.&lt;/p&gt;

&lt;p&gt;They recorded technical errors, information omissions, teamwork and the time required for the handover.&lt;/p&gt;

&lt;p&gt;Before the intervention, the mean number of technical errors per handover was 5.42. Afterward, it was 3.15.&lt;/p&gt;

&lt;p&gt;Information omissions fell from an average of 2.09 to 1.07.&lt;/p&gt;

&lt;p&gt;Average handover duration also went from 10.8 minutes to 9.4 minutes.&lt;/p&gt;

&lt;p&gt;There was another result I find particularly useful. Before the intervention, 9 of the 23 observed patients experienced more than one technical error &lt;strong&gt;and&lt;/strong&gt; more than one information error during handover. After the intervention, that happened in 3 of 27 cases.&lt;/p&gt;

&lt;p&gt;Those numbers need some restraint.&lt;/p&gt;

&lt;p&gt;This was a relatively small prospective intervention study. It didn't demonstrate that Formula 1 techniques reduced mortality, and it certainly didn't establish that every hospital should redesign itself around motorsport.&lt;/p&gt;

&lt;p&gt;What it showed was narrower and more useful: in this setting, a deliberately redesigned handover reduced measured technical errors and information omissions without making the transfer slower.&lt;/p&gt;

&lt;p&gt;That's already interesting enough.&lt;/p&gt;

&lt;p&gt;The story doesn't need us to upgrade the evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pit Stop Wasn't Really About Speed
&lt;/h2&gt;

&lt;p&gt;Formula 1 makes this story easy to misunderstand because pit stops are famous for being fast.&lt;/p&gt;

&lt;p&gt;But speed wasn't the useful thing the hospital borrowed.&lt;/p&gt;

&lt;p&gt;A pit stop is fast partly because very little needs to be decided after the car arrives.&lt;/p&gt;

&lt;p&gt;Imagine doing it differently. The car stops and a dozen people walk over. Someone asks who wants tyres. Two people reach for the same wheel. Someone discovers nobody brought the replacement front wing.&lt;/p&gt;

&lt;p&gt;Eventually the driver asks for an update in Teams.&lt;/p&gt;

&lt;p&gt;It would be a terrible pit stop, although a surprisingly recognizable software project.&lt;/p&gt;

&lt;p&gt;The choreography removes decisions from the moment when those decisions are most expensive.&lt;/p&gt;

&lt;p&gt;That's what makes the hospital story interesting to me. The team wasn't simply standardizing tasks; it was moving coordination work out of the critical path.&lt;/p&gt;

&lt;p&gt;Once I started looking at it that way, the connection to software became difficult to ignore.&lt;/p&gt;

&lt;p&gt;Not because an API is somehow comparable to a child leaving heart surgery. Obviously it isn't.&lt;/p&gt;

&lt;p&gt;But our systems contain plenty of transitions where several facts need to remain true while responsibility moves from one component to another. Those transitions have a habit of producing failures even when the components on either side are behaving correctly.&lt;/p&gt;

&lt;p&gt;Payments are particularly good at demonstrating this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Customer Has Been Charged. Your System Thinks They Haven't.
&lt;/h2&gt;

&lt;p&gt;Imagine an order service sending a payment request to a provider.&lt;/p&gt;

&lt;p&gt;The provider receives it, processes the transaction and charges the card. Before the response reaches us, the connection dies.&lt;/p&gt;

&lt;p&gt;Maybe there was a brief network interruption. Maybe our timeout was slightly aggressive. It doesn't matter yet.&lt;/p&gt;

&lt;p&gt;Our service sees:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ETIMEDOUT&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The provider sees:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SUCCESS&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The payment provider did its job, and our service behaved exactly according to its timeout policy. Unfortunately, the customer has now been charged while our application is preparing to tell them that the payment failed.&lt;/p&gt;

&lt;p&gt;This is the sort of failure that makes distributed systems interesting in ways nobody requested.&lt;/p&gt;

&lt;p&gt;The obvious response is to retry.&lt;/p&gt;

&lt;p&gt;But retry what?&lt;/p&gt;

&lt;p&gt;If we send another payment request and the provider treats it as a new transaction, the customer may be charged twice. The retry therefore has to represent the same business operation rather than “please charge this card again.”&lt;/p&gt;

&lt;p&gt;Now idempotency matters.&lt;/p&gt;

&lt;p&gt;We also need some identifier that both systems understand so we can ask the provider what happened. Perhaps the provider sends a webhook later confirming the transaction. That webhook needs to correlate with the order we created earlier.&lt;/p&gt;

&lt;p&gt;Then there is the uncomfortable question of what state our order occupies while we don't know the outcome.&lt;/p&gt;

&lt;p&gt;Not &lt;code&gt;FAILED&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We don't know that.&lt;/p&gt;

&lt;p&gt;Not &lt;code&gt;PAID&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We don't know that either.&lt;/p&gt;

&lt;p&gt;The truthful state might be &lt;code&gt;PAYMENT_PENDING_CONFIRMATION&lt;/code&gt;, or whatever equally unattractive name your domain model eventually settles on.&lt;/p&gt;

&lt;p&gt;“Unknown” is irritating, but it is a legitimate distributed-systems state. Pretending otherwise doesn't remove the uncertainty.&lt;/p&gt;

&lt;p&gt;It usually just moves it into customer support.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Handoff Is Where Things Become Expensive
&lt;/h2&gt;

&lt;p&gt;Look closely at what failed in that payment example.&lt;/p&gt;

&lt;p&gt;Neither participant necessarily malfunctioned.&lt;/p&gt;

&lt;p&gt;The trouble appeared while the outcome of an operation was being transferred between them.&lt;/p&gt;

&lt;p&gt;That's why the boring mechanisms around integrations matter.&lt;/p&gt;

&lt;p&gt;Idempotency keys aren't there because architects enjoy adding headers. Correlation IDs shouldn't exist merely to make logs look enterprise-ready. Status endpoints, webhooks and reconciliation jobs can look like several ways of solving the same problem until a synchronous response disappears after the provider has already changed the real world.&lt;/p&gt;

&lt;p&gt;At that point the question becomes very simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who knows what happened, and how do we prove it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's surprisingly close to the problem Great Ormond Street was working on.&lt;/p&gt;

&lt;p&gt;During a clinical handover, it wasn't enough for the operating team to know what had happened. The receiving team needed the relevant information in a form they could act on while responsibility for the patient changed.&lt;/p&gt;

&lt;p&gt;In software we tend to hide all of this behind an arrow:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Order Service → Payment Provider&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That arrow looks remarkably confident.&lt;/p&gt;

&lt;p&gt;It doesn't show the timeout, the retry or the possibility that the first request actually succeeded. It doesn't show the webhook arriving three minutes later.&lt;/p&gt;

&lt;p&gt;And it definitely doesn't show the engineer searching production logs using a transaction ID copied from a customer-support screenshot.&lt;/p&gt;

&lt;p&gt;Architecture diagrams are optimistic documents.&lt;/p&gt;

&lt;p&gt;Production tends to add the footnotes.&lt;/p&gt;

&lt;h2&gt;
  
  
  We Spend Much More Time Designing the Boxes
&lt;/h2&gt;

&lt;p&gt;I've noticed this repeatedly in architecture discussions.&lt;/p&gt;

&lt;p&gt;We'll spend a considerable amount of time deciding whether something deserves to be its own service. We'll discuss what owns the data, which database it should use, whether it needs a cache and how it will scale.&lt;/p&gt;

&lt;p&gt;All reasonable questions.&lt;/p&gt;

&lt;p&gt;Then somebody draws an arrow to another service and labels it &lt;code&gt;HTTP&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Done.&lt;/p&gt;

&lt;p&gt;Except the arrow represents a protocol between two systems capable of failing independently.&lt;/p&gt;

&lt;p&gt;What happens when the call times out? Can we retry safely? What if execution succeeded but the acknowledgement disappeared? How do both sides identify the same business operation? Who owns recovery if the outcome remains uncertain for ten minutes?&lt;/p&gt;

&lt;p&gt;And eventually there is the question most diagrams politely avoid: when does the machine stop trying and hand the problem to a human?&lt;/p&gt;

&lt;p&gt;Those aren't edge details around the architecture.&lt;/p&gt;

&lt;p&gt;They are the handoff design.&lt;/p&gt;

&lt;p&gt;A beautifully engineered Order Service and an equally beautiful Payment Service can still disagree about whether somebody has paid.&lt;/p&gt;

&lt;p&gt;The boxes aren't always where the interesting failure lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lesson Isn't “Run Your Team Like Ferrari”
&lt;/h2&gt;

&lt;p&gt;I should probably say this explicitly before someone introduces racing helmets to sprint planning.&lt;/p&gt;

&lt;p&gt;Formula 1 isn't a universal operating model, and neither aviation nor a pediatric ICU should be treated as one.&lt;/p&gt;

&lt;p&gt;Great Ormond Street didn't do that.&lt;/p&gt;

&lt;p&gt;The team had a specific problem: a complicated transition between groups where technical work, information and responsibility all had to move safely.&lt;/p&gt;

&lt;p&gt;They found another environment that had become unusually good at coordinating a complicated transition under pressure and studied how it worked. Some ideas transferred and others didn't. Aviation contributed additional thinking. Clinical reality determined what survived.&lt;/p&gt;

&lt;p&gt;Then they measured the result.&lt;/p&gt;

&lt;p&gt;That sequence is much more valuable than the generic instruction to “look outside your industry.”&lt;/p&gt;

&lt;p&gt;Looking outside your industry is easy. Recognizing &lt;strong&gt;what you're actually looking at&lt;/strong&gt; is harder.&lt;/p&gt;

&lt;p&gt;A Formula 1 pit stop and a postoperative cardiac handover don't resemble each other if you classify them by industry. On the surface, a racetrack full of mechanics has almost nothing in common with clinicians transferring a child into intensive care.&lt;/p&gt;

&lt;p&gt;They become related only when you stop describing the setting and describe the problem.&lt;/p&gt;

&lt;p&gt;Both involve something valuable arriving in the middle of a specialist team while several tasks need to happen around it. Some work can happen concurrently, other work depends on a particular sequence, information has to survive the transition, and responsibility eventually needs to become unambiguous.&lt;/p&gt;

&lt;p&gt;Once you describe the problem that way, the connection isn't particularly strange.&lt;/p&gt;

&lt;p&gt;What's remarkable is that somebody saw it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Saw the Connection?
&lt;/h2&gt;

&lt;p&gt;That's the part of this story that stays with me.&lt;/p&gt;

&lt;p&gt;What interests me most isn't Ferrari or even the improvement in the numbers. It's the moment before any of that happened: a group of clinicians watched a pit stop and recognized their own problem inside it.&lt;/p&gt;

&lt;p&gt;On the surface, almost everything was wrong for the analogy. One involved mechanics trying to return a racing car to a circuit; the other involved clinicians transferring a child who had just undergone heart surgery into intensive care.&lt;/p&gt;

&lt;p&gt;But the coordination problem underneath was familiar.&lt;/p&gt;

&lt;p&gt;Once they saw that, they could ask a much better question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What has Formula 1 learned about this problem that we haven't?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Software engineering could probably ask that question more often.&lt;/p&gt;

&lt;p&gt;Our terminology has a habit of making our problems feel unique. Distributed transactions, service boundaries and event-driven workflows sound like subjects that belong exclusively inside architecture meetings.&lt;/p&gt;

&lt;p&gt;Other industries won't necessarily use those words. That doesn't mean they haven't spent decades dealing with unreliable handoffs, incomplete information, ambiguous ownership, time pressure and humans coordinating around complicated systems.&lt;/p&gt;

&lt;p&gt;Sometimes the useful engineering paper isn't an engineering paper.&lt;/p&gt;

&lt;p&gt;Sometimes it's a hospital studying Ferrari.&lt;/p&gt;

&lt;p&gt;So the next time I'm staring at an architecture diagram full of carefully designed boxes and suspiciously innocent arrows, I'll probably remember that a group of heart surgeons once watched a Formula 1 pit stop and saw something everyone else was watching straight through.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The interesting question was never what they were watching. It was what they saw.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>mixedreality</category>
      <category>development</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The Shortest Route Is Sometimes the Slowest: What UPS Can Teach Software Engineers About Optimization</title>
      <dc:creator>Amrishkhan Sheik Abdullah</dc:creator>
      <pubDate>Thu, 03 Sep 2026 06:10:00 +0000</pubDate>
      <link>https://dev.to/amrishkhan05/the-shortest-route-is-sometimes-the-slowest-what-ups-can-teach-software-engineers-about-55fm</link>
      <guid>https://dev.to/amrishkhan05/the-shortest-route-is-sometimes-the-slowest-what-ups-can-teach-software-engineers-about-55fm</guid>
      <description>&lt;p&gt;Three right turns can be faster than one left turn.&lt;/p&gt;

&lt;p&gt;That sentence annoyed me the first time I properly thought about it.&lt;/p&gt;

&lt;p&gt;Not because it's difficult to understand. Quite the opposite. It sounds suspiciously like one of those business anecdotes that survives because it's memorable and nobody bothers checking whether it's actually true.&lt;/p&gt;

&lt;p&gt;UPS trucks don't turn left.&lt;/p&gt;

&lt;p&gt;Great story.&lt;/p&gt;

&lt;p&gt;Also not true.&lt;/p&gt;

&lt;p&gt;UPS trucks absolutely turn left. What UPS has done for years is try to reduce unnecessary left turns in countries where vehicles drive on the right.&lt;/p&gt;

&lt;p&gt;The distinction is important.&lt;/p&gt;

&lt;p&gt;A left turn across opposing traffic can leave a truck sitting at an intersection waiting for a gap. Sometimes that's a few seconds. Sometimes it means waiting through a signal cycle. The vehicle is consuming time and fuel while making precisely zero progress, and crossing traffic adds another source of accident risk.&lt;/p&gt;

&lt;p&gt;For one truck, on one Tuesday afternoon, this is almost boring.&lt;/p&gt;

&lt;p&gt;UPS doesn't operate one truck.&lt;/p&gt;

&lt;p&gt;And that's where the arithmetic gets interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shortest Route Has a Few Missing Columns
&lt;/h2&gt;

&lt;p&gt;Imagine two routes to the same delivery.&lt;/p&gt;

&lt;p&gt;One is 2.8 miles.&lt;/p&gt;

&lt;p&gt;The other is 3.1.&lt;/p&gt;

&lt;p&gt;If those are the only numbers available, there isn't much of a decision to make. Take the 2.8-mile route.&lt;/p&gt;

&lt;p&gt;Now add two awkward intersections to the shorter route. Perhaps the truck needs to cross several lanes of opposing traffic. Perhaps one turn regularly takes 40 seconds during the delivery window.&lt;/p&gt;

&lt;p&gt;The extra 0.3 miles no longer looks obviously wasteful.&lt;/p&gt;

&lt;p&gt;Nothing about the distance changed. We simply started accounting for more of the cost.&lt;/p&gt;

&lt;p&gt;UPS eventually turned this class of problem into ORION: On-Road Integrated Optimization and Navigation.&lt;/p&gt;

&lt;p&gt;Calling ORION a route-planning system is accurate, although slightly underselling the problem. UPS needed to calculate useful routes for tens of thousands of drivers making large numbers of stops while respecting delivery commitments, pickup windows, road conditions and the awkward realities of getting a large brown truck to the correct side of a building.&lt;/p&gt;

&lt;p&gt;The mathematics gets ugly quickly.&lt;/p&gt;

&lt;p&gt;A driver with around 120 stops doesn't have 120 possible routes. The number of possible sequences is astronomically larger. You can't enumerate everything, find the perfect route and go home early.&lt;/p&gt;

&lt;p&gt;And even a mathematically attractive route can be operationally ridiculous.&lt;/p&gt;

&lt;p&gt;An address might technically be on a road while the receiving dock is around the back. A route that works nicely at 9 AM may become painful when school traffic appears. A particular sequence may force the driver to cross the same busy road repeatedly.&lt;/p&gt;

&lt;p&gt;This is what makes ORION more interesting than the left-turn trivia.&lt;/p&gt;

&lt;p&gt;UPS wasn't solving geometry.&lt;/p&gt;

&lt;p&gt;It was trying to model reality closely enough to make better decisions inside it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six Miles Doesn't Sound Like Much
&lt;/h2&gt;

&lt;p&gt;During ORION's rollout, UPS reported reducing routes by roughly six to eight miles in areas where the system had been deployed.&lt;/p&gt;

&lt;p&gt;Six miles.&lt;/p&gt;

&lt;p&gt;If somebody told you they had spent years developing algorithms, collecting location data, changing driver workflows and deploying a large optimization platform so one vehicle could drive six fewer miles, you might reasonably wonder whether the project had escaped from a PowerPoint presentation.&lt;/p&gt;

&lt;p&gt;Then multiply it.&lt;/p&gt;

&lt;p&gt;UPS projected that full deployment could eliminate roughly 100 million miles of driving and save around 10 million gallons of fuel annually.&lt;/p&gt;

&lt;p&gt;That's the part of the story I like.&lt;/p&gt;

&lt;p&gt;There isn't one spectacular optimization hiding somewhere.&lt;/p&gt;

&lt;p&gt;One truck saves a little.&lt;/p&gt;

&lt;p&gt;Then another does.&lt;/p&gt;

&lt;p&gt;Then tens of thousands of trucks do it again tomorrow.&lt;/p&gt;

&lt;p&gt;Software engineering has a weakness for dramatic optimization. Replace the database. Rewrite the service in Rust. Introduce a new platform. Split the monolith. Announce an architecture initiative with a name that sounds faintly military.&lt;/p&gt;

&lt;p&gt;Those things are occasionally exactly what a system needs.&lt;/p&gt;

&lt;p&gt;But high-volume systems also contain much less glamorous opportunities.&lt;/p&gt;

&lt;p&gt;Remove a small allocation from a path executed hundreds of millions of times. Avoid serializing data nobody reads. Eliminate one unnecessary network round trip from a hot request. Improve a cache hit rate slightly on something called constantly.&lt;/p&gt;

&lt;p&gt;None of these makes an exciting conference keynote.&lt;/p&gt;

&lt;p&gt;At sufficient scale, they don't need to.&lt;/p&gt;

&lt;p&gt;And this was roughly where the UPS story started bothering the software engineer in me.&lt;/p&gt;

&lt;h2&gt;
  
  
  We Do This Constantly
&lt;/h2&gt;

&lt;p&gt;Suppose I put two backend designs in front of you.&lt;/p&gt;

&lt;p&gt;The first makes three network calls.&lt;/p&gt;

&lt;p&gt;The second makes five.&lt;/p&gt;

&lt;p&gt;Which one is faster?&lt;/p&gt;

&lt;p&gt;Three feels like the sensible answer.&lt;/p&gt;

&lt;p&gt;But suppose those three calls are sequential and one goes to an external provider. The five calls are internal, independent and executed concurrently.&lt;/p&gt;

&lt;p&gt;Now I haven't given you enough information.&lt;/p&gt;

&lt;p&gt;We need to know actual latency, whether connections are reused, failure rates, retry behaviour, rate limits, payload sizes and what those dependencies look like when they're having a bad afternoon.&lt;/p&gt;

&lt;p&gt;Counting calls wasn't useless. It just wasn't enough.&lt;/p&gt;

&lt;p&gt;Microservice diagrams make this particularly easy to forget because every dependency becomes an arrow.&lt;/p&gt;

&lt;p&gt;An API calling Redis and an API calling a third-party payment provider may each occupy roughly the same amount of ink.&lt;/p&gt;

&lt;p&gt;Operationally, they're very different dependencies.&lt;/p&gt;

&lt;p&gt;The external call crosses infrastructure you don't own. It can time out. It may be rate-limited. Its latency distribution can change without your deployment changing at all. When it fails, retries have to be designed carefully because an unhealthy dependency doesn't usually recover faster when all of its customers enthusiastically send it additional traffic.&lt;/p&gt;

&lt;p&gt;“Three calls instead of five” tells me almost nothing about that.&lt;/p&gt;

&lt;p&gt;Yet architecture discussions regularly treat these counts as evidence.&lt;/p&gt;

&lt;p&gt;We do the same thing with services, queues, database queries and deployment steps because they're visible and easy to count.&lt;/p&gt;

&lt;p&gt;Easy to count is not the same thing as expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Favourite Version Is “We Made It One Query”
&lt;/h2&gt;

&lt;p&gt;Database optimization produces a particularly good version of this mistake.&lt;/p&gt;

&lt;p&gt;An endpoint makes six queries.&lt;/p&gt;

&lt;p&gt;Someone notices.&lt;/p&gt;

&lt;p&gt;“We can make this one query.”&lt;/p&gt;

&lt;p&gt;Possibly.&lt;/p&gt;

&lt;p&gt;And sometimes that's a very good change.&lt;/p&gt;

&lt;p&gt;Other times the result is a magnificent SQL statement with enough joins, CTEs, aggregations and conditional logic that &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; starts looking like a genealogy project.&lt;/p&gt;

&lt;p&gt;Still, six became one.&lt;/p&gt;

&lt;p&gt;The dashboard in our heads turns green.&lt;/p&gt;

&lt;p&gt;The problem is that databases don't invoice us by query count.&lt;/p&gt;

&lt;p&gt;Six small indexed lookups can be perfectly reasonable. One badly planned query over large tables can be disastrous. The opposite can also be true: six network round trips may be unnecessary when the database could efficiently do the work once.&lt;/p&gt;

&lt;p&gt;You have to look.&lt;/p&gt;

&lt;p&gt;Execution plans matter. Cardinality matters. Index selectivity matters. Data volume matters. Locking matters. Round-trip latency matters. What happens when twenty requests execute the same query concurrently matters quite a lot.&lt;/p&gt;

&lt;p&gt;This sounds obvious when written down.&lt;/p&gt;

&lt;p&gt;It becomes surprisingly non-obvious during optimization work because reducing a visible number feels like progress.&lt;/p&gt;

&lt;p&gt;I've become suspicious of improvements whose strongest argument is that one number got smaller.&lt;/p&gt;

&lt;p&gt;Not dismissive. Suspicious.&lt;/p&gt;

&lt;p&gt;There is a difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  The One-Arrow Architecture
&lt;/h2&gt;

&lt;p&gt;The same problem gets more expensive once services start depending on each other.&lt;/p&gt;

&lt;p&gt;Service A needs information owned by Service B.&lt;/p&gt;

&lt;p&gt;The straightforward implementation is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Service A → Service B&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Done.&lt;/p&gt;

&lt;p&gt;One dependency. Easy to explain. Easy to draw.&lt;/p&gt;

&lt;p&gt;Then B becomes slow.&lt;/p&gt;

&lt;p&gt;Now A becomes slow.&lt;/p&gt;

&lt;p&gt;B occasionally becomes unavailable, so A starts failing too. We add retries because optimism is apparently an infrastructure pattern.&lt;/p&gt;

&lt;p&gt;The retries help with transient failures until B experiences a larger incident, at which point several callers simultaneously decide that the struggling service would benefit from additional requests.&lt;/p&gt;

&lt;p&gt;So we add exponential backoff. Maybe circuit breaking. Perhaps caching. We have to think about timeouts properly. Suddenly our one-arrow architecture contains quite a lot of behaviour that isn't visible in the arrow.&lt;/p&gt;

&lt;p&gt;There may be another design.&lt;/p&gt;

&lt;p&gt;B publishes relevant changes asynchronously. A consumes them and keeps the small piece of state it needs locally.&lt;/p&gt;

&lt;p&gt;The request path becomes independent of B, but we've paid for that independence elsewhere. There is a broker now. Event schemas need ownership. Consumers need monitoring. Replay needs to work. Someone has to decide what eventual consistency means for the business operation rather than merely writing the phrase in an architecture document and hoping everybody finds it reassuring.&lt;/p&gt;

&lt;p&gt;This is not automatically better.&lt;/p&gt;

&lt;p&gt;If the information must be strongly consistent, the asynchronous design may be wrong. If A requests the data twice a week, it is almost certainly unnecessary.&lt;/p&gt;

&lt;p&gt;We should not deploy Kafka every time two services make eye contact.&lt;/p&gt;

&lt;p&gt;But there are workloads where the design containing more components produces a simpler runtime failure model.&lt;/p&gt;

&lt;p&gt;That's a difficult trade-off to see if “fewer boxes” is being used as a proxy for simplicity.&lt;/p&gt;

&lt;p&gt;Some complexity comes from our architecture.&lt;/p&gt;

&lt;p&gt;Some complexity comes from the problem.&lt;/p&gt;

&lt;p&gt;Deleting the former is useful. Pretending the latter disappeared because we removed a box from a diagram tends to end less well.&lt;/p&gt;

&lt;h2&gt;
  
  
  ORION Had Another Input: Drivers
&lt;/h2&gt;

&lt;p&gt;One part of the ORION story tends to disappear when it gets reduced to “UPS avoids left turns.”&lt;/p&gt;

&lt;p&gt;The drivers already knew a lot.&lt;/p&gt;

&lt;p&gt;A driver who has served the same area for years knows things that are difficult to infer from an address database. Which entrance actually works. Where the receiving dock is. Which road becomes unpleasant at a particular hour. Where turning around a large vehicle is technically possible but strategically regrettable.&lt;/p&gt;

&lt;p&gt;Meanwhile, an optimization system can evaluate combinations and patterns that no driver could reasonably calculate during a shift.&lt;/p&gt;

&lt;p&gt;UPS spent years field-testing ORION rather than simply dropping an algorithm into trucks and declaring mathematics victorious. Its navigation systems became increasingly precise about actual delivery and pickup locations.&lt;/p&gt;

&lt;p&gt;That feels familiar.&lt;/p&gt;

&lt;p&gt;The first version of an architecture is based on what we know about the system before operating it.&lt;/p&gt;

&lt;p&gt;Then production contributes its review comments.&lt;/p&gt;

&lt;p&gt;A retry policy behaves differently under a real outage. A cache introduces an invalidation problem nobody cared about in the prototype. A dependency with perfectly acceptable average latency develops a horrible tail. A queue absorbs traffic beautifully until the consumer falls behind for six hours and recovery becomes the real problem.&lt;/p&gt;

&lt;p&gt;This doesn't necessarily mean the design was bad.&lt;/p&gt;

&lt;p&gt;It means production supplied information the original model didn't contain.&lt;/p&gt;

&lt;p&gt;The important part is what happens next.&lt;/p&gt;

&lt;p&gt;If measurements disagree with our assumptions, the assumptions don't get seniority.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fast Service That Takes Four Seconds
&lt;/h2&gt;

&lt;p&gt;Latency is probably where software engineers encounter this problem most often.&lt;/p&gt;

&lt;p&gt;Imagine two services.&lt;/p&gt;

&lt;p&gt;The first usually responds in 80 ms but occasionally takes four seconds.&lt;/p&gt;

&lt;p&gt;The second tends to respond around 130 ms and rarely exceeds 220 ms.&lt;/p&gt;

&lt;p&gt;Which one would you rather put in a checkout path?&lt;/p&gt;

&lt;p&gt;There isn't a universal answer, but I know which set of graphs I'd want to inspect before choosing.&lt;/p&gt;

&lt;p&gt;Average latency can make the first service look fantastic. Median latency might too.&lt;/p&gt;

&lt;p&gt;Customers eventually meet the tail.&lt;/p&gt;

&lt;p&gt;It gets worse when requests compose several dependencies. Your endpoint can be individually well behaved while inheriting somebody else's P99, and enough downstream calls give a request several opportunities to encounter an outlier.&lt;/p&gt;

&lt;p&gt;This is why I find optimizations focused entirely on averages increasingly difficult to trust.&lt;/p&gt;

&lt;p&gt;Sometimes predictability is worth paying a little latency for.&lt;/p&gt;

&lt;p&gt;The requirement isn't always “make the fastest request faster.”&lt;/p&gt;

&lt;p&gt;Quite often it's “make the bad requests stop being terrible.”&lt;/p&gt;

&lt;p&gt;Those are different engineering projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  So What Are We Actually Optimizing?
&lt;/h2&gt;

&lt;p&gt;This question should probably appear earlier in more performance discussions.&lt;/p&gt;

&lt;p&gt;Someone says:&lt;/p&gt;

&lt;p&gt;“We need to optimize this.”&lt;/p&gt;

&lt;p&gt;Okay.&lt;/p&gt;

&lt;p&gt;What is expensive?&lt;/p&gt;

&lt;p&gt;CPU? Database time? Network latency? Cloud spend? Failure rate? Human intervention? Customer waiting time?&lt;/p&gt;

&lt;p&gt;Those can point toward completely different changes, and improving one can make another worse.&lt;/p&gt;

&lt;p&gt;A cache might reduce latency while introducing stale-data behaviour.&lt;/p&gt;

&lt;p&gt;Parallel calls can reduce response time while increasing instantaneous pressure on downstream systems.&lt;/p&gt;

&lt;p&gt;More replicas improve capacity and resilience while costing money.&lt;/p&gt;

&lt;p&gt;A queue can isolate failures but adds another operational system and changes when work becomes visible to the caller.&lt;/p&gt;

&lt;p&gt;None of this is a reason to avoid optimization. It's a reason to be precise about what success means before changing the architecture.&lt;/p&gt;

&lt;p&gt;UPS could have optimized purely for mileage.&lt;/p&gt;

&lt;p&gt;It didn't, because a delivery operation doesn't make money by winning a shortest-distance competition.&lt;/p&gt;

&lt;p&gt;Software systems don't either.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sometimes the Longer Route Is Just Longer
&lt;/h2&gt;

&lt;p&gt;There is a danger in getting too enthusiastic about this idea.&lt;/p&gt;

&lt;p&gt;Once engineers discover that the obvious optimization can be wrong, we occasionally become suspicious of anything simple.&lt;/p&gt;

&lt;p&gt;That's how a service that needs to store three settings ends up with Kafka, Redis, CQRS and an architectural decision record explaining its “future scalability posture.”&lt;/p&gt;

&lt;p&gt;Sometimes the shortest path really is best.&lt;/p&gt;

&lt;p&gt;Sometimes one query is better than six.&lt;/p&gt;

&lt;p&gt;Sometimes removing a service reduces both latency and operational burden.&lt;/p&gt;

&lt;p&gt;And sometimes the left turn is completely empty and you should just turn left.&lt;/p&gt;

&lt;p&gt;The point isn't to prefer complicated routes.&lt;/p&gt;

&lt;p&gt;It's to stop assuming that distance and cost are interchangeable.&lt;/p&gt;

&lt;p&gt;Before optimizing something, I now want to understand three things reasonably well: what we're paying for, how often we're paying for it, and what the proposed improvement makes worse.&lt;/p&gt;

&lt;p&gt;That usually gets us much further than starting with “How do we reduce the number of X?”&lt;/p&gt;

&lt;p&gt;Especially when X happens to be the easiest thing on the dashboard to count.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Part Worth Stealing From UPS
&lt;/h2&gt;

&lt;p&gt;The famous version of the UPS story is wonderfully simple:&lt;/p&gt;

&lt;p&gt;Avoid left turns.&lt;/p&gt;

&lt;p&gt;I don't think that's the useful version.&lt;/p&gt;

&lt;p&gt;The useful version is that UPS spent years figuring out what a mile actually costs inside its operation.&lt;/p&gt;

&lt;p&gt;Not theoretically. Operationally.&lt;/p&gt;

&lt;p&gt;A mile driven at speed isn't equivalent to a mile spent crawling through traffic. A short route with awkward intersections isn't necessarily cheaper than a slightly longer one. A mathematically elegant sequence isn't particularly valuable if the driver can't execute it efficiently.&lt;/p&gt;

&lt;p&gt;Once you see the problem that way, the left-turn rule stops looking quirky. It's simply one consequence of having a better understanding of cost.&lt;/p&gt;

&lt;p&gt;That's the part worth carrying into software.&lt;/p&gt;

&lt;p&gt;We spend a lot of time making systems smaller on paper.&lt;/p&gt;

&lt;p&gt;Fewer calls. Fewer queries. Fewer services. Fewer components.&lt;/p&gt;

&lt;p&gt;Often that's exactly the right thing to do. Simplicity deserves a strong default.&lt;/p&gt;

&lt;p&gt;But the number getting smaller isn't proof that the system got better.&lt;/p&gt;

&lt;p&gt;A network call disappears, but perhaps the data now has to be synchronized somewhere else. Six queries become one, but perhaps that query behaves terribly once the table has 200 million rows. A service gets removed, but its responsibilities haven't vanished; they've just moved into another service that nobody wanted to make larger.&lt;/p&gt;

&lt;p&gt;The cost didn't disappear because the architecture diagram became prettier.&lt;/p&gt;

&lt;p&gt;And I think that's why the UPS story has stuck with me.&lt;/p&gt;

&lt;p&gt;It isn't really a story about left turns.&lt;/p&gt;

&lt;p&gt;It's about choosing the right thing to optimize.&lt;/p&gt;

&lt;p&gt;The shortest route is obvious when you're looking at a map. The better route only becomes obvious when you understand what actually happens while driving it.&lt;/p&gt;

&lt;p&gt;UPS figured out that driving a little farther can sometimes get you there faster.&lt;/p&gt;

&lt;p&gt;In software, we keep learning the same lesson.&lt;br&gt;
Usually right after deploying the shorter route to production!&lt;/p&gt;

</description>
      <category>mixedreality</category>
      <category>productivity</category>
      <category>learning</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Sometimes the Fastest System Is the One Willing to Stop</title>
      <dc:creator>Amrishkhan Sheik Abdullah</dc:creator>
      <pubDate>Wed, 02 Sep 2026 07:12:22 +0000</pubDate>
      <link>https://dev.to/amrishkhan05/sometimes-the-fastest-system-is-the-one-willing-to-stop-5a6a</link>
      <guid>https://dev.to/amrishkhan05/sometimes-the-fastest-system-is-the-one-willing-to-stop-5a6a</guid>
      <description>&lt;p&gt;There is a particular kind of meeting every engineer eventually attends.&lt;/p&gt;

&lt;p&gt;A metric is projected onto a screen. It has moved in the wrong direction. Someone asks why. Another person suggests we should make sure it never moves in that direction again.&lt;/p&gt;

&lt;p&gt;And, somewhere in the organization, a perfectly sensible system is about to be made slightly worse.&lt;/p&gt;

&lt;p&gt;Manufacturing has units per hour. Customer support has tickets closed. Software teams have deployment frequency, utilization, velocity and, for reasons historians will struggle to explain, story points.&lt;/p&gt;

&lt;p&gt;The attraction is obvious. Movement looks productive.&lt;/p&gt;

&lt;p&gt;A factory line that keeps moving feels efficient. A support queue that keeps shrinking looks healthy. A deployment pipeline that keeps shipping looks fast.&lt;/p&gt;

&lt;p&gt;Toyota built one of the world's most influential production systems partly by accepting something rather uncomfortable:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;sometimes the correct thing for a production line to do is stop.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Decades later, Amazon borrowed that idea.&lt;/p&gt;

&lt;p&gt;Except Amazon didn't have a car assembly line to stop.&lt;/p&gt;

&lt;p&gt;It had a website.&lt;/p&gt;

&lt;p&gt;And that small jump — from stopping defective cars to stopping defective products from being sold online — is far more interesting to me than either story on its own.&lt;/p&gt;

&lt;p&gt;Because this isn't really an article about Toyota.&lt;/p&gt;

&lt;p&gt;It isn't really about Amazon either.&lt;/p&gt;

&lt;p&gt;It's about recognizing when another industry has already solved your engineering problem, only with completely different nouns.&lt;/p&gt;




&lt;h2&gt;
  
  
  Toyota's strange idea: give people permission to interrupt production
&lt;/h2&gt;

&lt;p&gt;One of the foundations of the Toyota Production System is &lt;strong&gt;jidoka&lt;/strong&gt;, often translated as “automation with a human touch.”&lt;/p&gt;

&lt;p&gt;The idea predates Toyota's automobile factories.&lt;/p&gt;

&lt;p&gt;Toyota traces it back to Sakichi Toyoda and his automatic looms. One of the important innovations was deceptively simple: when something abnormal happened, such as a thread breaking, the machine could stop rather than continuing to produce defective fabric.&lt;/p&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;p&gt;A machine can have magnificent uptime while producing absolute rubbish.&lt;/p&gt;

&lt;p&gt;If you've worked with distributed systems, you have probably met its descendants.&lt;/p&gt;

&lt;p&gt;Toyota carried this principle into automobile manufacturing. Its historical documentation records the adoption of an Andon visual-control system in 1950 and stop-button-linked Andons on the Crown assembly line at the Honsha Plant in 1955.&lt;/p&gt;

&lt;p&gt;The Andon made abnormalities visible. Workers could signal for assistance when something wasn't right, and if the issue could not be resolved within the work cycle, production could be stopped rather than allowing the defect to continue downstream.&lt;/p&gt;

&lt;p&gt;Toyota's own description of the Toyota Production System still emphasizes this principle: equipment stops when abnormalities are detected, and operators can stop production when necessary so defective products are not simply passed to the next process.&lt;/p&gt;

&lt;p&gt;That's quite a bit more radical than it sounds today.&lt;/p&gt;

&lt;p&gt;Imagine explaining the idea purely through a utilization dashboard.&lt;/p&gt;

&lt;p&gt;“We installed a system that allows employees to interrupt our extremely expensive production machinery.”&lt;/p&gt;

&lt;p&gt;Wonderful.&lt;/p&gt;

&lt;p&gt;Did Finance approve this?&lt;/p&gt;




&lt;h2&gt;
  
  
  The internet version of Andon misses the clever bit
&lt;/h2&gt;

&lt;p&gt;The story is often compressed into:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every Toyota worker can pull a cord and stop the entire factory.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's memorable. It also makes Toyota sound slightly more theatrical than it actually is.&lt;/p&gt;

&lt;p&gt;Toyota's implementation evolved over time, and one particularly interesting design is the &lt;strong&gt;fixed-position line-stop system&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When a worker detects an abnormality, the first action isn't necessarily an immediate emergency stop. The Andon signals the problem and alerts the team leader while the vehicle continues toward a designated position.&lt;/p&gt;

&lt;p&gt;That creates a small intervention window.&lt;/p&gt;

&lt;p&gt;If the team leader reaches the worker and the problem can be resolved within the cycle, production continues. If it can't, the affected line stops at the fixed position.&lt;/p&gt;

&lt;p&gt;So the mechanism isn't simply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;problem → STOP EVERYTHING
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;abnormality detected
        ↓
make it visible
        ↓
bring help to the problem
        ↓
can we resolve it safely within the cycle?
       / \
     yes  no
      ↓    ↓
continue  stop
           ↓
      resolve cause
           ↓
         resume
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I like this much more than the viral version because it's recognizably good systems engineering.&lt;/p&gt;

&lt;p&gt;There is detection, escalation, a bounded recovery opportunity and a controlled stop.&lt;/p&gt;

&lt;p&gt;More importantly, the abnormality isn't allowed to quietly travel downstream just because stopping would make today's production chart look unpleasant.&lt;/p&gt;




&lt;h2&gt;
  
  
  A factory can be extremely efficient at producing defects
&lt;/h2&gt;

&lt;p&gt;Consider two imaginary factories.&lt;/p&gt;

&lt;p&gt;Factory A runs for an entire shift without interruption. Management sees beautiful utilization numbers.&lt;/p&gt;

&lt;p&gt;Unfortunately, several defects occur early in the process and aren't caught until much later. Components have already moved through additional stations. Labor has been added. Other parts have been attached. Some products require rework.&lt;/p&gt;

&lt;p&gt;Factory B stops several times during the same shift. Its utilization graph is uglier, but abnormalities are caught close to where they originate.&lt;/p&gt;

&lt;p&gt;Which one is more productive?&lt;/p&gt;

&lt;p&gt;You can't answer that until you decide where the system boundary is.&lt;/p&gt;

&lt;p&gt;If you're measuring &lt;strong&gt;minutes the assembly line moved&lt;/strong&gt;, Factory A looks fantastic.&lt;/p&gt;

&lt;p&gt;If you're measuring &lt;strong&gt;good products delivered with minimum total waste&lt;/strong&gt;, the answer may reverse completely.&lt;/p&gt;

&lt;p&gt;Software teams make this mistake all the time because local metrics are wonderfully easy to put on dashboards.&lt;/p&gt;

&lt;p&gt;A developer closes more tickets. Good.&lt;/p&gt;

&lt;p&gt;Deployment frequency increases. Good.&lt;/p&gt;

&lt;p&gt;CPU utilization improves. Good.&lt;/p&gt;

&lt;p&gt;Except perhaps the tickets are being reopened, the deployments are generating incidents and the service now has no headroom during traffic spikes.&lt;/p&gt;

&lt;p&gt;None of those metrics are useless. The problem starts when the metric becomes the objective instead of evidence about the objective.&lt;/p&gt;

&lt;p&gt;And defects have an especially nasty property: &lt;strong&gt;they become more expensive as they travel.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A bug noticed while writing the code might cost minutes.&lt;/p&gt;

&lt;p&gt;The same bug found by a unit test costs a little more.&lt;/p&gt;

&lt;p&gt;Found during integration testing, more again.&lt;/p&gt;

&lt;p&gt;Found after deployment, now we may have logs, rollback decisions, customer impact, corrupted state, support tickets and several engineers trying to remember who changed what on Tuesday.&lt;/p&gt;

&lt;p&gt;By the time a customer discovers it, the bug has acquired colleagues.&lt;/p&gt;

&lt;p&gt;Toyota's production philosophy attacks that propagation.&lt;/p&gt;

&lt;p&gt;Which brings us to Amazon.&lt;/p&gt;




&lt;h1&gt;
  
  
  Amazon looked at a car factory and saw a website
&lt;/h1&gt;

&lt;p&gt;In December 2006, Marc Onetto joined Amazon as Senior Vice President of Worldwide Operations. Amazon's SEC filings from the period confirm the role, and Onetto brought extensive manufacturing and operations experience from companies including GE and Solectron.&lt;/p&gt;

&lt;p&gt;Soon after arriving, he discussed applying Toyota's Andon concept at Amazon.&lt;/p&gt;

&lt;p&gt;He later described the story in an interview with McKinsey.&lt;/p&gt;

&lt;p&gt;Jeff Bezos supported the idea.&lt;/p&gt;

&lt;p&gt;Amazon implemented it in roughly six months.&lt;/p&gt;

&lt;p&gt;But there was an obvious problem.&lt;/p&gt;

&lt;p&gt;There was no assembly-line worker standing beside the Amazon product page.&lt;/p&gt;

&lt;p&gt;There was, however, someone unusually well positioned to detect defects:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;the customer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And there was another group receiving those defect reports all day:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;customer service.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suppose customers repeatedly contact Amazon because a particular product arrives with the wrong accessory.&lt;/p&gt;

&lt;p&gt;A conventional support system is very good at treating these as individual transactions.&lt;/p&gt;

&lt;p&gt;Refund the customer. Replace the item. Apologize. Close the ticket.&lt;/p&gt;

&lt;p&gt;Then sell another one.&lt;/p&gt;

&lt;p&gt;The next customer reports exactly the same defect.&lt;/p&gt;

&lt;p&gt;Refund. Replace.&lt;/p&gt;

&lt;p&gt;Another customer buys it.&lt;/p&gt;

&lt;p&gt;Same problem.&lt;/p&gt;

&lt;p&gt;At some point you no longer have a customer-service problem. You have built a &lt;strong&gt;defect subscription service&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Amazon changed the feedback loop.&lt;/p&gt;

&lt;p&gt;According to Onetto, when customer-service agents identified a repetitive defect, they were empowered to “stop the line” by removing the product from the website until the underlying problem could be investigated and corrected.&lt;/p&gt;

&lt;p&gt;Amazon also built a background process around identifying, tracking and resolving those defects.&lt;/p&gt;

&lt;p&gt;Onetto reported that the approach eliminated &lt;strong&gt;tens of thousands of defects per year&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That claim is worth dwelling on because this story sometimes gets repeated online as management folklore. In this case, we have the Amazon executive responsible for the implementation describing what happened, contemporary corporate records establishing his role, and lean-management organizations documenting the practice.&lt;/p&gt;

&lt;p&gt;Amazon really did build a digital Andon.&lt;/p&gt;




&lt;h2&gt;
  
  
  What exactly did Amazon copy?
&lt;/h2&gt;

&lt;p&gt;This is the part I find most useful as an engineer.&lt;/p&gt;

&lt;p&gt;Amazon obviously didn't copy Toyota literally.&lt;/p&gt;

&lt;p&gt;There were no ropes hanging beside customer-service desks. Nobody in Seattle needed to understand how to assemble a Corolla.&lt;/p&gt;

&lt;p&gt;Instead, somebody stripped away the domain-specific implementation and noticed the underlying structure.&lt;/p&gt;

&lt;p&gt;Toyota had something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;worker observes abnormality
        ↓
abnormality becomes visible
        ↓
team responds
        ↓
unresolved defect stops flow
        ↓
cause is corrected
        ↓
flow resumes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Amazon could map its own system onto the same structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customer experiences abnormality
        ↓
customer service sees the signal
        ↓
repeated defect is identified
        ↓
product availability is stopped
        ↓
cause is investigated/corrected
        ↓
selling resumes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting object isn't the Andon cord.&lt;/p&gt;

&lt;p&gt;It's the &lt;strong&gt;feedback architecture&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Detection happens near the place where evidence first appears. The signal is made visible. Someone has authority to act on it. Flow can be interrupted before more defective output is created. Then the system has a mechanism for recovery.&lt;/p&gt;

&lt;p&gt;Once you describe it that way, the automobile disappears from the idea completely.&lt;/p&gt;

&lt;p&gt;Toyota had solved a defect-propagation problem.&lt;/p&gt;

&lt;p&gt;Amazon also had a defect-propagation problem.&lt;/p&gt;

&lt;p&gt;The physical implementation was almost irrelevant.&lt;/p&gt;

&lt;p&gt;This is one of my favourite forms of innovation because it requires less invention and more abstraction.&lt;/p&gt;

&lt;p&gt;You look at something that works elsewhere and ask:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which parts of this solution are accidental details of that industry, and which parts are fundamental?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's a very engineering way of stealing ideas.&lt;/p&gt;




&lt;h1&gt;
  
  
  We've already built Andon cords into software
&lt;/h1&gt;

&lt;p&gt;Once I started looking at Andon this way, it became difficult not to see it everywhere in software architecture.&lt;/p&gt;

&lt;p&gt;A CI pipeline failing because a test breaks is an Andon-like mechanism.&lt;/p&gt;

&lt;p&gt;So is a circuit breaker.&lt;/p&gt;

&lt;p&gt;A canary deployment that detects an increase in error rate and automatically halts rollout is probably an even cleaner example.&lt;/p&gt;

&lt;p&gt;Suppose we're deploying version &lt;code&gt;v2&lt;/code&gt; across 200 instances.&lt;/p&gt;

&lt;p&gt;Instead of replacing all 200:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v1 → v2 everywhere
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we deliberately expose a small portion of traffic first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5% traffic → v2
95% traffic → v1

observe:
  error rate
  p95/p99 latency
  saturation
  business failures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the error budget starts burning unusually fast, rollout stops.&lt;/p&gt;

&lt;p&gt;From a narrow deployment-throughput perspective, this is slower. We've added monitoring periods, thresholds and rollback logic to something Kubernetes could otherwise execute quite enthusiastically.&lt;/p&gt;

&lt;p&gt;From a system perspective, we're paying a small interruption cost to limit the blast radius of a defect.&lt;/p&gt;

&lt;p&gt;The same reasoning appears in message processing.&lt;/p&gt;

&lt;p&gt;If an event fails schema validation, you can reject or quarantine it. Or you can be “resilient,” accept it anyway and discover six services later that one malformed event has been enthusiastically replicated across Kafka topics, databases, caches and analytics pipelines.&lt;/p&gt;

&lt;p&gt;The second architecture has excellent throughput right up until everyone opens Slack.&lt;/p&gt;

&lt;p&gt;Circuit breakers make the same trade.&lt;/p&gt;

&lt;p&gt;When a dependency is failing, continuing to send requests may feel like maintaining availability. In practice, you're often amplifying the failure: consuming threads, sockets, connection pools and retry budgets while increasing load on a service already struggling to recover.&lt;/p&gt;

&lt;p&gt;Opening the circuit deliberately refuses work for a period.&lt;/p&gt;

&lt;p&gt;Locally, that looks like failure.&lt;/p&gt;

&lt;p&gt;Globally, it may be the thing preventing one unhealthy service from dragging five healthy services down with it.&lt;/p&gt;

&lt;p&gt;These mechanisms don't exist because engineers dislike throughput.&lt;/p&gt;

&lt;p&gt;They exist because &lt;strong&gt;uncontrolled propagation is usually more expensive than controlled interruption&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The uncomfortable part isn't stopping. It's authority.
&lt;/h2&gt;

&lt;p&gt;This is where Andon becomes less of a technical pattern and more of an organizational one.&lt;/p&gt;

&lt;p&gt;Installing an alert is easy.&lt;/p&gt;

&lt;p&gt;Giving the person who sees the alert authority to interrupt something important is considerably harder.&lt;/p&gt;

&lt;p&gt;Toyota's worker isn't merely reporting an abnormality into a system where somebody might eventually create an action item.&lt;/p&gt;

&lt;p&gt;The signal demands a response.&lt;/p&gt;

&lt;p&gt;Amazon's customer-service agents weren't limited to apologizing more elegantly. The system gave frontline employees a path that could result in a product being removed from sale.&lt;/p&gt;

&lt;p&gt;That has commercial consequences.&lt;/p&gt;

&lt;p&gt;Which is why copying the visible mechanics of systems like Andon usually isn't enough.&lt;/p&gt;

&lt;p&gt;An organization can build a beautiful escalation workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Problem detected
      ↓
Ticket created
      ↓
Assigned to team
      ↓
Prioritized next sprint
      ↓
Moved to backlog
      ↓
Renamed "Known Limitation"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JIRA archaeology begins surprisingly quickly.&lt;/p&gt;

&lt;p&gt;Marc Onetto later discussed another revealing detail: unresolved Andon issues could eventually reach senior leadership. In an interview published by Planet Lean, he described personally reviewing issues that had remained unresolved for two weeks.&lt;/p&gt;

&lt;p&gt;That's the other half of the design.&lt;/p&gt;

&lt;p&gt;Escalation has to lead somewhere.&lt;/p&gt;

&lt;p&gt;Otherwise, you've built an expensive notification system for problems everybody already knows about.&lt;/p&gt;




&lt;h1&gt;
  
  
  The pattern I would steal
&lt;/h1&gt;

&lt;p&gt;If I were borrowing the Toyota/Amazon idea for a software platform, I wouldn't create something called an “Andon Service” and start adding Toyota terminology to architecture diagrams.&lt;/p&gt;

&lt;p&gt;That's how useful ideas end up becoming internal frameworks with 43 configuration options.&lt;/p&gt;

&lt;p&gt;I'd steal the constraints.&lt;/p&gt;

&lt;p&gt;For a critical flow, ask:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Where is the earliest reliable signal that something is abnormal?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Don't wait for the final customer-facing failure if the system can identify the condition three stages earlier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Can that signal automatically prevent propagation?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Validation, circuit breaking, deployment gates, quarantines and feature flags are all useful here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Is the interruption scoped appropriately?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Toyota's fixed-position approach is instructive. Don't shut down the planet because one request failed. Stop the smallest safe unit of flow.&lt;/p&gt;

&lt;p&gt;In distributed systems that could mean a tenant, partition, feature, deployment cohort, consumer group or dependency route.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. What is the recovery path?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A stop without recovery is just an outage.&lt;/p&gt;

&lt;p&gt;Who investigates? What proves the condition is safe again? Is recovery automatic or manual? What state needs reconciliation?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Does the organization actually permit the stop?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This one isn't technical.&lt;/p&gt;

&lt;p&gt;You can have flawless automated rollback logic, but if every failed deployment threshold gets manually overridden because “the release was promised for Thursday,” your Andon cord is decorative.&lt;/p&gt;

&lt;p&gt;That last failure mode is more common than I'd like.&lt;/p&gt;




&lt;h1&gt;
  
  
  There is also a danger in romanticizing this
&lt;/h1&gt;

&lt;p&gt;“Stop the line” sounds so sensible that it can become another slogan.&lt;/p&gt;

&lt;p&gt;Stopping has a cost.&lt;/p&gt;

&lt;p&gt;False positives have a cost.&lt;/p&gt;

&lt;p&gt;An overly sensitive circuit breaker can create an outage where none existed. Aggressive fraud controls can block legitimate customers. A noisy deployment gate can train engineers to override it. A marketplace that removes products based on weak signals can hurt sellers and customers.&lt;/p&gt;

&lt;p&gt;Toyota's system itself is evidence that the answer isn't simply “stop more often.”&lt;/p&gt;

&lt;p&gt;The fixed-position line-stop design introduced a bounded opportunity to resolve the issue before stopping became necessary.&lt;/p&gt;

&lt;p&gt;That's the sophisticated part.&lt;/p&gt;

&lt;p&gt;The design problem is deciding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;When is the confidence and expected downstream cost
high enough that interruption is cheaper than continuation?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a threshold problem.&lt;/p&gt;

&lt;p&gt;And thresholds need evidence.&lt;/p&gt;

&lt;p&gt;In software, I would want hysteresis, minimum sample sizes, error-budget context and carefully chosen recovery criteria rather than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if errors &amp;gt; 0:
    shut_down_company()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tempting though that implementation occasionally feels.&lt;/p&gt;

&lt;p&gt;The Toyota lesson isn't that stopping is good.&lt;/p&gt;

&lt;p&gt;It's that &lt;strong&gt;continuing is not automatically good&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Those are very different ideas.&lt;/p&gt;




&lt;h1&gt;
  
  
  The abstraction is the innovation
&lt;/h1&gt;

&lt;p&gt;What stays with me about the Amazon story isn't really that Amazon learned something from Toyota.&lt;/p&gt;

&lt;p&gt;Companies benchmark one another constantly.&lt;/p&gt;

&lt;p&gt;The clever move was recognizing what &lt;em&gt;not&lt;/em&gt; to copy.&lt;/p&gt;

&lt;p&gt;Toyota had factories, assembly workers, Andon boards, production cycles and physical vehicles.&lt;/p&gt;

&lt;p&gt;Amazon had warehouses, customer-service agents, web pages, suppliers and orders.&lt;/p&gt;

&lt;p&gt;At the surface, they're different systems.&lt;/p&gt;

&lt;p&gt;Underneath, both had the same uncomfortable question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What should happen when we know the output is defective but the system is perfectly capable of continuing anyway?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Toyota's answer was to make abnormality visible and allow production to stop before the defect travelled further.&lt;/p&gt;

&lt;p&gt;Amazon translated that principle into e-commerce and gave customer service a way to interrupt the source of repeated defects rather than endlessly cleaning up the consequences.&lt;/p&gt;

&lt;p&gt;Software architecture has been rediscovering versions of the same idea ever since.&lt;/p&gt;

&lt;p&gt;Circuit breakers.&lt;/p&gt;

&lt;p&gt;Canary rollouts.&lt;/p&gt;

&lt;p&gt;Automated rollback.&lt;/p&gt;

&lt;p&gt;Dead-letter queues.&lt;/p&gt;

&lt;p&gt;Schema gates.&lt;/p&gt;

&lt;p&gt;Kill switches.&lt;/p&gt;

&lt;p&gt;Feature flags.&lt;/p&gt;

&lt;p&gt;Different industries. Different vocabulary. Same architecture.&lt;/p&gt;

&lt;p&gt;And that's probably the part worth carrying into our own engineering work.&lt;/p&gt;

&lt;p&gt;When I'm looking at a difficult systems problem, the useful question may not always be:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Which new technology solves this?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes it's:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Who has already had this problem in a completely different industry?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer might be buried in manufacturing, aviation, logistics, medicine or finance rather than another engineering blog.&lt;/p&gt;

&lt;p&gt;So perhaps before adding another framework to &lt;code&gt;package.json&lt;/code&gt;, we should occasionally ask a stranger question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Have you tried looking at a factory?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>leadership</category>
      <category>systems</category>
      <category>design</category>
    </item>
    <item>
      <title>CocoIndex vs. code-review-graph: Semantic Search vs. Structural Code Intelligence</title>
      <dc:creator>Amrishkhan Sheik Abdullah</dc:creator>
      <pubDate>Wed, 26 Aug 2026 09:31:41 +0000</pubDate>
      <link>https://dev.to/amrishkhan05/cocoindex-vs-code-review-graph-semantic-search-vs-structural-code-intelligence-11bo</link>
      <guid>https://dev.to/amrishkhan05/cocoindex-vs-code-review-graph-semantic-search-vs-structural-code-intelligence-11bo</guid>
      <description>&lt;p&gt;Your Coding Agent Doesn't Need More Context. It Needs the Right Kind.&lt;/p&gt;

&lt;p&gt;AI coding agents have developed an expensive habit.&lt;/p&gt;

&lt;p&gt;When they don't understand a codebase, they read more of it.&lt;/p&gt;

&lt;p&gt;That works surprisingly well on a 30-file side project.&lt;/p&gt;

&lt;p&gt;On a mature repository, it's the software equivalent of solving a library question by reading every book.&lt;/p&gt;

&lt;p&gt;The obvious fix is indexing.&lt;/p&gt;

&lt;p&gt;Parse the repository once, keep the index fresh, and give the agent only the context relevant to its current task.&lt;/p&gt;

&lt;p&gt;Two open-source projects approaching this problem caught my attention recently: &lt;strong&gt;CocoIndex Code&lt;/strong&gt; and &lt;strong&gt;code-review-graph&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At first glance they look remarkably similar.&lt;/p&gt;

&lt;p&gt;Both parse code with Tree-sitter.&lt;/p&gt;

&lt;p&gt;Both update incrementally.&lt;/p&gt;

&lt;p&gt;Both integrate with coding agents through MCP.&lt;/p&gt;

&lt;p&gt;Both are trying to stop agents from stuffing unnecessary source files into already-expensive context windows.&lt;/p&gt;

&lt;p&gt;But after digging into how they work, I don't think they're really solving the same problem.&lt;/p&gt;

&lt;p&gt;My mental model is simpler:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;CocoIndex is better positioned to answer “Where is the code related to this concept?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;code-review-graph is designed to answer “What does this change affect?”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That distinction becomes important the moment your coding agent graduates from finding code to modifying it.&lt;/p&gt;

&lt;p&gt;Here are the official GitHub repositories for both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CocoIndex Code:&lt;/strong&gt; &lt;a href="https://github.com/cocoindex-io/cocoindex-code?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;github.com/cocoindex-io/cocoindex-code&lt;/a&gt; — AST-based semantic code search for coding agents. (&lt;a href="https://github.com/cocoindex-io/cocoindex-code?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;code-review-graph:&lt;/strong&gt; &lt;a href="https://github.com/tirth8205/code-review-graph?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;github.com/tirth8205/code-review-graph&lt;/a&gt; — structural code intelligence and blast-radius analysis for coding agents. (&lt;a href="https://github.com/tirth8205/code-review-graph?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Context Window Isn't a Codebase
&lt;/h2&gt;

&lt;p&gt;Suppose I ask an agent:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Find where session expiration is handled.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A naive agent can search filenames, grep for &lt;code&gt;session&lt;/code&gt;, inspect likely files and progressively build context.&lt;/p&gt;

&lt;p&gt;That's reasonable.&lt;/p&gt;

&lt;p&gt;Now ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I'm changing &lt;code&gt;validateSession()&lt;/code&gt;. What else could this break?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a different problem.&lt;/p&gt;

&lt;p&gt;Semantic similarity can help locate authentication-related code.&lt;/p&gt;

&lt;p&gt;But the most semantically similar function isn't necessarily the function that &lt;strong&gt;calls&lt;/strong&gt; &lt;code&gt;validateSession()&lt;/code&gt;, implements the same interface, depends on its return contract, or contains the test that catches the regression.&lt;/p&gt;

&lt;p&gt;This is where code intelligence starts splitting into two different retrieval models.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    CODEBASE
                       │
              ┌────────┴────────┐
              │                 │
              ▼                 ▼
       SEMANTIC VIEW      STRUCTURAL VIEW
              │                 │
        "What looks        "What depends
         relevant?"         on what?"
              │                 │
              ▼                 ▼
         CocoIndex        code-review-graph
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That diagram is deliberately simplified. CocoIndex is AST-aware, and code-review-graph also has search capabilities.&lt;/p&gt;

&lt;p&gt;The difference is what each system makes &lt;strong&gt;first-class&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  CocoIndex Treats Code Retrieval as an Indexing Problem
&lt;/h2&gt;

&lt;p&gt;CocoIndex itself is an incremental data-processing engine for AI workloads. Its code-indexing pipeline walks a repository, uses Tree-sitter to split source along syntax boundaries, embeds those chunks and maintains the resulting index as files change. The underlying incremental processing engine is implemented in Rust.&lt;/p&gt;

&lt;p&gt;The packaged developer tool is &lt;strong&gt;CocoIndex Code&lt;/strong&gt;, exposed through the &lt;code&gt;ccc&lt;/code&gt; CLI.&lt;/p&gt;

&lt;p&gt;Its core operation looks refreshingly unsurprising:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ccc index
ccc search &lt;span class="s2"&gt;"where is session expiration handled?"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Through MCP, the primary &lt;code&gt;search&lt;/code&gt; operation accepts a natural-language query or code snippet and returns matching chunks with their file paths, languages, line numbers and similarity scores. It can also filter by language and path.&lt;/p&gt;

&lt;p&gt;Architecturally, the important path looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Repository
    │
    ▼
Tree-sitter
    │
    ▼
AST-aware chunks
    │
    ▼
Embedding model
    │
    ▼
Vector index
    │
    ▼
Semantic query
    │
    ▼
Top-K relevant code
    │
    ▼
Coding agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a very useful abstraction because code search is often conceptual.&lt;/p&gt;

&lt;p&gt;I may not know whether the repository calls something:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SessionManager
AuthContext
TokenValidator
IdentityService
AccessPolicy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I only know what I'm trying to find:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Where do we decide whether a user is still authenticated?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Semantic retrieval is excellent at bridging that vocabulary gap.&lt;/p&gt;

&lt;p&gt;Traditional grep isn't.&lt;/p&gt;

&lt;p&gt;CocoIndex Code also includes a structural &lt;code&gt;ccc grep&lt;/code&gt; mode that matches syntax-tree patterns without requiring the embedding index, so the tool isn't exclusively vector retrieval.&lt;/p&gt;

&lt;p&gt;Still, semantic retrieval is the center of gravity.&lt;/p&gt;

&lt;p&gt;And for exploration, that's a very sensible center.&lt;/p&gt;




&lt;h2&gt;
  
  
  code-review-graph Starts Somewhere Else
&lt;/h2&gt;

&lt;p&gt;code-review-graph parses the repository with Tree-sitter too.&lt;/p&gt;

&lt;p&gt;But instead of primarily turning syntax units into searchable embeddings, it persists the repository as a structural graph.&lt;/p&gt;

&lt;p&gt;Its nodes represent things such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Files
Functions
Classes
Methods
Tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and its edges capture relationships such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;imports
calls
inheritance
test coverage
dependencies
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At review time, the system can traverse those relationships to calculate the &lt;strong&gt;blast radius&lt;/strong&gt; of a change.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         ┌──────────────┐
                         │ API Handler  │
                         └──────┬───────┘
                                │ calls
                                ▼
                       ┌────────────────┐
                       │ validateSession│
                       └───────┬────────┘
                               │
                   ┌───────────┼────────────┐
                   │           │            │
                called by   tested by    imports
                   │           │            │
                   ▼           ▼            ▼
             AuthGuard    SessionTests   TokenStore
                   │
                   ▼
             Route Group
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now changing &lt;code&gt;validateSession()&lt;/code&gt; isn't merely a search query.&lt;/p&gt;

&lt;p&gt;It's a graph traversal.&lt;/p&gt;

&lt;p&gt;That's a materially different operation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Similar Code and Dependent Code Are Not the Same Thing
&lt;/h2&gt;

&lt;p&gt;This is the part I think matters most.&lt;/p&gt;

&lt;p&gt;Imagine this TypeScript service:&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;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;calculateFare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;itinerary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Itinerary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;pricingContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PricingContext&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Fare&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// pricing logic&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A semantic index queried with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Find fare calculation logic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;should do very well.&lt;/p&gt;

&lt;p&gt;It can retrieve &lt;code&gt;calculateFare&lt;/code&gt;, adjacent pricing functions, perhaps tax calculations and discount logic.&lt;/p&gt;

&lt;p&gt;Now change the question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If I modify the return contract of &lt;code&gt;calculateFare&lt;/code&gt;, what needs review?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer might include code that isn't semantically about fare calculation at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BookingController
CheckoutMapper
PaymentRequestBuilder
AncillaryPricingAdapter
AnalyticsPublisher
FareContractTests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;AnalyticsPublisher&lt;/code&gt; may have almost no semantic resemblance to &lt;code&gt;calculateFare&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But if it consumes the output, I care.&lt;/p&gt;

&lt;p&gt;A lot.&lt;/p&gt;

&lt;p&gt;That's why I wouldn't frame this comparison as:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;vector database vs. graph database.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The more useful distinction is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;similarity retrieval vs. dependency retrieval.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One answers relevance.&lt;/p&gt;

&lt;p&gt;The other answers consequence.&lt;/p&gt;




&lt;h2&gt;
  
  
  CocoIndex Is the Tool I'd Reach for During Exploration
&lt;/h2&gt;

&lt;p&gt;Suppose I've just opened an unfamiliar repository.&lt;/p&gt;

&lt;p&gt;My questions are usually fuzzy:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Where is authorization enforced?&lt;/p&gt;

&lt;p&gt;How are payment retries implemented?&lt;/p&gt;

&lt;p&gt;Where does this service publish events?&lt;/p&gt;

&lt;p&gt;Find the logic responsible for refreshing tokens.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I don't yet know the symbols.&lt;/p&gt;

&lt;p&gt;I don't know the architecture.&lt;/p&gt;

&lt;p&gt;Sometimes I don't even know which package owns the behavior.&lt;/p&gt;

&lt;p&gt;This is exactly where semantic search earns its keep.&lt;/p&gt;

&lt;p&gt;CocoIndex Code's index is built from syntax-aware chunks rather than arbitrary windows of text, and incremental updates mean changed files can be reprocessed without rebuilding the whole repository index. CocoIndex's official example describes live filesystem indexing where only changed chunks are re-embedded and upserted.&lt;/p&gt;

&lt;p&gt;For an agent doing exploration, that produces a clean loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Question
   │
   ▼
Semantic search
   │
   ▼
5 relevant chunks
   │
   ▼
Agent reads them
   │
   ├── enough context? ──► reason
   │
   └── not enough? ─────► search again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is much healthier than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read src/
Read services/
Read auth/
Read utils/
Read another 14 files because we're here anyway
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CocoIndex Code repository currently advertises a 70% token-saving figure. I would treat that as a &lt;strong&gt;project-reported result&lt;/strong&gt;, not a universal constant: retrieval efficiency depends heavily on repository shape, query quality, embedding model and what the agent would otherwise have read.&lt;/p&gt;

&lt;p&gt;The architecture is more interesting than the headline percentage anyway.&lt;/p&gt;




&lt;h2&gt;
  
  
  code-review-graph Is the Tool I'd Reach for Before Changing Something Dangerous
&lt;/h2&gt;

&lt;p&gt;Now suppose I've found the code.&lt;/p&gt;

&lt;p&gt;The ticket says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Change session validation so revoked device tokens are rejected immediately.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finding &lt;code&gt;validateSession()&lt;/code&gt; isn't the difficult part anymore.&lt;/p&gt;

&lt;p&gt;The difficult questions are:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Who calls it?&lt;/p&gt;

&lt;p&gt;Which execution flows pass through it?&lt;/p&gt;

&lt;p&gt;What tests exercise those callers?&lt;/p&gt;

&lt;p&gt;Is another module depending on behavior that isn't obvious from the function signature?&lt;/p&gt;

&lt;p&gt;Does this change cross a community or subsystem boundary?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's where code-review-graph becomes interesting.&lt;/p&gt;

&lt;p&gt;Its &lt;code&gt;detect_changes&lt;/code&gt; and review workflows map changed code to affected functions, flows and tests. The project also exposes architecture maps, execution-flow tracing, community detection, refactoring tools and risk-scored reviews.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Changed file
    │
    ▼
Search for similar files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the reasoning becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Changed symbol
      │
      ▼
Direct callers
      │
      ▼
Transitive dependents
      │
      ├──────────► Tests
      │
      ├──────────► Entry points
      │
      └──────────► Cross-module edges
      │
      ▼
Impact radius
      │
      ▼
Agent review context
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For code review, I prefer that mental model.&lt;/p&gt;

&lt;p&gt;A pull request isn't asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What code resembles this diff?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What assumptions did this diff disturb?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Token Benchmarks Need a Footnote the Size of a Small Service
&lt;/h2&gt;

&lt;p&gt;code-review-graph publishes some dramatic context-reduction numbers.&lt;/p&gt;

&lt;p&gt;Its current README reports roughly &lt;strong&gt;82× median per-question reduction&lt;/strong&gt; across six benchmark repositories when comparing graph query context against reading the entire source corpus, with results ranging from 38× to 528×. The repository itself explicitly warns that the whole-corpus baseline is an upper bound because a competent coding agent wouldn't normally read every source file.&lt;/p&gt;

&lt;p&gt;I appreciate that qualification.&lt;/p&gt;

&lt;p&gt;The project maintains several benchmarks precisely because “tokens saved” changes meaning depending on what baseline you choose.&lt;/p&gt;

&lt;p&gt;Its documentation distinguishes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;whole-repository reading,&lt;/li&gt;
&lt;li&gt;a more realistic grep-and-read agent,&lt;/li&gt;
&lt;li&gt;changed-file context for reviews,&lt;/li&gt;
&lt;li&gt;and complete MCP workflow cost.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That matters enormously.&lt;/p&gt;

&lt;p&gt;If I change three lines in a 90-line file, a graph response containing impact edges, source snippets and test relationships could actually be &lt;strong&gt;larger&lt;/strong&gt; than simply handing the changed file to the model.&lt;/p&gt;

&lt;p&gt;The project documents this too: its formal review-context benchmark can produce ratios below 1 for small commits because the structural metadata itself has a cost.&lt;/p&gt;

&lt;p&gt;That's exactly the kind of boring benchmark detail I trust more than another giant “500×” badge.&lt;/p&gt;

&lt;p&gt;Context optimization isn't:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;graph = fewer tokens.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;graph = spend tokens describing relationships &lt;strong&gt;when those relationships are worth more than their serialization cost&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  At 10× Repository Size, the Difference Gets More Interesting
&lt;/h2&gt;

&lt;p&gt;Take a repository that grows from 300 files to 3,000.&lt;/p&gt;

&lt;p&gt;Then 30,000.&lt;/p&gt;

&lt;p&gt;Semantic retrieval has a pleasant property: the agent can still ask for the top few relevant chunks.&lt;/p&gt;

&lt;p&gt;The corpus becomes larger, but the final context doesn't necessarily grow linearly.&lt;/p&gt;

&lt;p&gt;That's good.&lt;/p&gt;

&lt;p&gt;A structural graph has a different scaling challenge.&lt;/p&gt;

&lt;p&gt;More code creates more:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nodes
edges
communities
call paths
cross-module dependencies
test relationships
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But that additional structure is precisely what becomes valuable in large systems.&lt;/p&gt;

&lt;p&gt;code-review-graph reports incremental updates based on changed-file hashes and graph relationships. Its current documentation describes a roughly 3,000-file Django repository where a two-file edit re-indexes in about 2.5 seconds on the hook path, with a substantial portion of that being process startup.&lt;/p&gt;

&lt;p&gt;CocoIndex attacks the same freshness problem from the indexing side: only changed chunks need to be reprocessed and re-embedded rather than rebuilding the corpus.&lt;/p&gt;

&lt;p&gt;So both systems understand the same production truth:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A code index that becomes stale every time somebody presses Save isn't code intelligence. It's documentation with impressive latency.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Incrementality isn't an optimization here.&lt;/p&gt;

&lt;p&gt;It's part of correctness.&lt;/p&gt;




&lt;h2&gt;
  
  
  Freshness Is a Consistency Problem
&lt;/h2&gt;

&lt;p&gt;This deserves more attention.&lt;/p&gt;

&lt;p&gt;Imagine an agent retrieves an index saying:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PaymentService → LegacyFraudClient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but three minutes ago another developer changed the code to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PaymentService → RiskGateway
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The index is now lying.&lt;/p&gt;

&lt;p&gt;That can be worse than having no index because the agent doesn't know its context is stale.&lt;/p&gt;

&lt;p&gt;Both tools address this through incremental updates.&lt;/p&gt;

&lt;p&gt;CocoIndex Code can refresh the semantic index and its agent integration can keep indexing current as the repository changes.&lt;/p&gt;

&lt;p&gt;code-review-graph offers watch mode and hooks that update the structural graph after file changes or commits.&lt;/p&gt;

&lt;p&gt;Architecturally, I think this should be treated like cache consistency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source code
    │
    │ change
    ▼
Change detector
    │
    ├────────► Semantic index update
    │
    └────────► Graph update
                    │
                    ▼
              Agent query
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent should never have to wonder whether yesterday's architecture is answering today's question.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where CocoIndex Starts Hurting
&lt;/h2&gt;

&lt;p&gt;Semantic search is not dependency analysis.&lt;/p&gt;

&lt;p&gt;That's the boundary I'd keep in mind.&lt;/p&gt;

&lt;p&gt;If I ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What code is related to payment retries?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;CocoIndex is a natural fit.&lt;/p&gt;

&lt;p&gt;If I ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can changing this retry function alter checkout behavior for callers that never mention retries?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;semantic similarity alone isn't enough.&lt;/p&gt;

&lt;p&gt;You can compensate by retrieving more context, searching symbols, letting the agent inspect imports and iterating.&lt;/p&gt;

&lt;p&gt;But at some point you're asking the LLM to reconstruct a dependency graph on demand.&lt;/p&gt;

&lt;p&gt;That's expensive.&lt;/p&gt;

&lt;p&gt;And slightly absurd when a parser can build one deterministically.&lt;/p&gt;

&lt;p&gt;The other operational cost is embeddings.&lt;/p&gt;

&lt;p&gt;CocoIndex Code supports local SentenceTransformers or cloud embedding providers through LiteLLM. Its full local installation brings heavier dependencies, while the slim installation expects a cloud embedding provider.&lt;/p&gt;

&lt;p&gt;That's not a criticism.&lt;/p&gt;

&lt;p&gt;Semantic retrieval requires a semantic representation somewhere.&lt;/p&gt;

&lt;p&gt;But it means your architecture has another dimension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local embeddings
    ├── privacy friendly
    ├── offline capable
    └── model/runtime footprint

Cloud embeddings
    ├── lighter local setup
    ├── potentially stronger model choice
    └── code/privacy/network boundary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For enterprise repositories, that trust boundary isn't a footnote.&lt;/p&gt;

&lt;p&gt;It's architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where code-review-graph Starts Hurting
&lt;/h2&gt;

&lt;p&gt;Graphs aren't free either.&lt;/p&gt;

&lt;p&gt;A static graph can tell me:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A calls B
B imports C
D tests B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But production software contains relationships that static analysis struggles to prove.&lt;/p&gt;

&lt;p&gt;Dependency injection.&lt;/p&gt;

&lt;p&gt;Runtime reflection.&lt;/p&gt;

&lt;p&gt;Dynamic imports.&lt;/p&gt;

&lt;p&gt;Framework magic.&lt;/p&gt;

&lt;p&gt;Configuration-selected implementations.&lt;/p&gt;

&lt;p&gt;Message brokers.&lt;/p&gt;

&lt;p&gt;Database-driven workflows.&lt;/p&gt;

&lt;p&gt;HTTP calls whose relationship exists in configuration rather than source syntax.&lt;/p&gt;

&lt;p&gt;A TypeScript service can publish:&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="nx"&gt;eventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;payment.completed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while the consumer lives in another repository.&lt;/p&gt;

&lt;p&gt;Your local AST doesn't magically know that relationship.&lt;/p&gt;

&lt;p&gt;A graph can only be as correct as the relationships it can observe or infer.&lt;/p&gt;

&lt;p&gt;This is why I would never interpret blast-radius analysis as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;These are all the things this change can affect.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I would interpret it as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;These are the dependencies the graph can prove or reasonably model.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's still extremely useful.&lt;/p&gt;

&lt;p&gt;It's just not omniscience.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security Changes the Recommendation
&lt;/h2&gt;

&lt;p&gt;For sensitive repositories, both tools have an attractive property: they can operate locally.&lt;/p&gt;

&lt;p&gt;code-review-graph stores its core graph in a local SQLite file under &lt;code&gt;.code-review-graph/&lt;/code&gt;, and its documentation warns that exported graph data can contain absolute paths and structural metadata that should be sanitized before publishing.&lt;/p&gt;

&lt;p&gt;CocoIndex Code can use local embeddings, meaning source doesn't need to leave the machine for embedding generation. It also supports cloud providers when teams prefer them.&lt;/p&gt;

&lt;p&gt;If I were deploying either in a regulated environment, I'd still review:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source access
     │
     ├── Parser process
     ├── Embedding provider
     ├── Local index
     ├── MCP transport
     ├── Agent process
     └── Export / telemetry behavior
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;“Local-first” is a useful property.&lt;/p&gt;

&lt;p&gt;It isn't a substitute for a data-flow review.&lt;/p&gt;




&lt;h2&gt;
  
  
  I Wouldn't Actually Choose One for Every Workflow
&lt;/h2&gt;

&lt;p&gt;After comparing them, I think asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CocoIndex or code-review-graph?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;is slightly the wrong architecture question.&lt;/p&gt;

&lt;p&gt;For an agent that spends most of its time &lt;strong&gt;discovering implementations&lt;/strong&gt;, I'd start with CocoIndex Code.&lt;/p&gt;

&lt;p&gt;For an agent heavily involved in &lt;strong&gt;PR review, refactoring and impact analysis&lt;/strong&gt;, I'd start with code-review-graph.&lt;/p&gt;

&lt;p&gt;For a serious engineering agent, the architecture I find most interesting is actually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         Developer Question
                                │
                                ▼
                         Coding Agent
                                │
              ┌─────────────────┴─────────────────┐
              │                                   │
              ▼                                   ▼
     Semantic Retrieval                  Structural Retrieval
              │                                   │
         CocoIndex                         Code Graph
              │                                   │
       "What code is                       "What code is
        relevant?"                          connected?"
              │                                   │
              └─────────────────┬─────────────────┘
                                ▼
                       Context Composer
                                │
                                ▼
                       Minimal useful context
                                │
                                ▼
                         Agent reasoning
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have two independent signals.&lt;/p&gt;

&lt;p&gt;Semantic similarity gives us &lt;strong&gt;meaning&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Graph traversal gives us &lt;strong&gt;relationships&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Those signals can disagree.&lt;/p&gt;

&lt;p&gt;That's useful.&lt;/p&gt;

&lt;p&gt;If semantic search says a function is relevant but the graph shows no dependency path from the changed code, maybe it's conceptual context rather than blast radius.&lt;/p&gt;

&lt;p&gt;If the graph says a distant analytics module is affected but semantic similarity ranks it near zero, that's exactly the kind of non-obvious dependency I want surfaced during review.&lt;/p&gt;

&lt;p&gt;The disagreement is information.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Best Retrieval System Might Be a Router
&lt;/h2&gt;

&lt;p&gt;I wouldn't blindly query both systems for every prompt.&lt;/p&gt;

&lt;p&gt;That's how an optimization becomes another token bill.&lt;/p&gt;

&lt;p&gt;I'd route based on intent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Where is X implemented?"
            │
            ▼
       Semantic Search


"What calls X?"
            │
            ▼
       Graph Traversal


"What breaks if I change X?"
            │
            ▼
       Graph + Tests


"How does authentication work?"
            │
            ▼
   Semantic + Execution Flow


"Find code similar to this pattern"
            │
            ▼
   Semantic / Structural Search


"Review this PR"
            │
            ▼
   Diff → Graph Blast Radius
            │
            ▼
   Semantic Search for Missing Context
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the architecture I'd want in an agent.&lt;/p&gt;

&lt;p&gt;Not one enormous context engine.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;context router&lt;/strong&gt; that knows what kind of evidence the current question requires.&lt;/p&gt;

&lt;p&gt;Because “relevant” is not one thing.&lt;/p&gt;




&lt;h2&gt;
  
  
  When I'd Choose Something Simpler
&lt;/h2&gt;

&lt;p&gt;Neither tool is automatically necessary.&lt;/p&gt;

&lt;p&gt;If your repository has 80 files, clear module boundaries and predictable naming, &lt;code&gt;rg&lt;/code&gt;, language-server references and an agent that knows how to search may already be enough.&lt;/p&gt;

&lt;p&gt;Adding embeddings, an indexing daemon, MCP configuration and persistent graph state just to save 900 tokens is infrastructure cosplay.&lt;/p&gt;

&lt;p&gt;I'd introduce code intelligence when I can identify an actual retrieval problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;agents repeatedly reading irrelevant files,&lt;/li&gt;
&lt;li&gt;large monorepos exhausting context,&lt;/li&gt;
&lt;li&gt;developers struggling to discover implementations,&lt;/li&gt;
&lt;li&gt;PR reviews missing transitive impact,&lt;/li&gt;
&lt;li&gt;cross-module refactors becoming dangerous,&lt;/li&gt;
&lt;li&gt;onboarding requiring archaeological expeditions through the repository.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture should pay rent.&lt;/p&gt;

&lt;p&gt;Otherwise, grep remains one of the best pieces of developer infrastructure ever shipped.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd Deploy
&lt;/h2&gt;

&lt;p&gt;If my primary problem were &lt;strong&gt;agent exploration&lt;/strong&gt;, I'd deploy CocoIndex Code first.&lt;/p&gt;

&lt;p&gt;Its semantic model maps naturally to the fuzzy questions developers ask when they don't yet understand a repository.&lt;/p&gt;

&lt;p&gt;If my primary problem were &lt;strong&gt;change safety&lt;/strong&gt;, I'd deploy code-review-graph first.&lt;/p&gt;

&lt;p&gt;Call relationships, tests and blast-radius analysis are closer to the evidence I want before modifying production behavior.&lt;/p&gt;

&lt;p&gt;For a mature platform with heavy AI-assisted development, I'd eventually want both capabilities behind one retrieval layer.&lt;/p&gt;

&lt;p&gt;Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    ┌─────────────────────┐
                    │     Agent Query     │
                    └──────────┬──────────┘
                               │
                               ▼
                    ┌─────────────────────┐
                    │   Intent Classifier │
                    └──────────┬──────────┘
                               │
             ┌─────────────────┼─────────────────┐
             │                 │                 │
             ▼                 ▼                 ▼
         Semantic          Structural          Hybrid
          Search             Graph              Query
             │                 │                 │
             └─────────────────┼─────────────────┘
                               ▼
                    ┌─────────────────────┐
                    │ Context Budgeter    │
                    │ rank · dedupe · cap │
                    └──────────┬──────────┘
                               │
                               ▼
                    ┌─────────────────────┐
                    │   Coding Agent      │
                    └─────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting engineering problem then stops being indexing.&lt;/p&gt;

&lt;p&gt;It becomes &lt;strong&gt;context selection&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Which evidence deserves 500 tokens?&lt;/p&gt;

&lt;p&gt;Which dependency edge is important enough to include?&lt;/p&gt;

&lt;p&gt;When should semantic similarity override structural distance?&lt;/p&gt;

&lt;p&gt;When should the agent expand another hop?&lt;/p&gt;

&lt;p&gt;When does the retrieval cost exceed the cost of simply reading the file?&lt;/p&gt;

&lt;p&gt;Those are the questions I expect code-agent infrastructure to spend a lot more time solving.&lt;/p&gt;

&lt;p&gt;Because a larger context window doesn't eliminate retrieval architecture.&lt;/p&gt;

&lt;p&gt;It just makes bad retrieval more expensive before anyone notices.&lt;/p&gt;

&lt;p&gt;CocoIndex and code-review-graph approach the problem from different directions.&lt;/p&gt;

&lt;p&gt;One asks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What code means something similar to what you're looking for?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The other asks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What code is structurally connected to what you're touching?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For exploration, meaning often wins.&lt;/p&gt;

&lt;p&gt;For change safety, relationships often win.&lt;/p&gt;

&lt;p&gt;And for serious codebase intelligence, I don't think the future is choosing between them.&lt;/p&gt;

&lt;p&gt;It's knowing &lt;strong&gt;which kind of context to ask for before the agent starts reading.&lt;/strong&gt;&lt;/p&gt;




</description>
      <category>ai</category>
      <category>agentskills</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>Ponytail: Teaching AI Coding Agents the Most Expensive Senior Engineering Skill — Restraint</title>
      <dc:creator>Amrishkhan Sheik Abdullah</dc:creator>
      <pubDate>Wed, 26 Aug 2026 06:19:41 +0000</pubDate>
      <link>https://dev.to/amrishkhan05/ponytail-teaching-ai-coding-agents-the-most-expensive-senior-engineering-skill-restraint-3p7</link>
      <guid>https://dev.to/amrishkhan05/ponytail-teaching-ai-coding-agents-the-most-expensive-senior-engineering-skill-restraint-3p7</guid>
      <description>&lt;p&gt;Give an AI coding agent a small problem and, occasionally, you'll get the software equivalent of hiring a construction crew to hang a picture frame.&lt;/p&gt;

&lt;p&gt;Need a date input?&lt;/p&gt;

&lt;p&gt;The agent can absolutely build one.&lt;/p&gt;

&lt;p&gt;A component. State management. Validation. Styling. A date-picker dependency. Maybe a wrapper around that dependency because apparently we have architectural ambitions now.&lt;/p&gt;

&lt;p&gt;Meanwhile, the browser has been sitting quietly in the corner with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"date"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of the more interesting failure modes of AI-assisted development.&lt;/p&gt;

&lt;p&gt;The models have become remarkably capable at &lt;strong&gt;writing code&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;They aren't always equally good at deciding whether the code should exist.&lt;/p&gt;

&lt;p&gt;That distinction is exactly why &lt;a href="https://ponytail.dev/" rel="noopener noreferrer"&gt;Ponytail&lt;/a&gt; caught my attention.&lt;/p&gt;

&lt;p&gt;Ponytail is an open-source ruleset/plugin by Dietrich Gebert that describes itself as putting a "lazy senior developer" inside your AI coding agent. It works across a surprisingly broad set of coding-agent environments, including Claude Code, Codex, GitHub Copilot CLI, Gemini CLI, OpenCode and several instruction-file-based integrations.&lt;/p&gt;

&lt;p&gt;The joke is good.&lt;/p&gt;

&lt;p&gt;The engineering idea underneath it is better.&lt;/p&gt;

&lt;p&gt;Ponytail is essentially trying to teach an AI agent something experienced engineers eventually learn the expensive way:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every line you don't write is a line you don't have to debug, secure, test, review, upgrade and explain three years later.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  AI Agents Have an Over-Engineering Problem
&lt;/h2&gt;

&lt;p&gt;The problem isn't that AI-generated code is always bad.&lt;/p&gt;

&lt;p&gt;That would actually be easier.&lt;/p&gt;

&lt;p&gt;The uncomfortable problem is that the code can be perfectly reasonable while the &lt;strong&gt;decision to create it was unnecessary&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Ask for a small feature and the agent has an enormous solution space available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Requirement
    │
    ▼
┌─────────────────────────────┐
│       AI Coding Agent       │
└─────────────────────────────┘
    │
    ├── Create abstraction
    ├── Add dependency
    ├── Create helper
    ├── Create component
    ├── Add configuration
    ├── Introduce interface
    └── Write custom solution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most of those options can produce valid software.&lt;/p&gt;

&lt;p&gt;That's precisely the problem.&lt;/p&gt;

&lt;p&gt;A compiler can tell you whether your TypeScript is valid. A test can tell you whether &lt;code&gt;calculatePrice()&lt;/code&gt; returned the expected value.&lt;/p&gt;

&lt;p&gt;Neither tells you:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why did we create &lt;code&gt;AbstractPriceCalculationStrategyFactory&lt;/code&gt; in the first place?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Senior engineering is full of these negative decisions.&lt;/p&gt;

&lt;p&gt;Don't create the service.&lt;/p&gt;

&lt;p&gt;Don't introduce Kafka yet.&lt;/p&gt;

&lt;p&gt;Don't add Redis because one query is slow.&lt;/p&gt;

&lt;p&gt;Don't write a custom retry framework when the client already supports retries.&lt;/p&gt;

&lt;p&gt;Don't introduce another abstraction because two functions happen to contain four similar lines.&lt;/p&gt;

&lt;p&gt;And please don't install 38 KB of JavaScript because HTML already solved the problem.&lt;/p&gt;

&lt;p&gt;Ponytail tries to move that decision &lt;strong&gt;before code generation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That ordering matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Seven-Rung Ladder Is the Real Product
&lt;/h2&gt;

&lt;p&gt;Strip away the branding and Ponytail's core mechanism is remarkably small.&lt;/p&gt;

&lt;p&gt;Before generating a solution, the agent walks through a hierarchy:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fitinhxp4mxe3bcyxdjy9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fitinhxp4mxe3bcyxdjy9.png" alt="Ponytail changes the agent's default path from “build a solution” to “find the smallest sufficient solution" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The actual rules add an important qualification: the ladder runs &lt;strong&gt;after the agent understands the problem&lt;/strong&gt;. Ponytail tells the agent to read the affected code and trace the real flow before choosing the smallest solution. It also explicitly favors fixing a shared root cause over scattering symptom patches across callers.&lt;/p&gt;

&lt;p&gt;That's an important distinction.&lt;/p&gt;

&lt;p&gt;Otherwise "write less code" becomes code golf wearing an architecture badge.&lt;/p&gt;

&lt;p&gt;Ponytail isn't really optimizing for minimum LOC.&lt;/p&gt;

&lt;p&gt;It's optimizing for &lt;strong&gt;minimum unnecessary ownership&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Those are very different objectives.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Best Rung Might Be Rung Zero
&lt;/h2&gt;

&lt;p&gt;The first question is the one engineering teams routinely skip:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this need to exist?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is YAGNI, but AI makes YAGNI more important than it used to be.&lt;/p&gt;

&lt;p&gt;Historically, unnecessary software had friction.&lt;/p&gt;

&lt;p&gt;Someone had to design it. Someone had to type it. Someone had to get tired halfway through implementing it.&lt;/p&gt;

&lt;p&gt;AI has dramatically reduced that friction.&lt;/p&gt;

&lt;p&gt;A developer can now generate an abstraction, tests, DTOs, documentation and adapters before their coffee has reached a drinkable temperature.&lt;/p&gt;

&lt;p&gt;That's useful when the abstraction is necessary.&lt;/p&gt;

&lt;p&gt;It's dangerous when generation cost gets confused with ownership cost.&lt;/p&gt;

&lt;p&gt;Suppose an agent can generate 500 lines in 30 seconds.&lt;/p&gt;

&lt;p&gt;Those 500 lines still enter your system.&lt;/p&gt;

&lt;p&gt;They still participate in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generated code
     │
     ├──► Code review
     │
     ├──► Tests
     │
     ├──► Security surface
     │
     ├──► Dependency upgrades
     │
     ├──► Refactoring
     │
     ├──► Debugging
     │
     ├──► Observability
     │
     └──► Future developer comprehension
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generation became cheap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maintenance didn't.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's the architectural consequence I find more interesting than Ponytail itself.&lt;/p&gt;

&lt;p&gt;As coding agents become faster, the scarce engineering resource shifts from implementation capacity toward &lt;strong&gt;judgment&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We don't need agents merely capable of producing more software.&lt;/p&gt;

&lt;p&gt;We need agents capable of refusing to produce software when the existing system already contains the answer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reuse Before Reinvention Is Harder Than It Sounds
&lt;/h2&gt;

&lt;p&gt;The second rung asks whether the codebase already contains the solution.&lt;/p&gt;

&lt;p&gt;This sounds obvious.It isn't.&lt;/p&gt;

&lt;p&gt;In a mature backend, the same conceptual operation may already exist behind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a shared utility,&lt;/li&gt;
&lt;li&gt;an internal SDK,&lt;/li&gt;
&lt;li&gt;middleware,&lt;/li&gt;
&lt;li&gt;a domain service,&lt;/li&gt;
&lt;li&gt;a framework extension,&lt;/li&gt;
&lt;li&gt;an infrastructure adapter,&lt;/li&gt;
&lt;li&gt;or an implementation whose name doesn't quite match the new ticket.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An agent working too locally can easily generate:&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;normalizeEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&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="kr"&gt;string&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;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&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;Perfectly harmless.&lt;/p&gt;

&lt;p&gt;Until &lt;code&gt;normalizeUserEmail()&lt;/code&gt; already exists three directories away with additional Unicode handling required by the system.&lt;/p&gt;

&lt;p&gt;Now we don't have one simple function.&lt;/p&gt;

&lt;p&gt;We have &lt;strong&gt;two definitions of what a normalized email means&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's how small duplication becomes domain drift.&lt;/p&gt;

&lt;p&gt;Ponytail's rules explicitly instruct the agent to inspect the code it touches and trace callers when fixing bugs. The smallest &lt;em&gt;correct&lt;/em&gt; diff may therefore involve changing a shared function rather than inserting a guard into the exact path named by the ticket.&lt;/p&gt;

&lt;p&gt;That is much closer to how an experienced engineer approaches maintenance work.&lt;/p&gt;

&lt;p&gt;The ticket tells you where somebody &lt;strong&gt;observed&lt;/strong&gt; the problem.&lt;/p&gt;

&lt;p&gt;It doesn't necessarily tell you where the problem &lt;strong&gt;lives&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Native Platform Features Are Criminally Underrated
&lt;/h2&gt;

&lt;p&gt;This is where Ponytail's benchmark gets interesting.&lt;/p&gt;

&lt;p&gt;The project tested the skill using real headless Claude Code sessions against a pinned version of the open-source FastAPI + React full-stack template. Twelve feature tasks were run four times per test arm using Haiku 4.5. Instead of counting prose emitted by the model, the newer benchmark measures added lines left in the actual &lt;code&gt;git diff&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The biggest reductions appeared where the platform already provided the feature.&lt;/p&gt;

&lt;p&gt;For the date-picker task, the baseline averaged &lt;strong&gt;404 added lines&lt;/strong&gt;. Ponytail averaged &lt;strong&gt;23&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For the color picker: &lt;strong&gt;287 versus 23&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For file upload/drop behavior: &lt;strong&gt;251 versus 95&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Meanwhile, straightforward backend tasks such as searching by title were effectively identical across approaches: 44 lines for the baseline and 44 for Ponytail.&lt;/p&gt;

&lt;p&gt;That last result matters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1sfosoq8sfvdmcgikr73.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1sfosoq8sfvdmcgikr73.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If Ponytail simply forced everything into absurd one-liners, I'd consider it a fun prompt rather than an engineering tool.&lt;/p&gt;

&lt;p&gt;Instead, the benchmark suggests its largest effect occurs where there is genuinely something unnecessary to remove.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 ROOM TO OVER-ENGINEER
                         ▲
                         │
 Native UI feature       │ ███████████████████
 Custom UI component     │ █████████████████
                         │
 File handling           │ ███████████
                         │
 CRUD endpoint           │ ██
                         │
 Simple query            │ █
                         └────────────────────────►
                           Potential Ponytail Gain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The less irreducible code a task requires, the more useful restraint becomes.&lt;/p&gt;

&lt;p&gt;That's exactly where I'd expect a senior engineer to save time too.&lt;/p&gt;

&lt;p&gt;Not by typing the same architecture faster.&lt;/p&gt;

&lt;p&gt;By noticing that half the architecture isn't necessary.&lt;/p&gt;




&lt;h2&gt;
  
  
  Less Code Is Not Automatically Better Code
&lt;/h2&gt;

&lt;p&gt;This is where any "lazy developer" philosophy can become dangerous.&lt;/p&gt;

&lt;p&gt;Take an untrusted filename:&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;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uploadDirectory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userProvidedFilename&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beautiful.&lt;/p&gt;

&lt;p&gt;Short.&lt;/p&gt;

&lt;p&gt;Potentially terrible.&lt;/p&gt;

&lt;p&gt;If the filename contains traversal sequences, brevity just bought you a security bug.&lt;/p&gt;

&lt;p&gt;Ponytail's rules explicitly exclude trust-boundary validation, security, accessibility and error handling that prevents data loss from simplification. Non-trivial logic is also expected to leave behind a small runnable check.&lt;/p&gt;

&lt;p&gt;The project's safety benchmark is small, so I wouldn't turn it into a universal security claim. The maintainers don't either.&lt;/p&gt;

&lt;p&gt;Across five security-oriented tasks with four runs per arm, Ponytail's outputs passed all 20 adversarial checks. A simple "YAGNI + prefer one-liners" control passed 19/20; one generated path-handling implementation allowed directory traversal. The benchmark itself explicitly says these deterministic checks are a floor, not proof that generated code is secure.&lt;/p&gt;

&lt;p&gt;That's the correct interpretation.&lt;/p&gt;

&lt;p&gt;There is a boundary between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             DELETE / SIMPLIFY
                    │
                    ▼
        ┌───────────────────────┐
        │ Boilerplate           │
        │ Duplicate abstraction │
        │ Reinvented helpers    │
        │ Unneeded dependencies │
        │ Speculative features  │
        └───────────────────────┘


               DO NOT CUT
                    │
                    ▼
        ┌───────────────────────┐
        │ Trust validation      │
        │ Authorization         │
        │ Data-loss protection  │
        │ Required error paths  │
        │ Accessibility         │
        └───────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference between minimal engineering and careless engineering is knowing which side of that line you're standing on.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Benchmark Story Is Better Because the First One Was Wrong
&lt;/h2&gt;

&lt;p&gt;This part deserves more attention than the headline percentages.&lt;/p&gt;

&lt;p&gt;Ponytail originally reported much larger reductions from a single-shot benchmark: roughly 80–94% less code.&lt;/p&gt;

&lt;p&gt;That benchmark was criticized because the baseline model produced conversational prose and multiple options, while the measurement counted lines of the whole response. In other words, some of the apparent reduction wasn't less implementation. It was simply less talking.&lt;/p&gt;

&lt;p&gt;The maintainer agreed with the criticism.&lt;/p&gt;

&lt;p&gt;Then rebuilt the benchmark around actual coding-agent sessions, fresh repository copies, isolated contexts and &lt;code&gt;git diff&lt;/code&gt; output. During that process, they also found a contamination bug where Ponytail's lifecycle hook had accidentally fired in baseline runs, making the baseline secretly use Ponytail. That run was discarded and the harness was fixed to isolate each test arm.&lt;/p&gt;

&lt;p&gt;I like this considerably more than pretending the original number never existed.&lt;/p&gt;

&lt;p&gt;The corrected feature-task aggregate was:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;LOC vs. Baseline&lt;/th&gt;
&lt;th&gt;Tokens&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Safety Check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ponytail&lt;/td&gt;
&lt;td&gt;-54%&lt;/td&gt;
&lt;td&gt;-22%&lt;/td&gt;
&lt;td&gt;-20%&lt;/td&gt;
&lt;td&gt;-27%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terse "caveman" control&lt;/td&gt;
&lt;td&gt;-20%&lt;/td&gt;
&lt;td&gt;+7%&lt;/td&gt;
&lt;td&gt;+3%&lt;/td&gt;
&lt;td&gt;+2%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;YAGNI + one-liner prompt&lt;/td&gt;
&lt;td&gt;-33%&lt;/td&gt;
&lt;td&gt;-14%&lt;/td&gt;
&lt;td&gt;-21%&lt;/td&gt;
&lt;td&gt;-30%&lt;/td&gt;
&lt;td&gt;95%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are project-reported results from one model, one repository family and a relatively small task set, not evidence that installing Ponytail will cut every engineering team's LOC bill in half. The project's own limitations call out the single-model test, &lt;code&gt;n=4&lt;/code&gt; nondeterminism, limited security tests and several timed-out benchmark cells.&lt;/p&gt;

&lt;p&gt;That's how I'd read the numbers.&lt;/p&gt;

&lt;p&gt;Not:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Ponytail makes AI coding 54% better."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Rather:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"There is measurable evidence that explicitly teaching an agent to search for simpler solution classes can materially reduce over-building on tasks where simpler solution classes exist."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Less exciting headline.&lt;/p&gt;

&lt;p&gt;Much more useful engineering claim.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Not Just Put "Use YAGNI" in Your Prompt?
&lt;/h2&gt;

&lt;p&gt;That was my first question.&lt;/p&gt;

&lt;p&gt;We don't exactly need another repository to tell a language model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Keep the solution simple.
Follow YAGNI.
Prefer existing functionality.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The benchmark tests roughly that idea with a short "YAGNI + one-liners" control.&lt;/p&gt;

&lt;p&gt;Sometimes it works extremely well. On the color-picker task it came close to Ponytail.&lt;/p&gt;

&lt;p&gt;Other times it didn't. The date-picker implementation averaged 162 lines against Ponytail's 23, and the command-palette result was actually larger than the baseline.&lt;/p&gt;

&lt;p&gt;This points to the useful part of packaging engineering behavior as an agent skill.&lt;/p&gt;

&lt;p&gt;A casual instruction is a preference.&lt;/p&gt;

&lt;p&gt;A repeated decision ladder, injected into the working context and supported by review/audit commands, is closer to a &lt;strong&gt;policy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Ponytail exposes intensity levels (&lt;code&gt;lite&lt;/code&gt;, &lt;code&gt;full&lt;/code&gt;, &lt;code&gt;ultra&lt;/code&gt;, &lt;code&gt;off&lt;/code&gt;) plus commands for reviewing a current diff, auditing a repository, tracking deferred shortcuts and inspecting benchmark gains. Its default mode is &lt;code&gt;full&lt;/code&gt;. Depending on the host, the rules can be injected through plugins/hooks or loaded through files such as &lt;code&gt;AGENTS.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That gives the idea persistence.&lt;/p&gt;

&lt;p&gt;And persistence matters with coding agents because every fresh context is another opportunity for your architectural preferences to mysteriously develop amnesia.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd Actually Use Ponytail For
&lt;/h2&gt;

&lt;p&gt;I wouldn't treat Ponytail as an architecture oracle.&lt;/p&gt;

&lt;p&gt;That's not what it is.&lt;/p&gt;

&lt;p&gt;I'd use it as a &lt;strong&gt;counterweight&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;AI coding agents have a natural advantage in construction. Give them a sufficiently precise requirement and they can generate implementation machinery very quickly.&lt;/p&gt;

&lt;p&gt;Ponytail introduces pressure in the opposite direction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    USER REQUIREMENT
                           │
                           ▼
                 ┌───────────────────┐
                 │ Understand Flow   │
                 └─────────┬─────────┘
                           │
                           ▼
                 ┌───────────────────┐
                 │ Ponytail Ladder   │
                 │ "Can we do less?" │
                 └─────────┬─────────┘
                           │
               ┌───────────┴───────────┐
               ▼                       ▼
       Existing capability      New code required
               │                       │
               ▼                       ▼
          Reuse / native         Minimum correct
             solution              implementation
               │                       │
               └───────────┬───────────┘
                           ▼
                 ┌───────────────────┐
                 │ Safety / Tests    │
                 └─────────┬─────────┘
                           ▼
                        DIFF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I especially like the idea for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mature repositories with established utilities and conventions,&lt;/li&gt;
&lt;li&gt;frontend work where native browser capabilities are easily overlooked,&lt;/li&gt;
&lt;li&gt;maintenance tickets where agents tend to widen the change unnecessarily,&lt;/li&gt;
&lt;li&gt;teams using agents heavily enough that generated-code volume is becoming a review burden,&lt;/li&gt;
&lt;li&gt;and repositories where dependency creep has started becoming noticeable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;/ponytail-review&lt;/code&gt; concept is arguably as interesting as generation itself.&lt;/p&gt;

&lt;p&gt;Generating unnecessary code is one problem.&lt;/p&gt;

&lt;p&gt;Finding the unnecessary code already sitting in your diff before it becomes permanent is another.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where I'd Turn It Down
&lt;/h2&gt;

&lt;p&gt;Minimalism isn't universally correct.&lt;/p&gt;

&lt;p&gt;There are situations where writing &lt;em&gt;more&lt;/em&gt; code today deliberately buys optionality, isolation or operational safety tomorrow.&lt;/p&gt;

&lt;p&gt;A payment integration may justify an adapter around a provider SDK because provider substitution and contract isolation are real architectural requirements.&lt;/p&gt;

&lt;p&gt;A distributed workflow may need explicit idempotency state even though the happy-path implementation works perfectly without it.&lt;/p&gt;

&lt;p&gt;A service boundary can be justified by deployment ownership and security isolation even when keeping everything in one process would require fewer lines.&lt;/p&gt;

&lt;p&gt;And sometimes a boring abstraction is valuable because twelve teams need to obey the same contract.&lt;/p&gt;

&lt;p&gt;This is where I'd be careful with &lt;code&gt;ultra&lt;/code&gt; mode.&lt;/p&gt;

&lt;p&gt;The smallest local diff and the lowest long-term system cost are not guaranteed to be the same thing.&lt;/p&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 LOCAL OPTIMUM
                 ─────────────
                 20-line patch
                       │
                       ▼
              Lowest code today


                 SYSTEM OPTIMUM
                 ──────────────
                 60-line adapter
                       │
             ┌─────────┴─────────┐
             ▼                   ▼
      Provider isolation    Stable contract
             │                   │
             └─────────┬─────────┘
                       ▼
              Lower change cost
                 over 3 years
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhii6zuqtjd49cww4i4qd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhii6zuqtjd49cww4i4qd.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ponytail's own rules partially protect against this by requiring the agent to understand the real flow first and by allowing deliberate simplifications to document their known ceiling and upgrade path.&lt;/p&gt;

&lt;p&gt;But the tool cannot know your future business constraints unless those constraints are represented in the codebase, instructions or task context.&lt;/p&gt;

&lt;p&gt;No prompt can.&lt;/p&gt;

&lt;p&gt;That's still our job.&lt;/p&gt;




&lt;h2&gt;
  
  
  At 10× Scale, LOC Probably Isn't What Breaks First
&lt;/h2&gt;

&lt;p&gt;There's another reason I wouldn't turn minimalism into dogma.&lt;/p&gt;

&lt;p&gt;When traffic jumps by an order of magnitude, the first failure usually isn't:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We have too many classes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's something operational.&lt;/p&gt;

&lt;p&gt;Connection-pool exhaustion.&lt;/p&gt;

&lt;p&gt;Queue depth.&lt;/p&gt;

&lt;p&gt;Lock contention.&lt;/p&gt;

&lt;p&gt;A downstream rate limit.&lt;/p&gt;

&lt;p&gt;An unbounded retry loop.&lt;/p&gt;

&lt;p&gt;Memory pressure.&lt;/p&gt;

&lt;p&gt;Hot partitions.&lt;/p&gt;

&lt;p&gt;Timeout budgets that looked generous until every upstream service consumed all of them simultaneously.&lt;/p&gt;

&lt;p&gt;Ponytail can reduce unnecessary implementation surface, but it doesn't remove the need for production reasoning.&lt;/p&gt;

&lt;p&gt;In fact, minimal code makes that reasoning &lt;strong&gt;more important&lt;/strong&gt;, not less.&lt;/p&gt;

&lt;p&gt;If you replace a custom subsystem with a native or standard-library capability, you need to understand that capability's operational ceiling.&lt;/p&gt;

&lt;p&gt;The right question isn't:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which solution has fewer lines?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which solution is the smallest one that still satisfies the system's actual invariants?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a much harder optimization target.&lt;/p&gt;

&lt;p&gt;And a much better one.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installing It Is Appropriately Uneventful
&lt;/h2&gt;

&lt;p&gt;The project supports multiple agent environments. For example, GitHub Copilot CLI currently uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;copilot plugin marketplace add DietrichGebert/ponytail
copilot plugin &lt;span class="nb"&gt;install &lt;/span&gt;ponytail@ponytail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude Code uses its plugin marketplace flow, Codex has a plugin installation path, Gemini CLI can install the repository as an extension, and several editor agents can consume the provided rules files directly. Check the repository's current installation section before installing because agent plugin interfaces are still moving quickly.&lt;/p&gt;

&lt;p&gt;Once active, the useful commands include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/ponytail lite
/ponytail full
/ponytail ultra
/ponytail off

/ponytail-review
/ponytail-audit
/ponytail-debt
/ponytail-gain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I would start with &lt;code&gt;full&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ultra&lt;/code&gt; sounds entertaining.&lt;/p&gt;

&lt;p&gt;Production has cured me of selecting modes based on how entertaining their names are.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bigger Idea Isn't Ponytail
&lt;/h2&gt;

&lt;p&gt;Ponytail is a small project built around a small idea.&lt;/p&gt;

&lt;p&gt;That's why I think it's worth paying attention to.&lt;/p&gt;

&lt;p&gt;We've spent much of the AI coding conversation measuring what models can generate: bigger features, longer autonomous runs, more tool calls, larger repository changes.&lt;/p&gt;

&lt;p&gt;But increasing generation capacity changes the economics of software development.&lt;/p&gt;

&lt;p&gt;When producing code becomes nearly free, &lt;strong&gt;saying no to code becomes more valuable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The next generation of useful agent tooling may therefore look less like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Here are more tools the agent can use."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and more like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Here is the engineering judgment that constrains when
the agent should use them."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ponytail's ladder is one version of that.&lt;/p&gt;

&lt;p&gt;Does the feature need to exist?&lt;/p&gt;

&lt;p&gt;Does the solution already exist?&lt;/p&gt;

&lt;p&gt;Can the language do it?&lt;/p&gt;

&lt;p&gt;Can the platform do it?&lt;/p&gt;

&lt;p&gt;Can something we already depend on do it?&lt;/p&gt;

&lt;p&gt;Only then do we start creating new machinery.&lt;/p&gt;

&lt;p&gt;That's not revolutionary architecture advice.&lt;/p&gt;

&lt;p&gt;It's almost aggressively boring.&lt;/p&gt;

&lt;p&gt;Which is exactly why it resembles senior engineering.&lt;/p&gt;

&lt;p&gt;The codebase doesn't care how impressive the generated solution looked in the terminal.&lt;/p&gt;

&lt;p&gt;Six months later, it only cares how much software we decided to own.&lt;/p&gt;

&lt;p&gt;And the cheapest component to operate, patch, secure, observe and eventually delete remains the one we had enough judgment &lt;strong&gt;not to build&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Article Metadata
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Recommended title:&lt;/strong&gt; Ponytail: Teaching AI Coding Agents the Most Expensive Senior Engineering Skill — Restraint&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Direct technical:&lt;/strong&gt; How Ponytail Reduces Over-Engineering in AI Coding Agents&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opinionated:&lt;/strong&gt; Your AI Coding Agent Writes Too Much Code. Ponytail Has a Point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Curiosity-driven:&lt;/strong&gt; What Happens When You Teach an AI Coding Agent to Be Lazy?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Meta description:&lt;/strong&gt; Ponytail teaches AI coding agents to prefer reuse, native features and smaller diffs. Here's what its architecture, benchmarks and trade-offs reveal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Canonical slug:&lt;/strong&gt; &lt;code&gt;ponytail-ai-coding-agent-over-engineering&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenGraph summary:&lt;/strong&gt; AI agents are getting better at writing code. Ponytail asks a more valuable question: should that code exist at all? A production-minded look at AI-assisted minimalism.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Primary topics:&lt;/strong&gt; AI coding agents, Ponytail, YAGNI, software architecture, code quality, developer productivity&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ponytail.dev/" rel="noopener noreferrer"&gt;Ponytail — Official Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dietrichgebert/ponytail" rel="noopener noreferrer"&gt;DietrichGebert/ponytail — GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Ponytail &lt;code&gt;AGENTS.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Ponytail Agentic Benchmark, June 18, 2026&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>softwaredevelopment</category>
      <category>productivity</category>
      <category>codequality</category>
      <category>ai</category>
    </item>
    <item>
      <title>Fix Next.js "params should be awaited" Error in Next.js 15+</title>
      <dc:creator>Amrishkhan Sheik Abdullah</dc:creator>
      <pubDate>Thu, 20 Aug 2026 06:16:41 +0000</pubDate>
      <link>https://dev.to/amrishkhan05/fix-nextjs-params-should-be-awaited-error-in-nextjs-15-273d</link>
      <guid>https://dev.to/amrishkhan05/fix-nextjs-params-should-be-awaited-error-in-nextjs-15-273d</guid>
      <description>&lt;h1&gt;
  
  
  Fix Next.js "params should be awaited" Error in Next.js 15+
&lt;/h1&gt;

&lt;p&gt;If you are seeing the &lt;strong&gt;params should be awaited Next.js&lt;/strong&gt; error after upgrading to Next.js 15 or following an older App Router tutorial, you are not alone.&lt;/p&gt;

&lt;p&gt;The error usually looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Route "/blog/[slug]" used params.slug. params should be awaited before using its properties.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes it appears with &lt;code&gt;searchParams&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Sometimes it appears with &lt;code&gt;cookies()&lt;/code&gt; or &lt;code&gt;headers()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And sometimes the page still seems to work, but your terminal keeps shouting at you.&lt;/p&gt;

&lt;p&gt;This article will slow it down and explain the fix in a beginner-friendly way.&lt;/p&gt;

&lt;p&gt;No deep framework lecture first. Just the actual problem, the broken code, the fixed code, and the reason it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Error Means in Plain English
&lt;/h2&gt;

&lt;p&gt;In older Next.js code, you may have treated &lt;code&gt;params&lt;/code&gt; like a normal JavaScript object.&lt;/p&gt;

&lt;p&gt;Something like this:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&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;slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That used to feel natural.&lt;/p&gt;

&lt;p&gt;If your route was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/blog/[slug]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the user opened:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/blog/my-first-post
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you expected:&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="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// "my-first-post"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In newer Next.js versions, especially Next.js 15+, some request-based values became asynchronous. That means you should treat them like values that need to be waited for before you read from them.&lt;/p&gt;

&lt;p&gt;So instead of reading &lt;code&gt;params.slug&lt;/code&gt; directly, you do this:&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slug&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;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the heart of the fix.&lt;/p&gt;

&lt;p&gt;The error is not saying your route is missing.&lt;/p&gt;

&lt;p&gt;It is not saying your &lt;code&gt;[slug]&lt;/code&gt; folder is wrong.&lt;/p&gt;

&lt;p&gt;It is saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You are trying to read route data before awaiting it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;The common flow: the page loads, the code reads &lt;code&gt;params.slug&lt;/code&gt; directly, Next.js expects &lt;code&gt;params&lt;/code&gt; to be awaited, and the error appears.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Changed
&lt;/h2&gt;

&lt;p&gt;Next.js has a group of features called &lt;strong&gt;Dynamic APIs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That sounds more complicated than it is.&lt;/p&gt;

&lt;p&gt;In simple terms, Dynamic APIs are values that depend on the current request.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What route did the user open?&lt;/li&gt;
&lt;li&gt;What query string is in the URL?&lt;/li&gt;
&lt;li&gt;What cookies came with this request?&lt;/li&gt;
&lt;li&gt;What headers came with this request?&lt;/li&gt;
&lt;li&gt;Is draft mode enabled?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those values are not the same for every user.&lt;/p&gt;

&lt;p&gt;They depend on the incoming request, so Next.js treats them differently from static code.&lt;/p&gt;

&lt;p&gt;In Next.js 15+, these Dynamic APIs are asynchronous:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;params&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;searchParams&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cookies()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;headers()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;draftMode()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why old examples from Next.js 13 or 14 can suddenly throw warnings or errors after an upgrade.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The same async idea applies to more than just &lt;code&gt;params&lt;/code&gt;. If it depends on the request, check whether it needs &lt;code&gt;await&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Broken Dynamic Route Example
&lt;/h2&gt;

&lt;p&gt;Let's start with the most common case: a blog post page.&lt;/p&gt;

&lt;p&gt;You have this route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/blog/[slug]/page.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;[slug]&lt;/code&gt; part means the route is dynamic.&lt;/p&gt;

&lt;p&gt;So these URLs can all use the same file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/blog/nextjs-routing
/blog/react-state
/blog/my-first-post
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In older tutorials, you may see code like this:&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="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;BlogPostPage&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&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;slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Blog&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&amp;gt;&lt;/span&gt;&lt;span class="err"&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 broken pattern in Next.js 15+.&lt;/p&gt;

&lt;p&gt;The problem is this line:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&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;slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You are reading &lt;code&gt;slug&lt;/code&gt; directly from &lt;code&gt;params&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But Next.js expects you to wait for &lt;code&gt;params&lt;/code&gt; first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fixed Dynamic Route Example
&lt;/h2&gt;

&lt;p&gt;Here is the fixed version:&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="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;BlogPostPage&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slug&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;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Blog&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things changed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The component became &lt;code&gt;async&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We used &lt;code&gt;await params&lt;/code&gt; before reading &lt;code&gt;slug&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is it.&lt;/p&gt;

&lt;p&gt;Small code change, big difference.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Before: read &lt;code&gt;params.slug&lt;/code&gt; directly. After: make the function &lt;code&gt;async&lt;/code&gt;, await &lt;code&gt;params&lt;/code&gt;, then read &lt;code&gt;slug&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Adding &lt;code&gt;async&lt;/code&gt; Matters
&lt;/h2&gt;

&lt;p&gt;This part is important for beginners.&lt;/p&gt;

&lt;p&gt;You cannot use &lt;code&gt;await&lt;/code&gt; inside a normal function.&lt;/p&gt;

&lt;p&gt;This will not work:&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="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;BlogPostPage&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slug&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;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JavaScript will complain because &lt;code&gt;await&lt;/code&gt; needs an &lt;code&gt;async&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;So you need this:&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="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;BlogPostPage&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slug&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;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Think of &lt;code&gt;async&lt;/code&gt; as telling JavaScript:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This function may need to wait for something.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And &lt;code&gt;await&lt;/code&gt; means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Wait here until this value is ready, then continue.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So the pattern is:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Page&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slug&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;params&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;h2&gt;
  
  
  Do Not Await &lt;code&gt;params.slug&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This is a very common mistake.&lt;/p&gt;

&lt;p&gt;You might try this:&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="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;BlogPostPage&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slug&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;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That looks close, but it is not the right fix.&lt;/p&gt;

&lt;p&gt;You are still trying to access &lt;code&gt;.slug&lt;/code&gt; before awaiting &lt;code&gt;params&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The correct order is:&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slug&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;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait for the whole &lt;code&gt;params&lt;/code&gt; object first.&lt;/p&gt;

&lt;p&gt;Then read &lt;code&gt;slug&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing &lt;code&gt;generateMetadata&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This error often appears on blog sites because the page is not the only place where you read &lt;code&gt;params&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You may also use &lt;code&gt;params&lt;/code&gt; inside &lt;code&gt;generateMetadata&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, this is broken:&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateMetadata&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&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;slug&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="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Post: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="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 fixed version is:&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="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;generateMetadata&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slug&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;params&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="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Post: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="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 matters because a lot of developers fix &lt;code&gt;page.js&lt;/code&gt;, refresh the app, and still see the same warning.&lt;/p&gt;

&lt;p&gt;Then they feel stuck.&lt;/p&gt;

&lt;p&gt;The reason is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The same broken &lt;code&gt;params.slug&lt;/code&gt; access may also exist in &lt;code&gt;generateMetadata&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So when you fix a dynamic route, search the whole file for:&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="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not only check the page component.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing &lt;code&gt;searchParams&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Now let's talk about &lt;code&gt;searchParams&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;params&lt;/code&gt; usually comes from the path.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/blog/[slug]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;searchParams&lt;/code&gt; comes from the query string.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/products?page=2&amp;amp;sort=new
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In older code, you might write:&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="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;ProductsPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;searchParams&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;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Next.js 15+, use the async pattern:&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="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;ProductsPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;searchParams&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;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sort&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new&lt;/span&gt;&lt;span class="dl"&gt;"&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;searchParams&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Sort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&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 idea is the same.&lt;/p&gt;

&lt;p&gt;Do not read from &lt;code&gt;searchParams&lt;/code&gt; first.&lt;/p&gt;

&lt;p&gt;Await it first.&lt;/p&gt;

&lt;p&gt;Then read from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing Both &lt;code&gt;params&lt;/code&gt; and &lt;code&gt;searchParams&lt;/code&gt; Together
&lt;/h2&gt;

&lt;p&gt;Sometimes a page needs both.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/category/shoes?page=2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your route might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/category/[slug]/page.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the fixed version:&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="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;CategoryPage&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="nx"&gt;searchParams&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;slug&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;params&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;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&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;searchParams&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Current&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/main&lt;/span&gt;&lt;span class="err"&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;You can await them separately.&lt;/p&gt;

&lt;p&gt;That keeps the code easy to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing &lt;code&gt;cookies()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The same migration affects &lt;code&gt;cookies()&lt;/code&gt; from &lt;code&gt;next/headers&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is the broken version:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cookies&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="s2"&gt;next/headers&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;DashboardPage&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;pre&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/pre&amp;gt;&lt;/span&gt;&lt;span class="err"&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 fixed version:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cookies&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="s2"&gt;next/headers&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;DashboardPage&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;cookieStore&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;cookies&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cookieStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;pre&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/pre&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, the order matters.&lt;/p&gt;

&lt;p&gt;Do not do this:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&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;cookies&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do this:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cookieStore&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;cookies&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cookieStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You wait for the cookie store first.&lt;/p&gt;

&lt;p&gt;Then you read from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing &lt;code&gt;headers()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;headers()&lt;/code&gt; follows the same idea.&lt;/p&gt;

&lt;p&gt;Broken:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;headers&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="s2"&gt;next/headers&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;Page&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;userAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fixed:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;headers&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="s2"&gt;next/headers&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;Page&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;headerStore&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;headers&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;userAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;headerStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you remember the cookie example, this should feel familiar.&lt;/p&gt;

&lt;p&gt;Await the store.&lt;/p&gt;

&lt;p&gt;Then read from the store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Route Handler Example
&lt;/h2&gt;

&lt;p&gt;This error can also show up in route handlers.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/api/users/[id]/route.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Broken:&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="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;GET&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="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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&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="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&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="na"&gt;userId&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fixed:&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="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;GET&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="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="kd"&gt;const&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="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;params&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;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="na"&gt;userId&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file is different, but the fix is the same.&lt;/p&gt;

&lt;p&gt;If the handler receives &lt;code&gt;params&lt;/code&gt;, await it before reading &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;slug&lt;/code&gt;, or any other dynamic value.&lt;/p&gt;

&lt;h2&gt;
  
  
  TypeScript Version
&lt;/h2&gt;

&lt;p&gt;If you use TypeScript, update the type too.&lt;/p&gt;

&lt;p&gt;Broken:&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;type&lt;/span&gt; &lt;span class="nx"&gt;PageProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;slug&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="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;BlogPostPage&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="nx"&gt;PageProps&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fixed:&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;type&lt;/span&gt; &lt;span class="nx"&gt;PageProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;slug&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="o"&gt;&amp;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="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;BlogPostPage&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="nx"&gt;PageProps&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;slug&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;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&amp;gt;&lt;/span&gt;&lt;span class="err"&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 where many build errors come from.&lt;/p&gt;

&lt;p&gt;The code may look logically correct, but the type still says &lt;code&gt;params&lt;/code&gt; is a plain object.&lt;/p&gt;

&lt;p&gt;In Next.js 15+, type it like a Promise when you are using this async pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Codemod Can Help
&lt;/h2&gt;

&lt;p&gt;Next.js provides a codemod for this migration.&lt;/p&gt;

&lt;p&gt;A codemod is a script that updates code automatically.&lt;/p&gt;

&lt;p&gt;You can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @next/codemod@latest next-async-request-api &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can update many &lt;code&gt;params&lt;/code&gt;, &lt;code&gt;searchParams&lt;/code&gt;, &lt;code&gt;cookies()&lt;/code&gt;, and &lt;code&gt;headers()&lt;/code&gt; usages for you.&lt;/p&gt;

&lt;p&gt;But do not run it and assume everything is perfect.&lt;/p&gt;

&lt;p&gt;After the codemod, review the changed files.&lt;/p&gt;

&lt;p&gt;Pay extra attention to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;custom TypeScript types&lt;/li&gt;
&lt;li&gt;helper functions&lt;/li&gt;
&lt;li&gt;auth utilities&lt;/li&gt;
&lt;li&gt;Supabase or other server clients&lt;/li&gt;
&lt;li&gt;&lt;code&gt;generateMetadata&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;route handlers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The codemod is useful, but your app still needs a human review.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Mistake 1: Awaiting the Property Instead of &lt;code&gt;params&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Avoid this:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slug&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;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use this:&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slug&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;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Await the object first.&lt;/p&gt;

&lt;p&gt;Then read the property.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 2: Forgetting &lt;code&gt;async&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Avoid this:&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="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;Page&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slug&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;params&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;Use this:&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="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;Page&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slug&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;params&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;If there is &lt;code&gt;await&lt;/code&gt;, the function needs &lt;code&gt;async&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 3: Fixing the Page but Forgetting Metadata
&lt;/h3&gt;

&lt;p&gt;You may fix this:&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="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;Page&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slug&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;params&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;But forget this:&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateMetadata&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&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;slug&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;If the warning still appears, search the file for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;params.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mistake 4: Copying Old Tutorial Code
&lt;/h3&gt;

&lt;p&gt;This one is common.&lt;/p&gt;

&lt;p&gt;You follow a tutorial.&lt;/p&gt;

&lt;p&gt;The tutorial uses:&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="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your app uses Next.js 15 or newer.&lt;/p&gt;

&lt;p&gt;Now you get an error.&lt;/p&gt;

&lt;p&gt;That does not mean the tutorial is completely useless. It may just be written for an older version of Next.js.&lt;/p&gt;

&lt;p&gt;Update the request-data parts to the newer async pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 5: Thinking the Route Folder Is Wrong
&lt;/h3&gt;

&lt;p&gt;When beginners see this error, they sometimes rename folders or move files around.&lt;/p&gt;

&lt;p&gt;Usually, that is not needed.&lt;/p&gt;

&lt;p&gt;If your route is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/blog/[slug]/page.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;that folder structure is fine.&lt;/p&gt;

&lt;p&gt;The issue is usually inside the code:&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="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;not the route folder itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beginner Debugging Checklist
&lt;/h2&gt;

&lt;p&gt;Use this when the error will not go away.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A quick checklist for finding the common places where this error hides.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ask these questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is this a dynamic route like &lt;code&gt;[slug]&lt;/code&gt;, &lt;code&gt;[id]&lt;/code&gt;, or &lt;code&gt;[category]&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Am I reading &lt;code&gt;params.slug&lt;/code&gt;, &lt;code&gt;params.id&lt;/code&gt;, or another property directly?&lt;/li&gt;
&lt;li&gt;Did I make the function &lt;code&gt;async&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Did I use &lt;code&gt;const { slug } = await params&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Did I also check &lt;code&gt;generateMetadata&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Am I using &lt;code&gt;searchParams&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Am I using &lt;code&gt;cookies()&lt;/code&gt; or &lt;code&gt;headers()&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Do my TypeScript types say &lt;code&gt;params&lt;/code&gt; is a Promise?&lt;/li&gt;
&lt;li&gt;Did I run the codemod and review the result?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most of the time, one of these checks will reveal the issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Fix Cheatsheet
&lt;/h2&gt;

&lt;p&gt;Here are the patterns to remember.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;params&lt;/code&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slug&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;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;code&gt;searchParams&lt;/code&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&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;searchParams&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;code&gt;cookies()&lt;/code&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cookieStore&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;cookies&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cookieStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;code&gt;headers()&lt;/code&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headerStore&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;headers&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;userAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;headerStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For TypeScript:&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;type&lt;/span&gt; &lt;span class="nx"&gt;PageProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;slug&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="o"&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;h2&gt;
  
  
  How This Relates to Other Next.js Bugs
&lt;/h2&gt;

&lt;p&gt;This error is part of a bigger pattern in Next.js.&lt;/p&gt;

&lt;p&gt;Some bugs happen because code runs in a different place than you expected.&lt;/p&gt;

&lt;p&gt;For example, in my previous article on &lt;a href="https://www.amrishkhan.dev/blog/the-nextjs-hydration-error-everyone-hits-and-how-to-actually-fix-it-35aa" rel="noopener noreferrer"&gt;fixing Next.js hydration errors with localStorage&lt;/a&gt;, the problem was server HTML not matching the first client render:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Server render and browser render did not match.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the middleware redirect loop article, the problem was request flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/dashboard redirects to /login
/login also triggers middleware
the app loops
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This article is also about flow.&lt;/p&gt;

&lt;p&gt;But this time, the flow is about when request data is ready:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wait for request data
then read from it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you see it that way, the error becomes much less scary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Next.js params should be awaited&lt;/strong&gt; error usually means you are reading request-based data too early.&lt;/p&gt;

&lt;p&gt;In older code, this looked normal:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&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;slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Next.js 15+, use this:&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slug&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;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And remember the simple rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Treat route and request data as async. Await it first, then read from it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That rule applies to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;params&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;searchParams&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cookies()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;headers()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;draftMode()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are upgrading a project, run the codemod, then review your dynamic routes, metadata functions, route handlers, and TypeScript types.&lt;/p&gt;

&lt;p&gt;I write practical debugging guides and build full-stack products. You can find more of my work at &lt;a href="https://www.amrishkhan.dev" rel="noopener noreferrer"&gt;amrishkhan.dev&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Suggested Internal Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Previous article: &lt;a href="https://www.amrishkhan.dev/blog/the-nextjs-hydration-error-everyone-hits-and-how-to-actually-fix-it-35aa" rel="noopener noreferrer"&gt;Fix Next.js Hydration Error with localStorage&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Next.js docs: &lt;a href="https://nextjs.org/docs/messages/sync-dynamic-apis" rel="noopener noreferrer"&gt;Dynamic APIs are Asynchronous&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Next.js docs: &lt;a href="https://nextjs.org/docs/app/guides/upgrading/version-15" rel="noopener noreferrer"&gt;Upgrading to Version 15&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Next.js docs: &lt;a href="https://nextjs.org/docs/app/api-reference/functions/cookies" rel="noopener noreferrer"&gt;cookies&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Next.js docs: &lt;a href="https://nextjs.org/docs/app/api-reference/file-conventions/dynamic-routes" rel="noopener noreferrer"&gt;Dynamic Segments&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Fix Next.js Middleware Redirect Loop: Why Your Login Page Keeps Redirecting</title>
      <dc:creator>Amrishkhan Sheik Abdullah</dc:creator>
      <pubDate>Wed, 19 Aug 2026 07:14:44 +0000</pubDate>
      <link>https://dev.to/amrishkhan05/fix-nextjs-middleware-redirect-loop-why-your-login-page-keeps-redirecting-3m8d</link>
      <guid>https://dev.to/amrishkhan05/fix-nextjs-middleware-redirect-loop-why-your-login-page-keeps-redirecting-3m8d</guid>
      <description>&lt;p&gt;This is the third article in &lt;strong&gt;Real Coding Problems, Simple Fixes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This one is about a problem that can waste hours because the app looks like it is doing the right thing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Protect private pages and redirect logged-out users to &lt;code&gt;/login&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But then the browser shows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERR_TOO_MANY_REDIRECTS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or your Next.js app keeps sending users back to the login page again and again.&lt;/p&gt;

&lt;p&gt;That is usually a &lt;strong&gt;Next.js middleware redirect loop&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The cause is often simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your middleware is protecting the login page too.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's fix it with clear code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Are Fixing
&lt;/h2&gt;

&lt;p&gt;If you are dealing with this bug, it usually looks like one of these problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Next.js middleware redirect loop&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Next.js login page keeps redirecting&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ERR_TOO_MANY_REDIRECTS Next.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NextAuth middleware redirect loop&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Next.js middleware matcher exclude login page&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Next.js protected routes middleware&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will use a simple auth-token example first, then talk about NextAuth/Auth.js after.&lt;/p&gt;

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

&lt;p&gt;Imagine your app has these routes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/login
/dashboard
/settings
/api/auth/login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You want this behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Logged out user opens /dashboard
Redirect to /login

Logged out user opens /login
Show the login page

Logged in user opens /dashboard
Show the dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That sounds straightforward.&lt;/p&gt;

&lt;p&gt;So you write middleware to check whether the user has an auth token.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Broken Middleware
&lt;/h2&gt;

&lt;p&gt;Here is a common broken version:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&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="s2"&gt;next/server&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;middleware&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auth_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;token&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="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&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;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;url&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="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&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 looks reasonable at first.&lt;/p&gt;

&lt;p&gt;If there is no token, send the user to &lt;code&gt;/login&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But there is a hidden problem.&lt;/p&gt;

&lt;p&gt;This middleware runs for more routes than you think.&lt;/p&gt;

&lt;p&gt;So the request flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User opens /dashboard
No token found
Redirect to /login

Browser opens /login
Middleware runs again
Still no token found
Redirect to /login again

Browser opens /login again
Middleware runs again
Still no token found
Redirect to /login again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the loop.&lt;/p&gt;

&lt;p&gt;The login page can never load because the middleware keeps redirecting it to itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Plain-English Explanation
&lt;/h2&gt;

&lt;p&gt;Middleware runs before the page is rendered.&lt;/p&gt;

&lt;p&gt;That makes it useful for auth checks, redirects, headers, rewrites, and request decisions.&lt;/p&gt;

&lt;p&gt;But it also means you need to be specific.&lt;/p&gt;

&lt;p&gt;If your middleware says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every logged-out request must go to &lt;code&gt;/login&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;then &lt;code&gt;/login&lt;/code&gt; itself also becomes a logged-out request.&lt;/p&gt;

&lt;p&gt;So the app gets stuck.&lt;/p&gt;

&lt;p&gt;The fix is not complicated:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do not run auth protection on public routes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Your public routes usually include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/login&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/register&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/forgot-password&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/auth&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;static files&lt;/li&gt;
&lt;li&gt;images&lt;/li&gt;
&lt;li&gt;favicon&lt;/li&gt;
&lt;li&gt;auth callback routes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fix 1: Add Public Route Checks
&lt;/h2&gt;

&lt;p&gt;The first fix is to define which routes are public.&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&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="s2"&gt;next/server&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;publicRoutes&lt;/span&gt; &lt;span class="o"&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;/login&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;/register&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;/forgot-password&lt;/span&gt;&lt;span class="dl"&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;isPublicRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pathname&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="nx"&gt;publicRoutes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;route&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;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;route&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;function&lt;/span&gt; &lt;span class="nf"&gt;middleware&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;pathname&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextUrl&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auth_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isPublicRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pathname&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="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;token&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;loginUrl&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;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;loginUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;from&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pathname&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;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginUrl&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="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&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;Now the request flow is different:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User opens /dashboard
No token found
Redirect to /login?from=/dashboard

Browser opens /login
/login is public
Show login page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That small public route check breaks the loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 2: Use &lt;code&gt;matcher&lt;/code&gt; to Protect Only Private Routes
&lt;/h2&gt;

&lt;p&gt;Another good fix is to tell Next.js exactly where the middleware should run.&lt;/p&gt;

&lt;p&gt;If only &lt;code&gt;/dashboard&lt;/code&gt; and &lt;code&gt;/settings&lt;/code&gt; need protection, the middleware should not run everywhere.&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&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="s2"&gt;next/server&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;middleware&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auth_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;token&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;loginUrl&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;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;loginUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;from&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;nextUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&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;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginUrl&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="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&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;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;matcher&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;/dashboard/:path*&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;/settings/:path*&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is often cleaner.&lt;/p&gt;

&lt;p&gt;With this setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/dashboard      -&amp;gt; middleware runs
/dashboard/team -&amp;gt; middleware runs
/settings       -&amp;gt; middleware runs
/login          -&amp;gt; middleware does not run
/api/auth/login -&amp;gt; middleware does not run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For many apps, this is the simplest and safest solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Fix Should You Use?
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;matcher&lt;/code&gt; when you have a clear list of private route groups.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/dashboard
/account
/admin
/settings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use public route checks when your app has more complex rules.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Some pages are public
Some pages are private
Some pages depend on role
Some pages depend on onboarding status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also combine both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use &lt;code&gt;matcher&lt;/code&gt; to avoid static files and unrelated routes&lt;/li&gt;
&lt;li&gt;use route checks inside middleware for auth-specific decisions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fix 3: Avoid Redirecting Logged-In Users Back to Login
&lt;/h2&gt;

&lt;p&gt;There is another common issue.&lt;/p&gt;

&lt;p&gt;After login, the user visits &lt;code&gt;/login&lt;/code&gt; again and still sees the login page.&lt;/p&gt;

&lt;p&gt;Usually, logged-in users should be redirected away from auth pages.&lt;/p&gt;

&lt;p&gt;You can handle that like this:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&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="s2"&gt;next/server&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;authRoutes&lt;/span&gt; &lt;span class="o"&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;/login&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;/register&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;privateRoutes&lt;/span&gt; &lt;span class="o"&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;/dashboard&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;/settings&lt;/span&gt;&lt;span class="dl"&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;matchesRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;routes&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="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;route&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;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;route&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;function&lt;/span&gt; &lt;span class="nf"&gt;middleware&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;pathname&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextUrl&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auth_token&lt;/span&gt;&lt;span class="dl"&gt;"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isAuthRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;matchesRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authRoutes&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;isPrivateRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;matchesRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;privateRoutes&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;isAuthRoute&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;token&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="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&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;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;url&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;isPrivateRoute&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;token&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;loginUrl&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;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;loginUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;from&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pathname&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;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginUrl&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="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&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;Now the rules are clear:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Logged-out user on /dashboard -&amp;gt; send to /login
Logged-out user on /login -&amp;gt; allow
Logged-in user on /login -&amp;gt; send to /dashboard
Logged-in user on /dashboard -&amp;gt; allow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the mental model you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 4: Exclude API Routes and Static Files
&lt;/h2&gt;

&lt;p&gt;If your middleware runs too broadly, it can also touch routes it should ignore.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/_next/static
/_next/image
/favicon.ico
/api/auth/login
/api/auth/callback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You usually do not want your page-auth middleware interfering with those.&lt;/p&gt;

&lt;p&gt;Here is a common matcher pattern:&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;matcher&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;/((?!api|_next/static|_next/image|favicon.ico).*)&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;This tells middleware to run on most pages, but skip API routes and common Next.js asset paths.&lt;/p&gt;

&lt;p&gt;Be careful with this pattern though.&lt;/p&gt;

&lt;p&gt;If your login page is still inside that matched group, you still need a public route check inside middleware.&lt;/p&gt;

&lt;p&gt;That is why I prefer being explicit for beginner-friendly auth:&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;matcher&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;/dashboard/:path*&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;/settings/:path*&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;/account/:path*&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is easier to reason about.&lt;/p&gt;

&lt;h2&gt;
  
  
  NextAuth/Auth.js Redirect Loop Note
&lt;/h2&gt;

&lt;p&gt;If you use NextAuth or Auth.js, redirect loops usually come from one of these problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your custom sign-in page is also protected by middleware.&lt;/li&gt;
&lt;li&gt;Your middleware does not know about your custom &lt;code&gt;signIn&lt;/code&gt; page.&lt;/li&gt;
&lt;li&gt;Your callback or auth API route is being matched accidentally.&lt;/li&gt;
&lt;li&gt;The user is redirected after login to a route that immediately fails another middleware rule.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a custom sign-in page, make sure your auth config and middleware agree about the sign-in route.&lt;/p&gt;

&lt;p&gt;Example idea:&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;signIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/login&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;And make sure &lt;code&gt;/login&lt;/code&gt; is not treated like a protected page.&lt;/p&gt;

&lt;p&gt;If your app uses a matcher like this:&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;matcher&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;/dashboard/:path*&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then &lt;code&gt;/login&lt;/code&gt; will not be protected by that middleware.&lt;/p&gt;

&lt;p&gt;That alone fixes many redirect loops.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Mistake 1: Matching Every Route
&lt;/h3&gt;

&lt;p&gt;This is risky:&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;matcher&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;/:path*&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can work, but only if your internal route logic is careful.&lt;/p&gt;

&lt;p&gt;If you are new to middleware, start by matching only private sections.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 2: Redirecting to the Same URL
&lt;/h3&gt;

&lt;p&gt;Before redirecting, think:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Am I already on the page I am redirecting to?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the user is already on &lt;code&gt;/login&lt;/code&gt;, do not redirect them to &lt;code&gt;/login&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 3: Protecting Auth Callback Routes
&lt;/h3&gt;

&lt;p&gt;Auth libraries often need callback routes to finish login.&lt;/p&gt;

&lt;p&gt;If middleware blocks those routes, login may succeed with the provider but fail when the app receives the callback.&lt;/p&gt;

&lt;p&gt;Watch routes like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/api/auth/callback
/api/auth/session
/auth/callback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact paths depend on your auth library.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 4: Forgetting Role-Based Redirect Loops
&lt;/h3&gt;

&lt;p&gt;Redirect loops are not only about login.&lt;/p&gt;

&lt;p&gt;They can also happen with roles.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User opens /admin
Middleware says user is not admin
Redirect to /dashboard

Middleware checks /dashboard
Onboarding is incomplete
Redirect to /onboarding

Middleware checks /onboarding
Some rule sends user back to /dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix is the same:&lt;/p&gt;

&lt;p&gt;Make every redirect rule clear, and make sure the destination route is allowed for that user state.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Simple Debugging Checklist
&lt;/h2&gt;

&lt;p&gt;When your Next.js login page keeps redirecting, check this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does middleware run on &lt;code&gt;/login&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Does middleware run on &lt;code&gt;/api/auth&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Does your matcher include too many routes?&lt;/li&gt;
&lt;li&gt;Are you redirecting to the same route you are already on?&lt;/li&gt;
&lt;li&gt;Does your custom sign-in page match your auth config?&lt;/li&gt;
&lt;li&gt;Are callback routes excluded?&lt;/li&gt;
&lt;li&gt;After login, are you redirecting to a route the user is actually allowed to access?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you are stuck, add temporary logging:&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;middleware&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="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="s2"&gt;middleware path:&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;nextUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&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;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&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;Then reload the page and watch which routes middleware is touching.&lt;/p&gt;

&lt;p&gt;The path list usually reveals the problem quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mental Model
&lt;/h2&gt;

&lt;p&gt;Middleware redirect bugs become easier when you think in flows.&lt;/p&gt;

&lt;p&gt;Do not only ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Is the user logged in?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Which route is the user trying to open?
Is that route public or private?
If I redirect, will the destination route be allowed?
Will middleware run again on the destination route?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last question is the important one.&lt;/p&gt;

&lt;p&gt;Because every redirect creates another request.&lt;/p&gt;

&lt;p&gt;And middleware can run again on that new request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;A Next.js middleware redirect loop usually happens because the middleware is too broad.&lt;/p&gt;

&lt;p&gt;Most commonly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/dashboard redirects to /login
/login also runs middleware
/login redirects to /login again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The practical fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Protect only private routes
+ Keep login/register pages public
+ Exclude auth API and callback routes
+ Use matcher carefully
+ Never redirect a route to itself
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once your routes are separated into public, auth-only, and private groups, the loop becomes much easier to avoid.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing SEO Notes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SEO title:
Fix Next.js Middleware Redirect Loop: Protect Routes Without Breaking Login

Meta description:
Learn why a Next.js middleware redirect loop happens, why login pages get accidentally protected, and how to fix protected routes with matcher and public route checks.

Suggested slug:
fix-nextjs-middleware-redirect-loop-protected-routes

Primary keyword:
Next.js middleware redirect loop

Secondary keywords:
Next.js auth redirect loop
Next.js login page keeps redirecting
ERR_TOO_MANY_REDIRECTS Next.js
NextAuth middleware redirect loop
Next.js middleware matcher exclude login page
Next.js protected routes middleware
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These notes are for publishing. You can keep them out of the visible article body if your CMS already has dedicated SEO fields.&lt;/p&gt;

&lt;h2&gt;
  
  
  Suggested Internal Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Previous article: &lt;code&gt;Fix Next.js Hydration Error with localStorage&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Related topic idea: &lt;code&gt;Fix Axios Refresh Token Infinite Loop in React and Node.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Portfolio CTA: &lt;code&gt;I write practical engineering guides and build full-stack products. See more at amrishkhan.dev.&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Next.js docs: &lt;a href="https://nextjs.org/docs/13/app/building-your-application/routing/middleware" rel="noopener noreferrer"&gt;Middleware&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Next.js docs: &lt;a href="https://nextjs.org/docs/15/pages/api-reference/file-conventions/middleware" rel="noopener noreferrer"&gt;File-system convention: middleware&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NextAuth docs: &lt;a href="https://next-auth.js.org/configuration/nextjs" rel="noopener noreferrer"&gt;Next.js middleware configuration&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Next.js docs: &lt;a href="https://nextjs.org/docs/app/guides/redirecting" rel="noopener noreferrer"&gt;Redirecting&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Next.js Hydration Error Everyone Hits and How to Actually Fix It</title>
      <dc:creator>Amrishkhan Sheik Abdullah</dc:creator>
      <pubDate>Tue, 18 Aug 2026 05:21:30 +0000</pubDate>
      <link>https://dev.to/amrishkhan05/the-nextjs-hydration-error-everyone-hits-and-how-to-actually-fix-it-35aa</link>
      <guid>https://dev.to/amrishkhan05/the-nextjs-hydration-error-everyone-hits-and-how-to-actually-fix-it-35aa</guid>
      <description>&lt;p&gt;This is the second article in &lt;strong&gt;Real Coding Problems, Simple Fixes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The first article was about a React search box showing old results because API responses came back in the wrong order.&lt;/p&gt;

&lt;p&gt;This one is a little harder.&lt;/p&gt;

&lt;p&gt;We are going to talk about one of the most confusing errors people hit when moving from normal React to Next.js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hydration failed because the server rendered HTML didn't match the client.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That error sounds serious. It also sounds like React is angry about something deep inside the framework.&lt;/p&gt;

&lt;p&gt;Most of the time, the problem is much simpler:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The server rendered one version of the page, but the browser rendered a different version when React started.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's break that down with a real example.&lt;/p&gt;

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

&lt;p&gt;Imagine you are building a dashboard.&lt;/p&gt;

&lt;p&gt;The dashboard has a theme toggle. If the user selected dark mode earlier, you store that choice in &lt;code&gt;localStorage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That feels normal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;theme = "dark"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So you write a component that reads the saved theme and renders the button text.&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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="s2"&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;ThemeToggle&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;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTheme&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="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;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toggleTheme&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;nextTheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light&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;dark&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;light&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;setTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextTheme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nextTheme&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;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;toggleTheme&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      Current theme: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;theme&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;button&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;If you are coming from a client-only React app, this may look fine.&lt;/p&gt;

&lt;p&gt;But in Next.js, this can break.&lt;/p&gt;

&lt;p&gt;You may see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ReferenceError: localStorage is not defined
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hydration failed because the server rendered HTML didn't match the client.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The confusing part is that the file already says:&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So why is this still a problem?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;code&gt;"use client"&lt;/code&gt; Does Not Mean "Only Runs in the Browser"
&lt;/h2&gt;

&lt;p&gt;This is the part that trips up a lot of developers.&lt;/p&gt;

&lt;p&gt;In Next.js App Router, &lt;code&gt;"use client"&lt;/code&gt; means the component is allowed to use browser-side React features like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;useState&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;useEffect&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;event handlers&lt;/li&gt;
&lt;li&gt;browser interactions after the page loads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the first HTML can still be prepared before the browser fully takes over.&lt;/p&gt;

&lt;p&gt;That means your first render must be safe for both worlds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the server&lt;/li&gt;
&lt;li&gt;the browser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The server does not have:&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="nb"&gt;window&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;
&lt;span class="nx"&gt;localStorage&lt;/span&gt;
&lt;span class="nx"&gt;sessionStorage&lt;/span&gt;
&lt;span class="nx"&gt;matchMedia&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those belong to the browser.&lt;/p&gt;

&lt;p&gt;So if your render logic depends on them too early, Next.js can end up with different output on the server and the client.&lt;/p&gt;

&lt;p&gt;That difference is what causes the hydration error.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Hydration Means in Plain English
&lt;/h2&gt;

&lt;p&gt;Think of Next.js like a restaurant preparing your table before you arrive.&lt;/p&gt;

&lt;p&gt;The server sends ready-made HTML so the page appears quickly.&lt;/p&gt;

&lt;p&gt;Then React loads in the browser and attaches all the interactive behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;clicks&lt;/li&gt;
&lt;li&gt;state&lt;/li&gt;
&lt;li&gt;event handlers&lt;/li&gt;
&lt;li&gt;updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That process is hydration.&lt;/p&gt;

&lt;p&gt;For hydration to work cleanly, React expects the first browser render to match the HTML that came from the server.&lt;/p&gt;

&lt;p&gt;If the server rendered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Current theme: light&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but the browser immediately renders:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Current theme: dark&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;React sees a mismatch.&lt;/p&gt;

&lt;p&gt;That is the error.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bad Pattern
&lt;/h2&gt;

&lt;p&gt;Here is the common mistake:&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTheme&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="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;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reads &lt;code&gt;localStorage&lt;/code&gt; while React is calculating the initial render.&lt;/p&gt;

&lt;p&gt;That is too early.&lt;/p&gt;

&lt;p&gt;The server cannot read &lt;code&gt;localStorage&lt;/code&gt;, and even if you add checks like this:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;savedTheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;theme&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you can still create different server and client output.&lt;/p&gt;

&lt;p&gt;The server may render &lt;code&gt;light&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The browser may instantly render &lt;code&gt;dark&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now the first HTML does not match.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 1: Start With a Safe Default, Then Read Browser Storage
&lt;/h2&gt;

&lt;p&gt;The safest beginner-friendly fix is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Render the same default value on the server and first client render.&lt;/li&gt;
&lt;li&gt;Read &lt;code&gt;localStorage&lt;/code&gt; inside &lt;code&gt;useEffect&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Update the UI after the component has mounted.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is the fixed version.&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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;useEffect&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="s2"&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;ThemeToggle&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;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTheme&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="s2"&gt;light&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isReady&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsReady&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="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;savedTheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;savedTheme&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;savedTheme&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dark&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="nf"&gt;setTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;savedTheme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;setIsReady&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;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toggleTheme&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;nextTheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light&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;dark&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;light&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;setTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextTheme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nextTheme&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isReady&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;button&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Loading theme...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&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="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;toggleTheme&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      Current theme: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;theme&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;button&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;Now the first render is predictable.&lt;/p&gt;

&lt;p&gt;The component starts with:&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTheme&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="s2"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, after the browser is ready, &lt;code&gt;useEffect&lt;/code&gt; reads &lt;code&gt;localStorage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Why does this work?&lt;/p&gt;

&lt;p&gt;Because &lt;code&gt;useEffect&lt;/code&gt; only runs in the browser after React has rendered.&lt;/p&gt;

&lt;p&gt;So the server is no longer asked to read something it does not have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 2: Create a Reusable &lt;code&gt;useMounted&lt;/code&gt; Hook
&lt;/h2&gt;

&lt;p&gt;If you do this in multiple components, repeating &lt;code&gt;isReady&lt;/code&gt; everywhere gets boring.&lt;/p&gt;

&lt;p&gt;You can create a small hook:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&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="s2"&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useMounted&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;mounted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setMounted&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="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&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="nf"&gt;setMounted&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;mounted&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;Then use it like this:&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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;useEffect&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="s2"&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useMounted&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="s2"&gt;./useMounted&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;ThemeToggle&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;mounted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMounted&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;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTheme&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="s2"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;mounted&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;savedTheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;savedTheme&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;savedTheme&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dark&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="nf"&gt;setTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;savedTheme&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="nx"&gt;mounted&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;toggleTheme&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;nextTheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light&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;dark&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;light&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;setTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextTheme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nextTheme&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;mounted&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;button&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Loading theme...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&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="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;toggleTheme&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      Current theme: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;theme&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;button&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 hook makes the intent clear:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do not show browser-dependent UI until the component is mounted in the browser.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is useful for theme, cart count, persisted filters, saved sidebar state, or anything else stored in the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 3: Build a Small &lt;code&gt;useLocalStorageValue&lt;/code&gt; Hook
&lt;/h2&gt;

&lt;p&gt;For a real project, I prefer making storage access explicit and reusable.&lt;/p&gt;

&lt;p&gt;Here is a simple hook:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&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="s2"&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useLocalStorageValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;defaultValue&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;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setValue&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="nx"&gt;defaultValue&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;ready&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setReady&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="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storedValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;storedValue&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;storedValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;setReady&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;span class="nx"&gt;key&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;updateValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nextValue&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="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;updateValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ready&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;Now the component becomes smaller:&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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;useLocalStorageValue&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="s2"&gt;./useLocalStorageValue&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;ThemeToggle&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="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;setTheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLocalStorageValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;theme&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;light&lt;/span&gt;&lt;span class="dl"&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;toggleTheme&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;nextTheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light&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;dark&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;light&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;setTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextTheme&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ready&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;button&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Loading theme...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&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="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;toggleTheme&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      Current theme: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;theme&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;button&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 not a perfect hook for every storage case. For example, it only stores strings.&lt;/p&gt;

&lt;p&gt;But the pattern is the important part:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use a safe default&lt;/li&gt;
&lt;li&gt;read browser storage after mount&lt;/li&gt;
&lt;li&gt;avoid changing the first render unexpectedly&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fix 4: Use &lt;code&gt;dynamic&lt;/code&gt; With &lt;code&gt;ssr: false&lt;/code&gt; for Browser-Only Widgets
&lt;/h2&gt;

&lt;p&gt;Sometimes a component is completely browser-only.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a chart library that needs &lt;code&gt;window&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a map widget&lt;/li&gt;
&lt;li&gt;a rich text editor&lt;/li&gt;
&lt;li&gt;a component that depends heavily on browser APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For those cases, you can tell Next.js not to server-render that component.&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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="nx"&gt;dynamic&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/dynamic&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;BrowserOnlyChart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./BrowserOnlyChart&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;ssr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AnalyticsPage&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;main&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;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Analytics&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&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;BrowserOnlyChart&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;main&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 can be the right fix when the component simply cannot produce meaningful HTML on the server.&lt;/p&gt;

&lt;p&gt;In the App Router, keep this &lt;code&gt;dynamic&lt;/code&gt; usage inside a Client Component file. If you try to use &lt;code&gt;ssr: false&lt;/code&gt; directly inside a Server Component, Next.js will complain.&lt;/p&gt;

&lt;p&gt;But do not use it everywhere.&lt;/p&gt;

&lt;p&gt;If you disable server rendering for too much of your page, you lose some of the benefits of Next.js:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;faster first HTML&lt;/li&gt;
&lt;li&gt;better SEO for meaningful content&lt;/li&gt;
&lt;li&gt;less useful server-rendered output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use it when the component is truly browser-only.&lt;/p&gt;

&lt;h2&gt;
  
  
  What About &lt;code&gt;suppressHydrationWarning&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;Next.js also gives you an escape hatch:&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;suppressHydrationWarning&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLocaleTimeString&lt;/span&gt;&lt;span class="p"&gt;()&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;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells React:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I know this content may be different. Do not warn me for this specific element.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This can be fine for small unavoidable differences, like a timestamp.&lt;/p&gt;

&lt;p&gt;But it should not be your first fix for normal app state.&lt;/p&gt;

&lt;p&gt;If your cart count, theme, auth UI, or dashboard content is mismatching, hiding the warning does not fix the actual problem. It only makes the warning quieter.&lt;/p&gt;

&lt;p&gt;Most of the time, you should fix the render flow instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Simple Debugging Checklist
&lt;/h2&gt;

&lt;p&gt;When you see a hydration error, ask these questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Am I reading &lt;code&gt;window&lt;/code&gt;, &lt;code&gt;document&lt;/code&gt;, or &lt;code&gt;localStorage&lt;/code&gt; during render?&lt;/li&gt;
&lt;li&gt;Am I rendering &lt;code&gt;Date.now()&lt;/code&gt; or &lt;code&gt;Math.random()&lt;/code&gt; directly in JSX?&lt;/li&gt;
&lt;li&gt;Does the server render one value while the browser immediately renders another?&lt;/li&gt;
&lt;li&gt;Is my first render dependent on auth, theme, cart, or browser storage?&lt;/li&gt;
&lt;li&gt;Is a third-party component using browser APIs before mount?&lt;/li&gt;
&lt;li&gt;Is my HTML valid, or do I have tags nested incorrectly?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The server HTML and the first client render should match.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After that first render, React can update the UI normally.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mental Model
&lt;/h2&gt;

&lt;p&gt;Here is the easiest way to remember it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Server render:
Should not depend on browser-only data.

First client render:
Should match what the server rendered.

After mount:
Safe to read localStorage, window, document, and other browser APIs.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you keep that order in your head, hydration errors become much less mysterious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;Hydration errors are not random.&lt;/p&gt;

&lt;p&gt;They usually happen because your first browser render does not match the HTML that came from the server.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;localStorage&lt;/code&gt;, the practical fix is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Start with a safe default
+ Read localStorage inside useEffect
+ Render browser-only UI after mount
+ Use dynamic imports only for truly browser-only components
+ Avoid suppressHydrationWarning unless you really mean it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you understand that, the error message becomes less scary.&lt;/p&gt;

&lt;p&gt;It is just React saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I expected the first client render to match the server HTML, but it did not.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And now you know where to look.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Next.js docs: &lt;a href="https://nextjs.org/docs/messages/react-hydration-error" rel="noopener noreferrer"&gt;Text content does not match server-rendered HTML&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;React docs: &lt;a href="https://react.dev/reference/react-dom/client/hydrateRoot" rel="noopener noreferrer"&gt;hydrateRoot&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;React docs: &lt;a href="https://react.dev/reference/react/useEffect" rel="noopener noreferrer"&gt;useEffect&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Next.js docs: &lt;a href="https://nextjs.org/docs/app/guides/lazy-loading" rel="noopener noreferrer"&gt;Lazy loading Client Components and libraries&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The React Search Bug That Shows Old Results and How to Fix It</title>
      <dc:creator>Amrishkhan Sheik Abdullah</dc:creator>
      <pubDate>Mon, 17 Aug 2026 13:18:00 +0000</pubDate>
      <link>https://dev.to/amrishkhan05/the-react-search-bug-that-shows-old-results-and-how-to-fix-it-3oi7</link>
      <guid>https://dev.to/amrishkhan05/the-react-search-bug-that-shows-old-results-and-how-to-fix-it-3oi7</guid>
      <description>&lt;p&gt;This is part of a series I like to think of as &lt;strong&gt;Real Coding Problems, Simple Fixes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;No fancy architecture. No interview puzzle. Just the kind of bug that shows up while building a normal app.&lt;/p&gt;

&lt;p&gt;Today we are fixing a very common search bug:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The user types a new search term, but the UI sometimes shows results from an older search.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you have built a search box, autocomplete field, customer lookup, product filter, user picker, or admin dashboard table, you have probably been close to this bug.&lt;/p&gt;

&lt;p&gt;It usually does not happen every time. That is what makes it annoying. On your machine, with fast internet, everything may look fine. Then someone uses the app on a slower network and suddenly the search results feel haunted.&lt;/p&gt;

&lt;p&gt;Let's make that bug visible, then fix it properly.&lt;/p&gt;

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

&lt;p&gt;Imagine you are building a customer lookup for a support dashboard.&lt;/p&gt;

&lt;p&gt;The support agent starts typing a customer name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a
am
amy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your React component sends a request for each value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /api/customers?search=a
GET /api/customers?search=am
GET /api/customers?search=amy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You probably expect the responses to come back in the same order.&lt;/p&gt;

&lt;p&gt;But the internet does not promise that.&lt;/p&gt;

&lt;p&gt;The request for &lt;code&gt;amy&lt;/code&gt; might finish first. Then the older request for &lt;code&gt;a&lt;/code&gt; might finish last. If your component blindly updates state whenever any response returns, the screen can end up showing results for &lt;code&gt;a&lt;/code&gt; even though the input says &lt;code&gt;amy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is the bug.&lt;/p&gt;

&lt;p&gt;In simple words:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The latest thing the user typed is not always the latest response your app receives.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Broken Version
&lt;/h2&gt;

&lt;p&gt;Here is a small version of the problem.&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&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="s2"&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;CustomerSearch&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;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setQuery&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCustomers&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLoading&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="kc"&gt;false&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;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setError&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="nf"&gt;useEffect&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setCustomers&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;}&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;searchCustomers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setLoading&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="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="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;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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/customers?search=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&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;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="nf"&gt;setCustomers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;);&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="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="s2"&gt;Could not load customers.&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;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&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;searchCustomers&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="nx"&gt;query&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;section&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;label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"customer-search"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Search customers&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&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;input&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"customer-search"&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onChange&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="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Type a customer name..."&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;loading&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;Searching...&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="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;error&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="si"&gt;}&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&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;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;customer&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;customer&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="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;customer&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;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;))&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;ul&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;section&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;At first glance, this looks reasonable.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;query&lt;/code&gt; changes, we fetch customers. When the response arrives, we put the customers into state.&lt;/p&gt;

&lt;p&gt;The hidden problem is that every request is allowed to update the same &lt;code&gt;customers&lt;/code&gt; state, even if that request is old news by the time it finishes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Happens
&lt;/h2&gt;

&lt;p&gt;React is not doing anything weird here.&lt;/p&gt;

&lt;p&gt;The bug comes from timing.&lt;/p&gt;

&lt;p&gt;Let's say the user types quickly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Search 1: "a"   -&amp;gt; slow response
Search 2: "am"  -&amp;gt; medium response
Search 3: "amy" -&amp;gt; fast response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response order might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"amy" returns first
"am" returns second
"a" returns last
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all three responses call &lt;code&gt;setCustomers&lt;/code&gt;, the oldest search can win just because it finished last.&lt;/p&gt;

&lt;p&gt;That kind of bug is called a &lt;strong&gt;race condition&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You do not need to memorize the term. Just remember the practical version:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Multiple async tasks are trying to update the same screen, and the wrong one finishes last.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  First Fix: Debounce the Input
&lt;/h2&gt;

&lt;p&gt;Before we cancel old requests, we should also avoid creating too many requests.&lt;/p&gt;

&lt;p&gt;If the user types &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;am&lt;/code&gt;, &lt;code&gt;amy&lt;/code&gt; quickly, we probably do not need to search after every single keypress. We can wait until the user pauses for a moment.&lt;/p&gt;

&lt;p&gt;That is called &lt;strong&gt;debouncing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is a small reusable hook:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&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="s2"&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useDebounce&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="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;400&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;debouncedValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setDebouncedValue&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="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timerId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&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="nf"&gt;setDebouncedValue&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="nx"&gt;delay&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timerId&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="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delay&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;debouncedValue&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;Now React will wait 400 milliseconds after the latest change before updating &lt;code&gt;debouncedValue&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That means this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a
am
amy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can become one actual search request instead of three.&lt;/p&gt;

&lt;p&gt;But there is an important detail:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Debounce reduces the number of requests. It does not guarantee old requests can never update your UI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If a request has already started, debouncing will not magically cancel it.&lt;/p&gt;

&lt;p&gt;So debounce is helpful, but it is not the complete fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Proper Fix: Cancel the Old Request
&lt;/h2&gt;

&lt;p&gt;The browser gives us a built-in way to cancel a &lt;code&gt;fetch&lt;/code&gt; request: &lt;code&gt;AbortController&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The idea is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an &lt;code&gt;AbortController&lt;/code&gt; for the current request.&lt;/li&gt;
&lt;li&gt;Pass its &lt;code&gt;signal&lt;/code&gt; to &lt;code&gt;fetch&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;When the effect cleans up, call &lt;code&gt;abort()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Ignore the abort error because it is expected.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is the fixed component.&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&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="s2"&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useDebounce&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="s2"&gt;./useDebounce&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;CustomerSearch&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;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setQuery&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCustomers&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setStatus&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="s2"&gt;idle&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setError&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;debouncedQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useDebounce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchTerm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;debouncedQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;searchTerm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setCustomers&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
      &lt;span class="nf"&gt;setStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;"&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="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;}&lt;/span&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;searchCustomers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;loading&lt;/span&gt;&lt;span class="dl"&gt;"&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="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;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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="s2"&gt;`/api/customers?search=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;searchTerm&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="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="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="o"&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;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Search request failed&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&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;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="nf"&gt;setCustomers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customers&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
        &lt;span class="nf"&gt;setStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&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;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="s2"&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="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;setCustomers&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
        &lt;span class="nf"&gt;setStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&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="s2"&gt;Could not load customers. 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="nf"&gt;searchCustomers&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="o"&gt;=&amp;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;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;debouncedQuery&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;section&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;label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"customer-search"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Search customers&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&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;input&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"customer-search"&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onChange&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="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Type a customer name..."&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;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;loading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;Searching...&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="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;error&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="si"&gt;}&lt;/span&gt;

      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;No customers found.&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;)&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;ul&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;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;customer&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;customer&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;strong&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;customer&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;strong&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;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;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&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;span&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;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;))&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;ul&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;section&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;Now when &lt;code&gt;debouncedQuery&lt;/code&gt; changes, React runs the cleanup function for the previous effect.&lt;/p&gt;

&lt;p&gt;That cleanup calls:&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the older request is told:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You are no longer needed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When &lt;code&gt;fetch&lt;/code&gt; is aborted, it throws an error with the name &lt;code&gt;AbortError&lt;/code&gt;. That is why we check this:&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="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="s2"&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="k"&gt;return&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;An aborted request is not a real failure from the user's point of view. It just means the user typed something newer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Fixed Flow Looks Like
&lt;/h2&gt;

&lt;p&gt;With the fixed version, the flow is cleaner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User types "a"
Wait briefly
Start request for "a"

User changes input to "amy"
React cleans up the old effect
Request for "a" is aborted
Wait briefly
Start request for "amy"

Only the useful result updates the UI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you two benefits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You send fewer requests because of debounce.&lt;/li&gt;
&lt;li&gt;You stop outdated requests because of &lt;code&gt;AbortController&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That combination is much stronger than debounce alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Tiny Mock API for Testing
&lt;/h2&gt;

&lt;p&gt;If you want to test this locally, you can create an endpoint that responds after a random delay.&lt;/p&gt;

&lt;p&gt;This makes the bug easier to reproduce.&lt;/p&gt;

&lt;p&gt;Here is a tiny Express-style example:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&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;customers&lt;/span&gt; &lt;span class="o"&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Amy Chen&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;amy@example.com&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Amir Khan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;amir@example.com&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ana Lopez&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ana@example.com&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Brian Stone&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brian@example.com&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/customers&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="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;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&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;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&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;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delay&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;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;customer&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;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;res&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="na"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;matches&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&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="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="s2"&gt;API running on http://localhost:3000&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The random delay is useful because real networks are not perfectly predictable either.&lt;/p&gt;

&lt;p&gt;Try the broken component with this API and type quickly. You may see old results appear. Then switch to the fixed version and test again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes to Avoid
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Mistake 1: Only using debounce
&lt;/h3&gt;

&lt;p&gt;Debounce is good for reducing noise.&lt;/p&gt;

&lt;p&gt;But if a request has already started, debounce does not stop it.&lt;/p&gt;

&lt;p&gt;Use debounce to avoid unnecessary requests. Use &lt;code&gt;AbortController&lt;/code&gt; to cancel requests that are no longer relevant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 2: Showing an error when a request was canceled
&lt;/h3&gt;

&lt;p&gt;If the user types a new search term, canceling the old request is normal.&lt;/p&gt;

&lt;p&gt;Do not show:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Something went wrong.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;just because an old request was aborted.&lt;/p&gt;

&lt;p&gt;Check for &lt;code&gt;AbortError&lt;/code&gt; and quietly return.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 3: Forgetting empty input
&lt;/h3&gt;

&lt;p&gt;If the user clears the search box, reset the results.&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;searchTerm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setCustomers&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
  &lt;span class="nf"&gt;setStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;"&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="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;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This avoids showing old results when the input is empty.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 4: Not encoding the search term
&lt;/h3&gt;

&lt;p&gt;Search text can contain spaces, symbols, and special characters.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;encodeURIComponent&lt;/code&gt; before putting user input into a query string:&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="s2"&gt;`/api/customers?search=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;searchTerm&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Could You Use a Library Instead?
&lt;/h2&gt;

&lt;p&gt;Yes.&lt;/p&gt;

&lt;p&gt;In a bigger app, you might use something like TanStack Query, SWR, or another data-fetching library. Those tools can help with caching, request state, retries, background refreshes, and more.&lt;/p&gt;

&lt;p&gt;But even if you use a library later, it is still worth understanding this bug.&lt;/p&gt;

&lt;p&gt;Because the root idea appears everywhere:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When async work finishes, make sure it is still relevant before updating the UI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That applies to search boxes, file uploads, page filters, route changes, modal forms, and many other everyday features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;The bug is not that React is slow.&lt;/p&gt;

&lt;p&gt;The bug is not that &lt;code&gt;useEffect&lt;/code&gt; is broken.&lt;/p&gt;

&lt;p&gt;The bug is that network responses can arrive in a different order than the requests were sent.&lt;/p&gt;

&lt;p&gt;For search inputs, a practical fix is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Debounce the input
+ Cancel outdated requests
+ Ignore expected abort errors
+ Reset state for empty input
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives users a search box that feels stable, even when the network is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;React docs: &lt;a href="https://react.dev/reference/react/useEffect" rel="noopener noreferrer"&gt;useEffect and race conditions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;React docs: &lt;a href="https://react.dev/learn/you-might-not-need-an-effect" rel="noopener noreferrer"&gt;You Might Not Need an Effect&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MDN: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortController" rel="noopener noreferrer"&gt;AbortController&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MDN: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal" rel="noopener noreferrer"&gt;AbortSignal&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The Hidden Cost of Object Spread</title>
      <dc:creator>Amrishkhan Sheik Abdullah</dc:creator>
      <pubDate>Thu, 09 Jul 2026 01:40:00 +0000</pubDate>
      <link>https://dev.to/amrishkhan05/the-hidden-cost-of-object-spread-4jj3</link>
      <guid>https://dev.to/amrishkhan05/the-hidden-cost-of-object-spread-4jj3</guid>
      <description>&lt;p&gt;One of the most common pieces of JavaScript you'll find in modern codebases looks like this:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextState&lt;/span&gt; &lt;span class="o"&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;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;React developers write it.&lt;/p&gt;

&lt;p&gt;Redux developers write it.&lt;/p&gt;

&lt;p&gt;Vue developers write it.&lt;/p&gt;

&lt;p&gt;Angular developers write it.&lt;/p&gt;

&lt;p&gt;Nearly every frontend framework encourages some variation of it.&lt;/p&gt;

&lt;p&gt;It feels clean.&lt;/p&gt;

&lt;p&gt;It feels immutable.&lt;/p&gt;

&lt;p&gt;It feels modern.&lt;/p&gt;

&lt;p&gt;It feels almost free.&lt;/p&gt;

&lt;p&gt;But here's the uncomfortable truth:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Object spread is often far more expensive than most developers realize.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This doesn't mean object spread is bad.&lt;/p&gt;

&lt;p&gt;It doesn't mean immutability is bad.&lt;/p&gt;

&lt;p&gt;It doesn't mean you should stop using it.&lt;/p&gt;

&lt;p&gt;But it does mean you should understand what it's actually doing.&lt;/p&gt;

&lt;p&gt;Because once you understand the cost, you start making much better engineering decisions.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Illusion
&lt;/h1&gt;

&lt;p&gt;When developers see:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextState&lt;/span&gt; &lt;span class="o"&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;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;they often mentally model it as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Take state
Change count
Done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But that's not what happens.&lt;/p&gt;

&lt;p&gt;JavaScript doesn't magically update the object.&lt;/p&gt;

&lt;p&gt;Instead it performs something conceptually closer to:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;for &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;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;nextState&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;nextState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the difference.&lt;/p&gt;

&lt;p&gt;We're not updating.&lt;/p&gt;

&lt;p&gt;We're copying.&lt;/p&gt;

&lt;p&gt;Every property.&lt;/p&gt;

&lt;p&gt;Every time.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Actually Happens
&lt;/h1&gt;

&lt;p&gt;Consider:&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="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="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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;john@example.com&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;updatedUser&lt;/span&gt; &lt;span class="o"&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;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;active&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Internally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Allocate New Object
↓
Copy id
↓
Copy name
↓
Copy email
↓
Add active
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For three properties?&lt;/p&gt;

&lt;p&gt;Nobody cares.&lt;/p&gt;

&lt;p&gt;For thousands of properties?&lt;/p&gt;

&lt;p&gt;You probably should.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Cost Grows With Size
&lt;/h1&gt;

&lt;p&gt;Imagine:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hugeObject&lt;/span&gt; &lt;span class="o"&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;containing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10,000 properties
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now:&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="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;hugeObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;updated&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;must:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Allocate New Object
+
Copy 10,000 Properties
+
Add One Property
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just to change one value.&lt;/p&gt;

&lt;p&gt;That's not free.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Famous Reduce Trap
&lt;/h1&gt;

&lt;p&gt;This is one of the most common performance issues I see.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usersById&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;acc&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="o"&gt;=&amp;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;acc&lt;/span&gt;&lt;span class="p"&gt;,&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="nx"&gt;id&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="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;Looks elegant.&lt;/p&gt;

&lt;p&gt;Looks immutable.&lt;/p&gt;

&lt;p&gt;Looks functional.&lt;/p&gt;

&lt;p&gt;Looks expensive.&lt;/p&gt;

&lt;p&gt;Let's see why.&lt;/p&gt;




&lt;h1&gt;
  
  
  Iteration 1
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 properties
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Iteration 2
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 property
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Iteration 3
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user2&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2 properties
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Iteration 1000
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Copy 999 properties
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Total Work
&lt;/h1&gt;

&lt;p&gt;What initially looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O(n)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can become closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O(n²)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;because every iteration copies everything accumulated so far.&lt;/p&gt;

&lt;p&gt;That is a very different performance profile.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Loop Equivalent
&lt;/h1&gt;

&lt;p&gt;Compare:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usersById&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;acc&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="o"&gt;=&amp;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;acc&lt;/span&gt;&lt;span class="p"&gt;,&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="nx"&gt;id&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="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;with:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usersById&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;for &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="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;usersById&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="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Creates One Object
Mutates One Object
Performs One Pass
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No repeated copies.&lt;/p&gt;

&lt;p&gt;No repeated allocations.&lt;/p&gt;

&lt;p&gt;No repeated garbage collection.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why React Popularized Object Spread
&lt;/h1&gt;

&lt;p&gt;This is where things get interesting.&lt;/p&gt;

&lt;p&gt;React didn't make object spread popular by accident.&lt;/p&gt;

&lt;p&gt;React relies heavily on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Reference Equality
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example:&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;previousState&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;nextState&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;rerender&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 works beautifully when:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextState&lt;/span&gt; &lt;span class="o"&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;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;because:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New Object
New Reference
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;React can instantly detect the change.&lt;/p&gt;

&lt;p&gt;This is a great reason to use object spread.&lt;/p&gt;

&lt;p&gt;But:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Useful
≠
Free
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Deeply Nested Objects
&lt;/h1&gt;

&lt;p&gt;This is where things get ugly.&lt;/p&gt;

&lt;p&gt;Suppose:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextState&lt;/span&gt; &lt;span class="o"&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;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;user&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="nx"&gt;state&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="na"&gt;address&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="nx"&gt;state&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="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dubai&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;You've probably written something similar.&lt;/p&gt;

&lt;p&gt;Maybe many times.&lt;/p&gt;

&lt;p&gt;Let's count.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Copy state
Copy user
Copy address
Update city
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiple allocations.&lt;/p&gt;

&lt;p&gt;Multiple copies.&lt;/p&gt;

&lt;p&gt;For one change.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Immer Became Popular
&lt;/h1&gt;

&lt;p&gt;This problem became so common that libraries emerged specifically to solve it.&lt;/p&gt;

&lt;p&gt;One of the most popular is Immer.&lt;/p&gt;

&lt;p&gt;Instead of:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextState&lt;/span&gt; &lt;span class="o"&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;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;user&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="nx"&gt;state&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="na"&gt;address&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="nx"&gt;state&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="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dubai&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;Immer allows:&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="nx"&gt;draft&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="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dubai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while still producing an immutable result.&lt;/p&gt;

&lt;p&gt;This dramatically improves readability.&lt;/p&gt;




&lt;h1&gt;
  
  
  Garbage Collection Matters
&lt;/h1&gt;

&lt;p&gt;Most developers focus on CPU.&lt;/p&gt;

&lt;p&gt;But memory matters too.&lt;/p&gt;

&lt;p&gt;Every spread operation creates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Temporary Objects
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those objects eventually become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Garbage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Garbage Collection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;must clean them.&lt;/p&gt;

&lt;p&gt;The larger the application becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;More Allocations
↓
More Garbage
↓
More GC Work
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes the bottleneck isn't computation.&lt;/p&gt;

&lt;p&gt;It's memory churn.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Hidden Cost In State Management
&lt;/h1&gt;

&lt;p&gt;Consider:&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;loading&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks harmless.&lt;/p&gt;

&lt;p&gt;Now imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100 updates per second
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;across:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Multiple Stores
Multiple Components
Large State Trees
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suddenly those allocations become measurable.&lt;/p&gt;

&lt;p&gt;Not catastrophic.&lt;/p&gt;

&lt;p&gt;Just measurable.&lt;/p&gt;

&lt;p&gt;And that's the point.&lt;/p&gt;




&lt;h1&gt;
  
  
  When Object Spread Is Perfect
&lt;/h1&gt;

&lt;p&gt;Let's be fair.&lt;/p&gt;

&lt;p&gt;Object spread solves real problems.&lt;/p&gt;

&lt;p&gt;Example:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updatedUser&lt;/span&gt; &lt;span class="o"&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;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;active&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clear.&lt;/p&gt;

&lt;p&gt;Readable.&lt;/p&gt;

&lt;p&gt;Predictable.&lt;/p&gt;

&lt;p&gt;For small objects:&lt;/p&gt;

&lt;p&gt;Use it.&lt;/p&gt;

&lt;p&gt;Without hesitation.&lt;/p&gt;




&lt;h1&gt;
  
  
  When You Should Be Careful
&lt;/h1&gt;

&lt;p&gt;Pay attention when you see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Large Collections
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Large State Trees
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Reducers Executing Frequently
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Performance-Critical Loops
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where spread begins to matter.&lt;/p&gt;




&lt;h1&gt;
  
  
  Benchmark Mentality
&lt;/h1&gt;

&lt;p&gt;One of the biggest mistakes developers make is assuming:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spread Is Slow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spread Is Fast
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both are wrong.&lt;/p&gt;

&lt;p&gt;The correct answer is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;It Depends
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How many properties?&lt;/p&gt;

&lt;p&gt;How often?&lt;/p&gt;

&lt;p&gt;How frequently is the code executed?&lt;/p&gt;

&lt;p&gt;How large are the objects?&lt;/p&gt;

&lt;p&gt;Engineering is always contextual.&lt;/p&gt;




&lt;h1&gt;
  
  
  Real World Example: API Processing
&lt;/h1&gt;

&lt;p&gt;Bad:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;acc&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="o"&gt;=&amp;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;acc&lt;/span&gt;&lt;span class="p"&gt;,&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="nx"&gt;id&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="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;Better:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;for &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="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;result&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="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second version scales significantly better.&lt;/p&gt;




&lt;h1&gt;
  
  
  Real World Example: React State
&lt;/h1&gt;

&lt;p&gt;Good:&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="nf"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;({&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The object is small.&lt;/p&gt;

&lt;p&gt;Readability wins.&lt;/p&gt;

&lt;p&gt;Optimization would be pointless.&lt;/p&gt;




&lt;h1&gt;
  
  
  Real World Example: Deep Updates
&lt;/h1&gt;

&lt;p&gt;Instead of:&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="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;a&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="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;b&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="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;c&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;State normalization&lt;/li&gt;
&lt;li&gt;Immer&lt;/li&gt;
&lt;li&gt;Better state structure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Architecture often beats optimization.&lt;/p&gt;




&lt;h1&gt;
  
  
  Pros Of Object Spread
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. Readable
&lt;/h2&gt;

&lt;p&gt;Intent is obvious.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Immutable
&lt;/h2&gt;

&lt;p&gt;Reduces accidental mutations.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. React-Friendly
&lt;/h2&gt;

&lt;p&gt;Works perfectly with reference equality.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Predictable
&lt;/h2&gt;

&lt;p&gt;Creates explicit state transitions.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Easy To Learn
&lt;/h2&gt;

&lt;p&gt;Minimal cognitive overhead.&lt;/p&gt;




&lt;h1&gt;
  
  
  Cons Of Object Spread
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. Allocations
&lt;/h2&gt;

&lt;p&gt;Every spread creates a new object.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Property Copying
&lt;/h2&gt;

&lt;p&gt;The larger the object, the more expensive the copy.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Garbage Collection Pressure
&lt;/h2&gt;

&lt;p&gt;Temporary objects accumulate.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Easy To Abuse In Reducers
&lt;/h2&gt;

&lt;p&gt;Repeated spreads can become surprisingly expensive.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Deep Updates Become Ugly
&lt;/h2&gt;

&lt;p&gt;Nested spreads quickly reduce readability.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Real Lesson
&lt;/h1&gt;

&lt;p&gt;The biggest mistake developers make with object spread is treating it as a free operation.&lt;/p&gt;

&lt;p&gt;It isn't.&lt;/p&gt;

&lt;p&gt;The second biggest mistake is treating it as a bad operation.&lt;/p&gt;

&lt;p&gt;It isn't.&lt;/p&gt;

&lt;p&gt;Object spread is a tool.&lt;/p&gt;

&lt;p&gt;A very useful tool.&lt;/p&gt;

&lt;p&gt;A very readable tool.&lt;/p&gt;

&lt;p&gt;A very common tool.&lt;/p&gt;

&lt;p&gt;But every immutable update has a cost.&lt;/p&gt;

&lt;p&gt;The goal isn't avoiding object spread.&lt;/p&gt;

&lt;p&gt;The goal is understanding when its benefits outweigh its costs.&lt;/p&gt;

&lt;p&gt;Because once you understand the tradeoff, you stop blindly copying objects.&lt;/p&gt;

&lt;p&gt;And start making deliberate engineering decisions.&lt;/p&gt;




&lt;h1&gt;
  
  
  What's Next?
&lt;/h1&gt;

&lt;p&gt;In the next article we'll discuss:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Composability Is The Real Superpower&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because after exploring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduce&lt;/li&gt;
&lt;li&gt;Transducers&lt;/li&gt;
&lt;li&gt;Functors&lt;/li&gt;
&lt;li&gt;FlatMap&lt;/li&gt;
&lt;li&gt;Monads&lt;/li&gt;
&lt;li&gt;RxJS&lt;/li&gt;
&lt;li&gt;Event Sourcing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;you'll discover that all of them ultimately revolve around a single idea:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Composition
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that idea is far more important than any individual function.&lt;/p&gt;




&lt;h1&gt;
  
  
  About The Author
&lt;/h1&gt;

&lt;p&gt;Hi, I'm Amrish Khan.&lt;/p&gt;

&lt;p&gt;I enjoy building developer tools, exploring software architecture, and writing about the deeper ideas behind everyday programming concepts.&lt;/p&gt;

&lt;p&gt;I'm also building Aruvix — a growing ecosystem of local-first developer tools designed to process data directly in the browser without unnecessary uploads.&lt;/p&gt;

&lt;p&gt;Here's a detailed blog on Aruvix:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/amrishkhan05/aruvix-the-ultimate-offline-first-developer-toolkit-e0i"&gt;https://dev.to/amrishkhan05/aruvix-the-ultimate-offline-first-developer-toolkit-e0i&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can follow my work and thoughts here:&lt;/p&gt;

&lt;p&gt;Portfolio:&lt;br&gt;
&lt;a href="https://www.amrishkhan.dev" rel="noopener noreferrer"&gt;https://www.amrishkhan.dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;LinkedIn:&lt;br&gt;
&lt;a href="https://www.linkedin.com/in/amrishkhan" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/amrishkhan&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub:&lt;br&gt;
&lt;a href="https://www.github.com/amrishkhan05" rel="noopener noreferrer"&gt;https://www.github.com/amrishkhan05&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you enjoyed this article, consider following for more deep dives into JavaScript, architecture, local-first software, and performance engineering.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>javascript</category>
      <category>performance</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
