<?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: Nainik Mehta</title>
    <description>The latest articles on DEV Community by Nainik Mehta (@nainikmehta).</description>
    <link>https://dev.to/nainikmehta</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%2F2447268%2F50173e95-5d7c-4576-b905-125bcab1c744.jpeg</url>
      <title>DEV Community: Nainik Mehta</title>
      <link>https://dev.to/nainikmehta</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nainikmehta"/>
    <language>en</language>
    <item>
      <title>Solving the Invisible Update Bug: Read-Your-Writes Guide</title>
      <dc:creator>Nainik Mehta</dc:creator>
      <pubDate>Fri, 11 Sep 2026 07:31:43 +0000</pubDate>
      <link>https://dev.to/nainikmehta/solving-the-invisible-update-bug-read-your-writes-guide-1hf6</link>
      <guid>https://dev.to/nainikmehta/solving-the-invisible-update-bug-read-your-writes-guide-1hf6</guid>
      <description>&lt;h2&gt;
  
  
  The Invisible Update: Understanding Read-Your-Writes Consistency
&lt;/h2&gt;

&lt;p&gt;We have all been there. You update your profile settings, click "Save," and the page reloads. For a split second, everything looks correct—then, you hit refresh, and your changes vanish. The old data is back. You panic, re-type your changes, hit save again, and suddenly, your previous update reappears.&lt;/p&gt;

&lt;p&gt;This is the classic "invisible update" bug. It is a textbook failure of &lt;strong&gt;read-your-writes consistency&lt;/strong&gt; in distributed systems. While it may seem like a simple UI glitch, it is actually a profound architectural challenge that highlights the friction between database scalability and user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Happening Under the Hood?
&lt;/h2&gt;

&lt;p&gt;In modern web applications, we rarely read and write from the same database instance. To achieve high availability and handle massive read traffic, we use a primary-replica architecture. &lt;/p&gt;

&lt;p&gt;The primary database handles all write operations, while read replicas handle the heavy lifting of serving GET requests. The problem arises during the replication process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Write:&lt;/strong&gt; Your application sends an &lt;code&gt;UPDATE&lt;/code&gt; command to the primary database. It commits successfully.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Replication:&lt;/strong&gt; The primary database writes the change to its Write-Ahead Log (WAL) and asynchronously propagates that change to the read replicas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Read:&lt;/strong&gt; Milliseconds after the write, the user’s browser triggers a GET request. Your load balancer, attempting to distribute traffic, routes this read to a replica that hasn't yet processed the WAL entry from the primary.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The replica returns the stale data. The user thinks their data is gone. Five seconds later, the replication catches up, and the data "magically" reappears. This is the definition of eventual consistency failing the user's expectation of immediate feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Anti-Pattern: The Lazy Hack
&lt;/h2&gt;

&lt;p&gt;Early in my career, we tried to solve this with a "lazy hack": routing all reads to the primary database for 10 seconds after any write. &lt;/p&gt;

&lt;p&gt;While this technically solves the consistency issue, it is a performance nightmare. By routing all post-write traffic to the primary, you negate the benefits of having read replicas. Under high load, this causes a stampede on the primary database, leading to increased latency and potential outages. It is fragile, unscalable, and ultimately, a band-aid on a systemic issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Robust Solutions for Modern Systems
&lt;/h2&gt;

&lt;p&gt;If you want to maintain scale without sacrificing user trust, you need to implement more sophisticated strategies.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. LSN-Based Routing (The Gold Standard)
&lt;/h3&gt;

&lt;p&gt;The most reliable way to ensure consistency is to track the state of the database using a Log Sequence Number (LSN) or a Global Transaction ID (GTID).&lt;/p&gt;

&lt;p&gt;Instead of guessing how long replication takes, we track the specific point in the transaction log where the user's write occurred.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Workflow:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When a write completes, the database returns the LSN of that transaction.&lt;/li&gt;
&lt;li&gt;Store this &lt;code&gt;lastWriteLSN&lt;/code&gt; in a fast cache like Redis, scoped to the user session (e.g., &lt;code&gt;user:123:last_lsn&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;When a read request arrives, the application queries the replica’s current LSN.&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;replicaLSN &amp;gt;= userLastWriteLSN&lt;/code&gt;, the replica is "up to date" and safe to read from.&lt;/li&gt;
&lt;li&gt;If not, the application routes the read to the primary.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Conceptual logic for LSN-based routing&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;getConsistentRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&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;userLastLSN&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;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`user:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:last_lsn`&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;replicaLSN&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replica&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT pg_last_wal_replay_lsn()&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;replicaLSN&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;userLastLSN&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replica&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Fallback to primary for strict consistency&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&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;h3&gt;
  
  
  2. Define Strict 'Trust Boundaries'
&lt;/h3&gt;

&lt;p&gt;Not every piece of data requires strong consistency. Applying the same strict routing logic to your entire application is overkill.&lt;/p&gt;

&lt;p&gt;Instead, define "trust boundaries." &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High-Trust Data:&lt;/strong&gt; Billing info, security settings, passwords, and account status. These should always be read from the primary database to ensure the user sees the absolute truth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low-Trust Data:&lt;/strong&gt; Activity feeds, non-critical dashboards, or public profiles. These are perfect candidates for eventual consistency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By segregating your data, you reduce the load on the primary database while keeping the most critical user interactions consistent.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Leverage Optimistic UI
&lt;/h3&gt;

&lt;p&gt;Sometimes, the best infrastructure fix is actually a client-side fix. Optimistic UI is a pattern where the frontend updates to reflect the user's action immediately, assuming the server request will succeed.&lt;/p&gt;

&lt;p&gt;In frameworks like React or Next.js, instead of waiting for a full server re-fetch, you update your local state (e.g., React Query or SWR cache) as soon as the API returns a &lt;code&gt;200 OK&lt;/code&gt;. By bypassing the immediate read from the server, you avoid the replication lag issue entirely. If the request fails, you simply roll back the state and show an error message.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: Balancing Consistency and Scale
&lt;/h2&gt;

&lt;p&gt;System architecture is always a series of trade-offs. Forcing global strong consistency kills your ability to scale reads, but ignoring replication lag destroys user trust.&lt;/p&gt;

&lt;p&gt;The key is to be intentional. Use &lt;strong&gt;Optimistic UI&lt;/strong&gt; for immediate feedback, define &lt;strong&gt;Trust Boundaries&lt;/strong&gt; to protect critical data, and implement &lt;strong&gt;LSN-based routing&lt;/strong&gt; when you absolutely need to guarantee that a user sees their own writes. &lt;/p&gt;

&lt;p&gt;How does your team handle database replication lag in production? Let's discuss in the comments.&lt;/p&gt;

</description>
      <category>database</category>
      <category>architecture</category>
      <category>scalability</category>
      <category>backend</category>
    </item>
    <item>
      <title>Fix Next.js Parallel Routes 404 on Refresh | App Router Guide</title>
      <dc:creator>Nainik Mehta</dc:creator>
      <pubDate>Thu, 10 Sep 2026 13:01:59 +0000</pubDate>
      <link>https://dev.to/nainikmehta/fix-nextjs-parallel-routes-404-on-refresh-app-router-guide-11o2</link>
      <guid>https://dev.to/nainikmehta/fix-nextjs-parallel-routes-404-on-refresh-app-router-guide-11o2</guid>
      <description>&lt;h2&gt;
  
  
  The Frustration of the Disappearing Dashboard
&lt;/h2&gt;

&lt;p&gt;You’ve spent weeks architecting the perfect Next.js dashboard. You’ve leveraged the App Router’s power to implement parallel routes, creating a sophisticated, multi-pane UI that feels like a native desktop application. The client-side routing is buttery smooth, the state management is locked in, and the user experience is top-tier.&lt;/p&gt;

&lt;p&gt;Then, you deploy to production. You send the link to a stakeholder or a beta tester. They open the dashboard, navigate through a few tabs, and—out of habit—hit &lt;code&gt;Cmd + R&lt;/code&gt; to refresh the page.&lt;/p&gt;

&lt;p&gt;Instead of the dashboard reloading, they are greeted by a stark, unfriendly 404 error page.&lt;/p&gt;

&lt;p&gt;If you’ve experienced this, you aren't failing as a developer; you’ve simply hit one of the most common "gotchas" in the Next.js App Router ecosystem. Understanding why this happens—and how to fix it—is the difference between a prototype and a production-ready enterprise application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Soft Navigation vs. Hard Refresh: The Root Cause
&lt;/h2&gt;

&lt;p&gt;To understand why this happens, we have to look at how Next.js handles routing under the hood. The App Router makes a clear distinction between "soft" and "hard" navigation.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Magic of Soft Navigation
&lt;/h3&gt;

&lt;p&gt;When a user clicks a link within your application, Next.js performs a soft navigation. It doesn’t reload the entire page. Instead, it fetches the necessary data and components for the new route and patches the DOM. Crucially, it preserves the state of your parallel route slots. If a slot doesn't have a new component to render for the current URL, Next.js simply leaves the existing component in place. This is why everything looks perfect while the user is clicking around.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Reality of Hard Refresh
&lt;/h3&gt;

&lt;p&gt;A hard refresh (hitting the refresh button or &lt;code&gt;Cmd + R&lt;/code&gt;) forces the browser to request the page from the server from scratch. Next.js must now perform a full server-side render of the entire layout. It parses the current URL and attempts to resolve a matching route for every single parallel slot defined in your directory structure.&lt;/p&gt;

&lt;p&gt;If a slot doesn't have a route that matches the current URL, Next.js hits a wall. It doesn't have the "previous state" to fall back on because it is rebuilding from zero. Lacking a defined path for that specific URL within that specific slot, the framework defaults to the safest option: throwing a 404.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: The Power of &lt;code&gt;default.js&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The fix for this behavior is remarkably straightforward, yet it is frequently missed during initial development because soft navigation masks the underlying issue.&lt;/p&gt;

&lt;p&gt;To prevent the 404, you must provide Next.js with a fallback for every slot. This is handled by the &lt;code&gt;default.js&lt;/code&gt; file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing the Fallback Pattern
&lt;/h3&gt;

&lt;p&gt;When you add a &lt;code&gt;default.js&lt;/code&gt; file to a slot, you are essentially telling Next.js: "If you can't find a matching page for the current URL in this slot, render this default component instead."&lt;/p&gt;

&lt;p&gt;Here is a minimal example of how to implement 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="c1"&gt;// app/@analytics/default.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;AnalyticsFallback&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/AnalyticsFallback&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;DefaultAnalytics&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// You can render a loading state, a placeholder, &lt;/span&gt;
  &lt;span class="c1"&gt;// or even null to keep the UI clean.&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="nc"&gt;AnalyticsFallback&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By adding this file, you ensure that even during a cold hard refresh, the slot has something valid to render. The 404 is avoided, and your dashboard remains intact.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Silent Killer: Greedy Catch-All Routes
&lt;/h2&gt;

&lt;p&gt;While &lt;code&gt;default.js&lt;/code&gt; solves the majority of these issues, there is a secondary culprit that often trips up developers: "greedy" catch-all routes.&lt;/p&gt;

&lt;p&gt;If you have a slot containing a folder like &lt;code&gt;[...nextauth]&lt;/code&gt; or any standard catch-all route, it can aggressively intercept URLs that were intended for other parts of your application. These routes are designed to match everything, which often causes them to hijack the resolution process for your slots.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix: Optional Catch-Alls
&lt;/h3&gt;

&lt;p&gt;If you find that your catch-all routes are causing unexpected behavior or interfering with your fallback mechanism, consider swapping them for optional catch-all routes. By using the &lt;code&gt;[[...slug]]&lt;/code&gt; syntax, you make the route optional, which prevents the folder from being overly aggressive in its matching logic. This allows the Next.js router to resolve your slots more predictably.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices for Scaling Layouts
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Always Define &lt;code&gt;default.js&lt;/code&gt;:&lt;/strong&gt; Treat &lt;code&gt;default.js&lt;/code&gt; as a mandatory file for every slot in your parallel routing architecture. Don't wait for a bug report to add them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit Your Catch-Alls:&lt;/strong&gt; If you are using &lt;code&gt;[...slug]&lt;/code&gt; patterns, ensure they are strictly necessary. If they are causing routing conflicts, refactor to &lt;code&gt;[[...slug]]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test Hard Refreshes Often:&lt;/strong&gt; During development, don't just rely on clicking links. Regularly perform hard refreshes on different views of your dashboard to ensure the state is being hydrated correctly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leverage Loading States:&lt;/strong&gt; Use &lt;code&gt;loading.js&lt;/code&gt; alongside &lt;code&gt;default.js&lt;/code&gt; to provide a seamless transition while the server resolves the slots.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Parallel routes are a powerful tool for building complex, modular UIs in Next.js. While the 404-on-refresh issue can be frustrating, it is a predictable consequence of how server-side routing works. By proactively implementing &lt;code&gt;default.js&lt;/code&gt; fallbacks and keeping an eye on your catch-all route definitions, you can build dashboards that are as robust as they are beautiful.&lt;/p&gt;

&lt;p&gt;Have you encountered similar issues while scaling your Next.js architecture? What other strategies do you use to keep your layouts stable? Let's discuss in the comments.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Why React 19's use() API Can Break the Rules of Hooks</title>
      <dc:creator>Nainik Mehta</dc:creator>
      <pubDate>Thu, 10 Sep 2026 07:31:37 +0000</pubDate>
      <link>https://dev.to/nainikmehta/why-react-19s-use-api-can-break-the-rules-of-hooks-4e8d</link>
      <guid>https://dev.to/nainikmehta/why-react-19s-use-api-can-break-the-rules-of-hooks-4e8d</guid>
      <description>&lt;h2&gt;
  
  
  The Paradigm Shift: React 19 and the &lt;code&gt;use&lt;/code&gt; API
&lt;/h2&gt;

&lt;p&gt;For years, the "Rules of Hooks" have been the foundational dogma of React development. Since the introduction of Hooks in 2018, developers have lived by the mantra: &lt;em&gt;Hooks must be called at the top level, never inside loops, conditions, or nested functions.&lt;/em&gt; This restriction was not arbitrary—it was a technical necessity for React’s reconciliation engine to maintain state consistency across re-renders.&lt;/p&gt;

&lt;p&gt;However, React 19 has arrived, and it has done the unthinkable: it has officially broken these rules. With the introduction of the new &lt;code&gt;use&lt;/code&gt; API, React is moving toward a more flexible, declarative future. But how can a framework that relies so heavily on call-order tracking suddenly allow conditional hooks? The answer lies in a fundamental shift from stateful tracking to stateless evaluation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Rules of Hooks Existed
&lt;/h2&gt;

&lt;p&gt;To understand why &lt;code&gt;use&lt;/code&gt; is revolutionary, we must first understand why we couldn't use &lt;code&gt;useState&lt;/code&gt; or &lt;code&gt;useEffect&lt;/code&gt; conditionally.&lt;/p&gt;

&lt;p&gt;React tracks hooks using an internal linked list or array. When your component renders, React executes your hooks in the exact order they appear in your code. If you have three &lt;code&gt;useState&lt;/code&gt; calls, React maps them to index 0, 1, and 2 in its internal memory. &lt;/p&gt;

&lt;p&gt;If you were to wrap one of those calls in an &lt;code&gt;if&lt;/code&gt; statement, and that condition evaluated to &lt;code&gt;false&lt;/code&gt;, the sequence of calls would shift. React would look for the second hook, but instead of finding the expected state, it would find the third hook. The application would lose its "place," leading to unpredictable bugs and crashes.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the &lt;code&gt;use&lt;/code&gt; API Changes the Game
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;use&lt;/code&gt; API is fundamentally different because it is &lt;strong&gt;stateless&lt;/strong&gt;. It does not rely on a fixed call order because it does not store state in the same way &lt;code&gt;useState&lt;/code&gt; does. &lt;/p&gt;

&lt;p&gt;Instead, &lt;code&gt;use&lt;/code&gt; reads the current value of a resource—such as a Promise or a Context—on every single render. Because it doesn't need to maintain a persistent index in a list of hooks, it can be called inside &lt;code&gt;if&lt;/code&gt; statements, loops, and even within the render body of a component without confusing the reconciler.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integration with Suspense and Error Boundaries
&lt;/h3&gt;

&lt;p&gt;The power of &lt;code&gt;use&lt;/code&gt; extends beyond mere flexibility; it is designed to work natively with the React Suspense and Error Boundary architecture:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pending:&lt;/strong&gt; If you pass a Promise to &lt;code&gt;use&lt;/code&gt; and it is still pending, the component suspends. React will display the nearest &lt;code&gt;Suspense&lt;/code&gt; fallback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolved:&lt;/strong&gt; Once the Promise resolves, &lt;code&gt;use&lt;/code&gt; returns the value, and the component re-renders with the data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rejected:&lt;/strong&gt; If the Promise is rejected, &lt;code&gt;use&lt;/code&gt; throws the error, which is caught by the nearest &lt;code&gt;Error Boundary&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This declarative approach removes the need for manual &lt;code&gt;loading&lt;/code&gt; and &lt;code&gt;error&lt;/code&gt; state variables, significantly reducing boilerplate code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pitfall: Avoiding Infinite Re-render Loops
&lt;/h2&gt;

&lt;p&gt;While &lt;code&gt;use&lt;/code&gt; offers incredible power, it introduces a dangerous pitfall: the infinite re-render loop. Because &lt;code&gt;use&lt;/code&gt; suspends when a Promise is pending, the component unmounts and re-renders when the state changes. If you define a new Promise directly inside your component's render body, that Promise is recreated on every single render.&lt;/p&gt;

&lt;p&gt;Consider this anti-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="c1"&gt;// DANGEROUS: This causes an infinite loop&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;UserProfile&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// A new promise is created every time the component renders&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="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; 
  &lt;span class="k"&gt;return&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="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;name&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;/div&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;Because &lt;code&gt;fetchData(userId)&lt;/code&gt; returns a new Promise on every render, React sees a new pending resource, suspends, re-renders, and repeats the cycle indefinitely.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: Stable Promises
&lt;/h3&gt;

&lt;p&gt;To use &lt;code&gt;use&lt;/code&gt; effectively in production, your Promises must be &lt;strong&gt;stable&lt;/strong&gt;. They should be cached outside the render body. This is typically handled by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Server Components:&lt;/strong&gt; Fetching data on the server and passing it down.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Route Loaders:&lt;/strong&gt; Fetching data at the routing layer (like in React Router or Next.js).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Cache Mechanisms:&lt;/strong&gt; Using libraries like &lt;code&gt;React Query&lt;/code&gt; or a custom memoized cache that returns the same Promise reference across renders.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Is &lt;code&gt;use&lt;/code&gt; a Replacement for Everything?
&lt;/h2&gt;

&lt;p&gt;It is tempting to view &lt;code&gt;use&lt;/code&gt; as a "better hook" that replaces &lt;code&gt;useContext&lt;/code&gt; or even &lt;code&gt;useEffect&lt;/code&gt;. However, it is important to note that &lt;code&gt;use&lt;/code&gt; is not a silver bullet.&lt;/p&gt;

&lt;p&gt;While it is a superior way to consume Context conditionally, it is &lt;strong&gt;not&lt;/strong&gt; a replacement for &lt;code&gt;useEffect&lt;/code&gt;. You still need &lt;code&gt;useEffect&lt;/code&gt; for side effects that require cleanup logic, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Attaching and removing event listeners.&lt;/li&gt;
&lt;li&gt;  Managing WebSocket subscriptions.&lt;/li&gt;
&lt;li&gt;  Interacting with imperative browser APIs.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;React 19’s &lt;code&gt;use&lt;/code&gt; API represents a significant evolution in how we build user interfaces. By allowing us to break the traditional Rules of Hooks, React is enabling more declarative, cleaner, and more readable code. &lt;/p&gt;

&lt;p&gt;However, this power comes with the responsibility of understanding the underlying architecture. As we transition to this new model, the difference between a high-performing app and one plagued by memory leaks and infinite loops will be a deep understanding of how data flows through the component lifecycle.&lt;/p&gt;

&lt;p&gt;Are you ready to embrace the stateless nature of the &lt;code&gt;use&lt;/code&gt; API, or will you keep your data fetching logic within the safety of &lt;code&gt;useEffect&lt;/code&gt; for the time being? The future of React is here—use it wisely.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Database-per-Service is a Trap: Try Logical Schemas</title>
      <dc:creator>Nainik Mehta</dc:creator>
      <pubDate>Thu, 10 Sep 2026 03:01:30 +0000</pubDate>
      <link>https://dev.to/nainikmehta/database-per-service-is-a-trap-try-logical-schemas-2kkp</link>
      <guid>https://dev.to/nainikmehta/database-per-service-is-a-trap-try-logical-schemas-2kkp</guid>
      <description>&lt;h2&gt;
  
  
  The Database-per-Service Fallacy
&lt;/h2&gt;

&lt;p&gt;For many engineering teams, the "database-per-service" pattern has become the de facto gold standard for microservices architecture. We are taught that to truly decouple services, we must enforce physical data isolation. If a service owns its data, it should own its database.&lt;/p&gt;

&lt;p&gt;However, for a 15-person engineering team, this "best practice" often becomes a massive anchor. You find yourself spending more time managing distributed transactions and Kafka topics than building features that actually deliver value to your users. &lt;/p&gt;

&lt;p&gt;If you are struggling with complex Saga orchestrators just to join a &lt;code&gt;User&lt;/code&gt; table with an &lt;code&gt;Order&lt;/code&gt; table, you have likely fallen into a common architectural trap: premature optimization.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cost of Premature Isolation
&lt;/h2&gt;

&lt;p&gt;When startups and mid-sized teams rush to split their databases, they often create a "distributed monolith"—a system that suffers from the complexity of microservices without gaining any of the benefits of scalability.&lt;/p&gt;

&lt;p&gt;By forcing physical isolation too early, you inherit several significant operational burdens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Distributed Consistency Nightmares:&lt;/strong&gt; Without ACID-compliant transactions across services, you are forced to implement complex patterns like Sagas or distributed locks.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Latency Spikes:&lt;/strong&gt; Simple read operations that could have been a single SQL join now require multiple network hops between services.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Operational Overkill:&lt;/strong&gt; You end up maintaining complex event-driven pipelines simply to keep data synchronized across boundaries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I recall a project where we migrated a billing service to its own database. Suddenly, a simple check to see if a client was active required a network call from the subscription service. We spent weeks debugging race conditions and implementing retry queues and outbox patterns. It was a massive waste of engineering cycles that could have been spent on product growth.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pragmatic Alternative: Logical Schemas
&lt;/h2&gt;

&lt;p&gt;The alternative is not to abandon modularity, but to rethink where you enforce it. You can achieve clear ownership boundaries without the overhead of multiple database instances by using &lt;strong&gt;logical schemas&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In a system like PostgreSQL, you can isolate your data using namespaces (schemas) within a single physical instance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Implementing Logical Isolation in Postgres
&lt;/h3&gt;

&lt;p&gt;Instead of creating two separate databases, organize your tables into logical schemas. This allows you to maintain clean code boundaries while keeping the data accessible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Create distinct schemas for service ownership&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;billing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Define tables within their respective schemas&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;profiles&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;billing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoices&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- You can still perform efficient, ACID-compliant joins&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;billing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoices&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;profiles&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Logical Schemas Win
&lt;/h2&gt;

&lt;p&gt;Using logical schemas provides the best of both worlds:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Clear Ownership:&lt;/strong&gt; Your application code connects to the database with a search path restricted to its own schema, enforcing logical separation.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Future-Proofing:&lt;/strong&gt; If a specific service grows to the point where it truly needs its own hardware, moving a schema to a separate database instance is a straightforward migration.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Pragmatism:&lt;/strong&gt; When you need to perform cross-service reporting or complex queries, you aren't fighting against your own infrastructure. You have the full power of SQL at your fingertips.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion: Build for Your Current Scale
&lt;/h2&gt;

&lt;p&gt;A recent industry report noted that 34% of practitioners admit that multiple microservices end up managing the same tables anyway. We must stop paying the "distributed systems tax" before we have even reached product-market fit.&lt;/p&gt;

&lt;p&gt;Keep your stack simple. Build clean boundaries within a single database, and only reach for physical isolation when your team size, traffic, and data volume actually demand it. Your future self—and your velocity—will thank you.&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>architecture</category>
      <category>database</category>
      <category>backend</category>
    </item>
    <item>
      <title>Resilient AI: Multi-Provider LLM Fallback Routing Guide</title>
      <dc:creator>Nainik Mehta</dc:creator>
      <pubDate>Wed, 09 Sep 2026 13:02:12 +0000</pubDate>
      <link>https://dev.to/nainikmehta/resilient-ai-multi-provider-llm-fallback-routing-guide-5bm3</link>
      <guid>https://dev.to/nainikmehta/resilient-ai-multi-provider-llm-fallback-routing-guide-5bm3</guid>
      <description>&lt;h2&gt;
  
  
  The Case for Resilience in AI Architecture
&lt;/h2&gt;

&lt;p&gt;In the early days of integrating Large Language Models (LLMs) into production applications, developers often treated them like standard third-party APIs. You pick a provider, integrate their SDK, and call it a day. However, the landscape of 2023 and 2024 proved that relying on a single provider is a significant operational risk. &lt;/p&gt;

&lt;p&gt;From major outages at OpenAI to service disruptions at Anthropic, the reality has become clear: &lt;strong&gt;LLM gateway fallback routing is no longer an optional optimization—it is a production necessity.&lt;/strong&gt; If your AI feature is hardcoded to a single provider’s SDK, your application’s uptime is strictly bounded by theirs. If they go down, you go down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving Beyond Single-Provider Dependency
&lt;/h2&gt;

&lt;p&gt;Last quarter, we faced a critical turning point in our analytics engine. We noticed that a single 503 error from our primary LLM was causing our entire user onboarding flow to crash. The user experience was brittle, and the business impact was immediate. We decided to migrate our architecture to a multi-provider fallback pattern.&lt;/p&gt;

&lt;p&gt;The goal was simple: decouple our product uptime from the status of any single AI provider. By implementing a tiered routing architecture, we ensured that if our primary model fails, the system automatically redirects the request to a secondary, and if necessary, a tertiary model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the Fallback Pattern
&lt;/h2&gt;

&lt;p&gt;The implementation doesn't have to be complex. At its core, it is about wrapping your LLM calls in a resilient retry and fallback mechanism. Here is a simplified example of how we implemented this logic inside our Next.js edge route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getAIResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&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="c1"&gt;// Attempt primary model (e.g., Claude 3.5 Sonnet)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;callPrimaryLLM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&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="k"&gt;async &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="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;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Primary LLM failed, initiating fallback:&lt;/span&gt;&lt;span class="dl"&gt;'&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="c1"&gt;// Attempt tier-2 model (e.g., GPT-4o)&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;callFallbackLLM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&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;fallbackErr&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tier-2 fallback also failed:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fallbackErr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Optional: Trigger a final, cost-effective model or return a cached response&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;callFinalSafetyModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Engineering Takeaways
&lt;/h2&gt;

&lt;p&gt;Migrating to a multi-provider strategy taught us several lessons that are essential for any production-grade system.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Standardize Your Schemas
&lt;/h3&gt;

&lt;p&gt;The biggest hurdle in swapping providers is the differences in their response formats. To solve this, you must use a unified interface. Whether you use the Vercel AI SDK or build your own internal abstraction layer, ensure your frontend parsing logic remains agnostic of the underlying model provider. This allows you to swap providers or add new ones without refactoring your entire codebase.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Monitor the Latency Penalty
&lt;/h3&gt;

&lt;p&gt;Fallbacks are not "free." They add execution time. If your primary request waits 10 seconds to timeout before triggering a fallback, your user has already abandoned the page. In interactive UI contexts, set aggressive timeouts (e.g., 3–4 seconds) for the primary call. If it doesn't respond within that window, fail fast and trigger the fallback immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Track Your Costs
&lt;/h3&gt;

&lt;p&gt;Tiered routing is a powerful tool, but it can be dangerous for your budget. If your primary model fails during a high-traffic period, you might accidentally route thousands of requests to a significantly more expensive model. Always implement real-time alerting on fallback usage so you can identify if a provider is consistently failing and adjust your routing logic accordingly.&lt;/p&gt;

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

&lt;p&gt;Building for production-grade AI means embracing the mindset that things &lt;em&gt;will&lt;/em&gt; fail. Relying on a single provider is a gamble with your user experience. By implementing a robust LLM gateway fallback routing strategy, you can achieve near-perfect uptime and gain the flexibility to adapt to the rapidly changing AI landscape.&lt;/p&gt;

&lt;p&gt;Are you handling LLM downtime in your production apps? Are you using self-hosted gateways, custom middleware, or third-party routers? Let’s discuss in the comments.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>architecture</category>
      <category>ai</category>
      <category>engineering</category>
    </item>
    <item>
      <title>React Context Is Not State Management: Stop Using It</title>
      <dc:creator>Nainik Mehta</dc:creator>
      <pubDate>Wed, 09 Sep 2026 03:01:52 +0000</pubDate>
      <link>https://dev.to/nainikmehta/react-context-is-not-state-management-stop-using-it-4i3l</link>
      <guid>https://dev.to/nainikmehta/react-context-is-not-state-management-stop-using-it-4i3l</guid>
      <description>&lt;h2&gt;
  
  
  The Architectural Trap: Why React Context Isn't a State Manager
&lt;/h2&gt;

&lt;p&gt;In the modern React ecosystem, "prop drilling" is often cited as the ultimate developer productivity killer. To solve it, many teams reflexively reach for React Context. It’s built-in, it’s easy to use, and it seems to solve the problem of passing data through deeply nested component trees.&lt;/p&gt;

&lt;p&gt;However, after auditing dozens of enterprise-grade React codebases, I have identified a recurring architectural pattern that is quietly crippling application performance: using React Context as a high-frequency state management tool.&lt;/p&gt;

&lt;p&gt;It is time to clarify a fundamental truth: &lt;strong&gt;React Context is a dependency injection tool, not a state manager.&lt;/strong&gt; When you use it for the wrong purpose, you aren't just writing messy code—you are creating a performance bottleneck that will eventually choke your main thread.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mechanics of the Performance Hit
&lt;/h2&gt;

&lt;p&gt;To understand why Context fails under high-frequency updates, we have to look at how React handles re-renders. When a value provided by a &lt;code&gt;Context.Provider&lt;/code&gt; changes, React notifies every single component that consumes that context.&lt;/p&gt;

&lt;p&gt;Crucially, this process bypasses &lt;code&gt;React.memo&lt;/code&gt;. If your component consumes a context, it will re-render whenever the context value changes, regardless of whether the specific data the component cares about has actually changed.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Inline Object" Anti-Pattern
&lt;/h3&gt;

&lt;p&gt;The most common mistake I see involves passing an object literal directly into the provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The Anti-Pattern&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&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="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;dispatch&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;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&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;Because React uses &lt;code&gt;Object.is&lt;/code&gt; for reference equality checks, the object &lt;code&gt;{ state, dispatch }&lt;/code&gt; is re-created on every single render of the parent component. Even if &lt;code&gt;state&lt;/code&gt; hasn't changed, the reference has. This forces a massive, unnecessary cascade of re-renders across your entire component tree.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Real-World Performance Impact
&lt;/h2&gt;

&lt;p&gt;We recently audited an enterprise dashboard featuring a complex form with 50+ fields. Users reported that typing in the fields felt sluggish and unresponsive. After profiling the application, we found the update latency was a staggering 250ms per keystroke.&lt;/p&gt;

&lt;p&gt;By migrating the high-velocity UI state from a monolithic React Context to &lt;strong&gt;Zustand&lt;/strong&gt;, we achieved a dramatic improvement. Zustand leverages atomic, selector-based subscriptions. This means that when a user types in a single input field, only that specific component re-renders. &lt;/p&gt;

&lt;p&gt;The result? Update latency dropped from 250ms to a buttery-smooth 12ms.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Should You Actually Use Context?
&lt;/h2&gt;

&lt;p&gt;React Context is not "bad"—it is simply a specialized tool. It excels at managing low-velocity global data that rarely changes.&lt;/p&gt;

&lt;p&gt;Here is the rule of thumb I recommend for your architecture:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Low-Velocity Data Only:&lt;/strong&gt; Use Context for data that changes infrequently, such as UI themes, user authentication status, or locale settings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Split Your Contexts:&lt;/strong&gt; If you must use Context for state, split your providers. Separate your &lt;code&gt;StateContext&lt;/code&gt; from your &lt;code&gt;DispatchContext&lt;/code&gt;. This ensures that components only interested in calling a dispatch function do not re-render when the state changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External Stores for High-Frequency Data:&lt;/strong&gt; For anything that changes on every keystroke, mouse movement, or real-time data stream, move that state into an external store like &lt;strong&gt;Zustand&lt;/strong&gt; or &lt;strong&gt;Jotai&lt;/strong&gt;. These libraries utilize &lt;code&gt;useSyncExternalStore&lt;/code&gt; under the hood, providing a performant, predictable way to manage state without triggering global re-renders.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion: Stop Choking Your Main Thread
&lt;/h2&gt;

&lt;p&gt;As we move through 2025, the complexity of our frontends continues to grow. We need to be more disciplined about our architectural choices. Stop using React Context as a catch-all solution. Your users deserve a responsive interface, and your main thread deserves a break.&lt;/p&gt;

&lt;p&gt;What is your go-to state management tool for React apps in 2025? Let’s discuss in the comments.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>performance</category>
    </item>
    <item>
      <title>Systems Architecture for Agentic AI in Next.js | Nainik Mehta</title>
      <dc:creator>Nainik Mehta</dc:creator>
      <pubDate>Tue, 08 Sep 2026 13:01:35 +0000</pubDate>
      <link>https://dev.to/nainikmehta/systems-architecture-for-agentic-ai-in-nextjs-nainik-mehta-5gfb</link>
      <guid>https://dev.to/nainikmehta/systems-architecture-for-agentic-ai-in-nextjs-nainik-mehta-5gfb</guid>
      <description>&lt;h2&gt;
  
  
  The Agentic Bottleneck
&lt;/h2&gt;

&lt;p&gt;As we move deeper into 2026, the promise of agentic AI is everywhere. However, a silent crisis is brewing in the developer community: most agentic applications feel like slow, brittle wrappers around LLM APIs. Why? Because we are still trying to force these complex, multi-step systems into the rigid, monolithic request-response patterns of the past.&lt;/p&gt;

&lt;p&gt;When we first began building our agentic platform, we hit a wall. Our initial architecture relied on standard HTTP connections between our Next.js frontend and our LLM-backed backend. As soon as we introduced multi-step reasoning loops—where an agent needs to plan, tool-use, and reflect—those connections began to fail. We were fighting against API gateway timeouts, dangling sockets, and a frontend that felt sluggish, constantly waiting for a monolithic process to complete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rethinking Systems Architecture
&lt;/h2&gt;

&lt;p&gt;To scale effectively, we realized we had to stop treating AI agents as synchronous REST endpoints. The traditional "request-response" model assumes that a user asks a question and receives an answer in a predictable timeframe. But agentic workflows are inherently unpredictable. They involve multiple steps, external tool calls, and variable processing times.&lt;/p&gt;

&lt;p&gt;We decided to completely decouple execution from presentation. By shifting to an asynchronous, event-driven architecture, we transformed our system from a chain of fragile dependencies into a resilient, reactive pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Event-Driven Shift
&lt;/h2&gt;

&lt;p&gt;Instead of keeping an HTTP stream open directly from a Next.js serverless function to the LLM agent, we introduced a message broker. This allows us to queue agent tasks immediately. Once a task is submitted, the serverless function can return a 202 Accepted status, freeing up resources while the heavy lifting happens in the background.&lt;/p&gt;

&lt;p&gt;Our background workers process each step of the agent's logic. To keep the UI responsive, we use a lightweight WebSocket channel to stream updates back to the browser in real-time. This allows the frontend to render optimistic UI updates, showing the user exactly what the agent is doing at any given moment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tracking the Thought Process
&lt;/h3&gt;

&lt;p&gt;To maintain consistency across our event-driven system, we defined a standard interface for agent steps. This allows our backend workers and frontend services to communicate state changes predictably:&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AgentStep&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;taskId&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="nl"&gt;stepNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;thinking&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;acting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;done&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;output&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="nl"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By pushing these objects through our WebSocket layer, the frontend can render the agent’s internal "thought process" progressively. This doesn't just improve performance; it builds user trust by making the agent's reasoning transparent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results and Scalability
&lt;/h2&gt;

&lt;p&gt;The impact on our platform was immediate. Our perceived UI latency dropped to sub-50ms because the frontend no longer waited for the entire agent workflow to finish before showing the first "thought." &lt;/p&gt;

&lt;p&gt;Furthermore, because we decoupled the execution, our system now scales horizontally. We can handle thousands of concurrent multi-step reasoning sessions without worrying about serverless runtime limits or connection timeouts. We are no longer limited by the duration of a single HTTP request.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Path Forward
&lt;/h2&gt;

&lt;p&gt;If you are building in the agentic era, take this as a sign to rethink your stack. Stop forcing agents into synchronous boxes. Build for asynchronicity, embrace event-driven patterns, and prioritize real-time state streaming.&lt;/p&gt;

&lt;p&gt;How are you handling real-time state streaming for complex AI agent workflows in your Next.js applications? Are you using WebSockets, Server-Sent Events, or something else? Let's discuss in the comments below.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>nextjs</category>
      <category>ai</category>
      <category>scalability</category>
    </item>
    <item>
      <title>How to Fix LLM Streaming Lag and React Render Storms</title>
      <dc:creator>Nainik Mehta</dc:creator>
      <pubDate>Tue, 08 Sep 2026 07:31:15 +0000</pubDate>
      <link>https://dev.to/nainikmehta/how-to-fix-llm-streaming-lag-and-react-render-storms-13ee</link>
      <guid>https://dev.to/nainikmehta/how-to-fix-llm-streaming-lag-and-react-render-storms-13ee</guid>
      <description>&lt;h2&gt;
  
  
  The LLM 'Render Storm': Why 50 Tokens/Sec Will Lag Your React App
&lt;/h2&gt;

&lt;p&gt;Generative AI is transforming how we build applications, but it has introduced a silent performance killer in frontend development: the "Render Storm." If you are building an AI-powered chat interface or a real-time document generator, you have likely encountered the challenge of streaming text. &lt;/p&gt;

&lt;p&gt;Many developers start by piping their LLM stream directly into a React state variable. It seems logical—data comes in, you update the state, and the UI reflects the change. However, this approach is a recipe for disaster. If you aren't careful, your high-performance LLM implementation will quickly become a high-performance lag machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Math Behind the Lag
&lt;/h2&gt;

&lt;p&gt;To understand why this happens, we have to look at the numbers. Modern LLMs can stream tokens at speeds ranging from 30 to over 100 tokens per second. &lt;/p&gt;

&lt;p&gt;If you are using a naive implementation where every incoming network chunk triggers a &lt;code&gt;setState&lt;/code&gt; call, you are effectively forcing React to perform a full render cycle for every single token. At 50 tokens per second, that is 50 state updates per second. &lt;/p&gt;

&lt;p&gt;Because these tokens arrive as asynchronous network chunks, React’s automatic batching—which usually coalesces updates within the same synchronous event loop task—cannot help you. Each token arrives as its own discrete task, forcing a re-render. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Frame Budget Reality
&lt;/h3&gt;

&lt;p&gt;To maintain a smooth 60 frames per second (FPS) experience, your application must complete its render and commit cycle within a 16.6ms frame budget. When you trigger 50 renders per second, you are consuming your entire frame budget just on reconciliation and DOM updates. The result is predictable: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Severe scroll jitter&lt;/li&gt;
&lt;li&gt;Input latency&lt;/li&gt;
&lt;li&gt;Dropped frames&lt;/li&gt;
&lt;li&gt;A UI that feels "locked" or unresponsive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a recent benchmark I conducted on a production React 18.3 build, streaming at 80 tokens per second resulted in over 40 renders per second, with average commit durations hitting 52ms. The interface was essentially unusable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Decouple Intake from Render
&lt;/h2&gt;

&lt;p&gt;The key to solving this is to decouple your stream intake from your render cycle. You should not be letting the network dictate your frame rate. Instead, you should control the flow of updates to the UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;requestAnimationFrame&lt;/code&gt; Pattern
&lt;/h3&gt;

&lt;p&gt;The most effective approach for 95% of use cases is to buffer incoming tokens in a mutable &lt;code&gt;useRef&lt;/code&gt; and flush them to the state using &lt;code&gt;requestAnimationFrame&lt;/code&gt;. This ensures that your UI updates only as often as the browser can actually paint them.&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;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useBufferedStream&lt;/span&gt; &lt;span class="o"&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setContent&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;streamRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&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;requested&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&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="nx"&gt;handleToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;streamRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&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="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;requested&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;requested&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&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;requestAnimationFrame&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;setContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;streamRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;requested&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&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="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;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleToken&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;By implementing this pattern, we effectively cap the render rate at 12–16 frames per second, which is perfectly aligned with the display refresh rate. In my tests, this simple change caused the average commit duration to plummet from 52ms to just 5ms. The UI became buttery smooth, even during heavy generation tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  When You Need More: Web Workers and Canvas
&lt;/h2&gt;

&lt;p&gt;For most applications, the &lt;code&gt;requestAnimationFrame&lt;/code&gt; buffer is sufficient. However, if you are building an application with extreme throughput requirements—say, exceeding 150 tokens per second or rendering massive amounts of markdown—you may need to offload the heavy lifting.&lt;/p&gt;

&lt;p&gt;In these scenarios, consider:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Web Workers:&lt;/strong&gt; Move the text processing, parsing, and measurement logic to a background thread to keep the main thread free for interaction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Canvas Rendering:&lt;/strong&gt; If the text volume is massive, avoid the DOM entirely. Drawing text directly to a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; element bypasses the overhead of the React reconciliation tree, allowing for near-instantaneous updates regardless of token speed.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Don't let your AI features compromise your user experience. While it is tempting to use the simplest implementation, streaming data requires a more disciplined approach to React state management. By buffering your stream, you can ensure your application remains fast, responsive, and professional. &lt;/p&gt;

&lt;p&gt;Are you still relying on raw &lt;code&gt;setState&lt;/code&gt; for your streaming AI features, or have you implemented a custom buffering solution? Let me know in the comments.",article_title:&lt;/p&gt;

</description>
      <category>react</category>
      <category>performance</category>
      <category>ai</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Stop Treating 'use client' Like a Code Smell in Next.js</title>
      <dc:creator>Nainik Mehta</dc:creator>
      <pubDate>Tue, 08 Sep 2026 03:01:09 +0000</pubDate>
      <link>https://dev.to/nainikmehta/stop-treating-use-client-like-a-code-smell-in-nextjs-3a1j</link>
      <guid>https://dev.to/nainikmehta/stop-treating-use-client-like-a-code-smell-in-nextjs-3a1j</guid>
      <description>&lt;h2&gt;
  
  
  The "Server-Everything" Trap
&lt;/h2&gt;

&lt;p&gt;In the current landscape of modern web development, the introduction of React Server Components (RSC) within the Next.js ecosystem has been nothing short of revolutionary. We have moved away from the "everything is a client component" era of the early 2020s toward a model that prioritizes initial load times, SEO, and reduced JavaScript bundles. &lt;/p&gt;

&lt;p&gt;However, a dangerous trend has emerged: developers are treating &lt;code&gt;use client&lt;/code&gt; as if it were a code smell. There is a prevailing, often unspoken pressure to keep components on the server at all costs, under the assumption that any client-side code is inherently "bad" for performance. I’m here to tell you that this is an overcorrection that is actively making your applications feel sluggish.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cost of Architectural Purity
&lt;/h2&gt;

&lt;p&gt;Server Components are incredible for what they do: they eliminate database-to-client waterfalls and reduce the amount of JavaScript sent to the browser. But they were never intended to handle every single interaction. &lt;/p&gt;

&lt;p&gt;When you force a modal toggle, a complex dropdown, or a real-time search input to perform a network roundtrip to the server for every keystroke or click, you are trading a snappy, responsive user experience for a misguided sense of architectural purity. The user doesn't care about your server-side rendering strategy; they care about how fast the button responds when they click it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three-Question Audit
&lt;/h2&gt;

&lt;p&gt;To avoid "client creep"—where unnecessary logic bloats your server components or, conversely, where server-side roundtrips bloat your latency—I use a simple heuristic. Before deciding whether to add the &lt;code&gt;'use client'&lt;/code&gt; directive, I run the component through this Three-Question Audit:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Does it use React hooks?&lt;/strong&gt; (e.g., &lt;code&gt;useState&lt;/code&gt;, &lt;code&gt;useEffect&lt;/code&gt;, &lt;code&gt;useContext&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does it require event handlers?&lt;/strong&gt; (e.g., &lt;code&gt;onClick&lt;/code&gt;, &lt;code&gt;onChange&lt;/code&gt;, &lt;code&gt;onScroll&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does it call browser-only APIs?&lt;/strong&gt; (e.g., &lt;code&gt;window&lt;/code&gt;, &lt;code&gt;localStorage&lt;/code&gt;, &lt;code&gt;navigator&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the answer to all three is "no," it stays as a Server Component. If the answer to any of them is "yes," it becomes a Client Component. It is that simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1: The "Leaf" Pattern
&lt;/h2&gt;

&lt;p&gt;The biggest mistake developers make is placing the &lt;code&gt;'use client'&lt;/code&gt; directive at the top of a large file, effectively turning the entire sub-tree into client-side code. Instead, we should be using the &lt;strong&gt;Leaf Pattern&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Push the &lt;code&gt;'use client'&lt;/code&gt; directive as deep down the component tree as possible. For example, if you have a complex Navbar, don't make the entire component client-side just because of a mobile toggle menu. Extract the mobile menu into its own small, isolated component. By treating client components as isolated, interactive "islands," you can reduce your First Load JavaScript bundle by 40% to 60%.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2: The "Children" Composition Pattern
&lt;/h2&gt;

&lt;p&gt;What if you have a heavy server-side component that needs to be wrapped in an interactive client component? The solution is component composition. By passing Server Components as &lt;code&gt;children&lt;/code&gt; to a Client Component, the interactive wrapper mounts on the client, but the heavy data-fetching subtree remains on the server.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;InteractiveWrapper&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&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;isOpen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsOpen&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="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setIsOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isOpen&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;gt;&lt;/span&gt;Toggle Content&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="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isOpen&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;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// app/page.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;InteractiveWrapper&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/InteractiveWrapper&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;HeavyServerContent&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/HeavyServerContent&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="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="nc"&gt;InteractiveWrapper&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;HeavyServerContent&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;InteractiveWrapper&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;h2&gt;
  
  
  Conclusion: Performance is About Balance
&lt;/h2&gt;

&lt;p&gt;In a recent production migration, adopting these two patterns helped us slash our JavaScript bundles by 57% and improve Time to Interactive (TTI) by up to 75%. We didn't do this by abandoning Server Components; we did it by being intentional about where we used them.&lt;/p&gt;

&lt;p&gt;Stop treating &lt;code&gt;use client&lt;/code&gt; as a failure. It is a vital tool in your belt for creating fluid, high-performance UI. Use the server for data fetching and static rendering, and use the client to bring your application to life. Your users will thank you for the extra milliseconds of speed.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>React 19 useActionState: 3 Silent Trapdoors to Avoid</title>
      <dc:creator>Nainik Mehta</dc:creator>
      <pubDate>Mon, 07 Sep 2026 13:01:19 +0000</pubDate>
      <link>https://dev.to/nainikmehta/react-19-useactionstate-3-silent-trapdoors-to-avoid-239m</link>
      <guid>https://dev.to/nainikmehta/react-19-useactionstate-3-silent-trapdoors-to-avoid-239m</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;React 19 has introduced a suite of powerful hooks designed to simplify form handling and state management. Among these, &lt;code&gt;useActionState&lt;/code&gt; is perhaps the most anticipated. It promises to eliminate the tedious boilerplate of &lt;code&gt;useState&lt;/code&gt; hooks for loading states, error handling, and form submission tracking. &lt;/p&gt;

&lt;p&gt;On the surface, it looks like a clean, declarative replacement for traditional form logic. However, as I discovered while refactoring a complex, multi-step signup process, &lt;code&gt;useActionState&lt;/code&gt; is not a simple drop-in replacement. It introduces a paradigm shift that requires a deeper understanding of React's new concurrency model and web-standard form behavior. &lt;/p&gt;

&lt;p&gt;If you treat it as a direct replacement for your existing &lt;code&gt;useState&lt;/code&gt; patterns, you will likely encounter subtle, frustrating bugs in production. In this article, we will explore the three "silent trapdoors" I encountered and how to avoid them.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Stale Closure Trap
&lt;/h2&gt;

&lt;p&gt;The signature for the action function in &lt;code&gt;useActionState&lt;/code&gt; is &lt;code&gt;(prevState, formData) =&amp;gt; nextState&lt;/code&gt;. This function is intended to be pure or at least independent of the component's render cycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;When you read outer component state or props directly inside your action function, you are creating a closure that captures those values at the time the component was rendered. Because the action might be executed asynchronously, the state or props it references might have changed by the time the action actually runs.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;Instead of relying on outer scope, you must extract all necessary data from the &lt;code&gt;formData&lt;/code&gt; object passed to the action. If you have data that isn't part of the form submission (like a user ID from context), use the &lt;code&gt;.bind()&lt;/code&gt; method to pass these values explicitly to the action function when you define it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// AVOID: Accessing outer state directly&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;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;formAction&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useActionState&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;prevState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;formData&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;submitData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// userId might be stale!&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// PREFER: Using .bind() to pass values&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;boundAction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;submitData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&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="nx"&gt;userId&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;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;formAction&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useActionState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;boundAction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. The &lt;code&gt;isPending&lt;/code&gt; Focus Trap
&lt;/h2&gt;

&lt;p&gt;One of the most touted features of &lt;code&gt;useActionState&lt;/code&gt; is the &lt;code&gt;isPending&lt;/code&gt; boolean, which automatically tracks the status of the form submission. A common pattern is to disable inputs while &lt;code&gt;isPending&lt;/code&gt; is true to prevent double submissions.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;While disabling inputs is a standard UX practice, doing so improperly causes accessibility issues. When an input field is disabled while it currently holds focus, the browser immediately strips focus from that element. If the user is navigating via keyboard, their focus is reset to the &lt;code&gt;body&lt;/code&gt; of the document, forcing them to restart their navigation from the top of the page.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;Keep your inputs interactive but provide visual feedback. Use CSS to style the input as "disabled" or "loading" without actually setting the &lt;code&gt;disabled&lt;/code&gt; attribute on the DOM element. If you absolutely must disable the input, manage the focus state programmatically to ensure the user isn't lost.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Manual Triggering and Context Loss
&lt;/h2&gt;

&lt;p&gt;React 19’s form hooks are designed to work seamlessly with the native &lt;code&gt;&amp;lt;form action={...}&amp;gt;&lt;/code&gt; attribute. This allows React to track the transition lifecycle automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Developers often try to trigger these actions programmatically—for example, inside a &lt;code&gt;useEffect&lt;/code&gt; or an &lt;code&gt;onClick&lt;/code&gt; handler on a non-form element. When you bypass the native &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; element, you often break the internal tracking mechanism. In these cases, the &lt;code&gt;isPending&lt;/code&gt; flag might flip back to &lt;code&gt;false&lt;/code&gt; prematurely, before your asynchronous API call has actually resolved, leading to race conditions and UI inconsistencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;Lean into web-standard HTML form semantics. If you need to trigger an action, do it through a native &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; element. If you need a custom UI, consider using a hidden form or a &lt;code&gt;&amp;lt;button type="submit"&amp;gt;&lt;/code&gt; that is styled to look like your desired UI element.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;useActionState&lt;/code&gt; is an incredibly powerful tool for declarative form management, but it is not a "magic bullet." It expects you to write code that respects the browser's native form lifecycle. By avoiding stale closures, handling focus states gracefully, and sticking to native form submission patterns, you can leverage the power of React 19 without falling into these silent traps.&lt;/p&gt;

&lt;p&gt;Have you started migrating your forms to React 19? What unexpected behaviors have you encountered? Let's discuss in the comments.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Why Your AI Agent Should Just Be a Simple while Loop</title>
      <dc:creator>Nainik Mehta</dc:creator>
      <pubDate>Mon, 07 Sep 2026 03:01:24 +0000</pubDate>
      <link>https://dev.to/nainikmehta/why-your-ai-agent-should-just-be-a-simple-while-loop-467k</link>
      <guid>https://dev.to/nainikmehta/why-your-ai-agent-should-just-be-a-simple-while-loop-467k</guid>
      <description>&lt;h2&gt;
  
  
  The Case for Simplicity in Agentic Systems
&lt;/h2&gt;

&lt;p&gt;In the rapidly evolving landscape of Large Language Models (LLMs), the term "AI Agent" has become synonymous with complexity. Developers are rushing to adopt heavy-duty frameworks like LangChain, CrewAI, or complex graph-based orchestration tools. While these tools have their place in research or highly specific multi-agent orchestrations, they often introduce unnecessary fragility into production environments.&lt;/p&gt;

&lt;p&gt;The reality is that 90% of production AI agents do not need these abstractions. What you actually need is a robust, explicit &lt;strong&gt;native agent architecture&lt;/strong&gt; centered around a controlled &lt;code&gt;while&lt;/code&gt; loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Frameworks Often Fail in Production
&lt;/h2&gt;

&lt;p&gt;When we think of "agents," our minds often jump to open-ended autonomy—systems that can reason, plan, and execute indefinitely. However, in production, autonomy is often a liability. Complex frameworks frequently hide the control flow behind layers of abstraction, making it nearly impossible to debug when the system enters an infinite loop or hallucinates a tool call.&lt;/p&gt;

&lt;p&gt;By relying on a framework, you are inheriting its opinionated architecture, its overhead, and its specific way of handling state. When things go wrong, you aren't just debugging your logic; you are debugging the framework's implementation of that logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Power of the 100-Line Master Loop
&lt;/h2&gt;

&lt;p&gt;The most reliable AI systems currently in the wild often use a minimal master loop. Consider the recent performance of agents on the SWE-bench Verified benchmark. Several top-performing agents—some scoring as high as 76.8%—are built on fewer than 100 lines of code.&lt;/p&gt;

&lt;p&gt;These systems succeed because they prioritize deterministic control flow over "magic." When you write the loop yourself, you have total visibility into every state transition and every tool execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Minimalist Implementation
&lt;/h3&gt;

&lt;p&gt;At its core, a native agent is just a loop that manages context and tool execution. Here is a simple, production-ready 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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;runAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;initialContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;initialContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MAX_STEPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&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;budget&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;BudgetTracker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;5.00&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// $5 limit&lt;/span&gt;

  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;MAX_STEPS&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;budget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exceeded&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getLLMResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;history&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isFinished&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;finalAnswer&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calls&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;results&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;executeTools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;updateHistory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;steps&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="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;Agent reached safety limits.&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;h2&gt;
  
  
  Implementing Non-Negotiable Safety Brakes
&lt;/h2&gt;

&lt;p&gt;Building a &lt;code&gt;while&lt;/code&gt; loop in production is dangerous if you don't implement strict guardrails. Without them, a single bug in your prompt or a rogue model response can rack up massive API bills in minutes. Whenever I architect a native agent, I enforce three non-negotiable safety brakes:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Hard Iteration Limits
&lt;/h3&gt;

&lt;p&gt;Never allow an agent to run indefinitely. By capping the loop at 15 to 20 steps, you force the agent to prioritize efficiency. If it hasn't solved the problem by then, it’s likely caught in a logic trap.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Dollar/Token Budgeting
&lt;/h3&gt;

&lt;p&gt;Every session should have a hard ceiling on cost. Integrating a budget tracker that checks the token count or estimated cost before every iteration is a simple way to prevent financial disasters.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Repetition Detectors
&lt;/h3&gt;

&lt;p&gt;Agents often get stuck in "circular reasoning," where they repeatedly call the same tool with the same arguments. By hashing tool calls and tracking them in a &lt;code&gt;Set&lt;/code&gt; or &lt;code&gt;Map&lt;/code&gt;, you can detect these patterns and kill the process before it wastes further resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learning from Industry Leaders
&lt;/h2&gt;

&lt;p&gt;Even sophisticated systems like Anthropic’s Claude Code agent rely on a single-threaded master loop. These systems are designed to manage resources actively. For example, when context utilization approaches a certain percentage (e.g., 92%), the agent triggers context compression. It summarizes history to protect performance and prevent the cost spikes associated with massive context windows.&lt;/p&gt;

&lt;p&gt;Industry data supports this minimalist approach: roughly 68% of production agents execute fewer than 10 steps before requiring some form of human-in-the-loop validation. The "fully autonomous" dream is often less practical than a "collaborative assistant" that knows when to stop and ask for help.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: Keep It Simple
&lt;/h2&gt;

&lt;p&gt;The next time you start a project, ask yourself if you really need a graph framework or a complex agentic library. If you are building a tool to solve specific tasks—writing code, analyzing logs, or extracting data—a native agent architecture will save you weeks of debugging. &lt;/p&gt;

&lt;p&gt;Write the loop yourself, build explicit brakes, and keep your control flow deterministic. Your production environment, and your API bill, will thank you.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>coding</category>
      <category>llm</category>
    </item>
    <item>
      <title>Next.js 15 next/form: Stop Writing Search Form Boilerplate</title>
      <dc:creator>Nainik Mehta</dc:creator>
      <pubDate>Sun, 06 Sep 2026 13:01:14 +0000</pubDate>
      <link>https://dev.to/nainikmehta/nextjs-15-nextform-stop-writing-search-form-boilerplate-1nke</link>
      <guid>https://dev.to/nainikmehta/nextjs-15-nextform-stop-writing-search-form-boilerplate-1nke</guid>
      <description>&lt;h2&gt;
  
  
  The End of Search Boilerplate
&lt;/h2&gt;

&lt;p&gt;For years, building a search or filtering interface in React-based frameworks felt like a chore. We all know the drill: create a &lt;code&gt;useState&lt;/code&gt; hook for your input, add an &lt;code&gt;onSubmit&lt;/code&gt; handler, prevent the default behavior, manually construct a query string, and finally call &lt;code&gt;router.push()&lt;/code&gt; to update the URL. &lt;/p&gt;

&lt;p&gt;It’s repetitive. It’s imperative. And frankly, it’s unnecessary. &lt;/p&gt;

&lt;p&gt;With the release of Next.js 15, we finally have a native solution that embraces the browser’s built-in capabilities rather than fighting them: the &lt;code&gt;&amp;lt;Form /&amp;gt;&lt;/code&gt; component. This new addition is a game-changer for developer experience, allowing us to replace dozens of lines of boilerplate with a declarative, standard HTML-first approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Old Way: Why We Need a Change
&lt;/h2&gt;

&lt;p&gt;Think about the complexity involved in a standard search implementation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;State Syncing&lt;/strong&gt;: You have to keep your input value in sync with the URL search parameters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigation Logic&lt;/strong&gt;: You need to handle &lt;code&gt;router.push&lt;/code&gt; calls, ensuring you don't trigger unnecessary re-renders.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loading States&lt;/strong&gt;: Managing the transition between typing and the results updating often requires complex &lt;code&gt;useEffect&lt;/code&gt; hooks or debouncing logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error Handling&lt;/strong&gt;: What happens if the navigation fails or the user hits enter while the JS bundle is still downloading?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This imperative approach is where most bugs are born. By manually managing the URL, we introduce room for race conditions and inconsistent UI states.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing &lt;code&gt;next/form&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;next/form&lt;/code&gt; component is a drop-in replacement for the standard HTML &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; element. It is designed to handle URL encoding, navigation, and prefetching out of the box.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Simple Example
&lt;/h3&gt;

&lt;p&gt;Look at how little code you need to implement a fully functional search feature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Form&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/form&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;SearchBar&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="nc"&gt;Form&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;"/search\"&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;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;"query\"&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;"Search for products...\"&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;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;"submit\"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Search&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;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Form&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;When a user types a query and hits submit, Next.js automatically navigates to &lt;code&gt;/search?query=your-input&lt;/code&gt;. It handles the URL encoding, the client-side navigation, and ensures the transition is smooth—all without writing a single line of &lt;code&gt;useState&lt;/code&gt; or &lt;code&gt;router.push&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Engineering Win: Progressive Enhancement
&lt;/h2&gt;

&lt;p&gt;The most impressive aspect of &lt;code&gt;next/form&lt;/code&gt; isn't just the reduction of code—it’s the commitment to &lt;strong&gt;progressive enhancement&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;In the modern web, we often assume JavaScript is always available. But what happens on a slow 3G connection? What if a user has JavaScript disabled? In the old imperative model, your search would simply break. With &lt;code&gt;next/form&lt;/code&gt;, the component gracefully falls back to a standard HTML &lt;code&gt;GET&lt;/code&gt; request. The application remains functional, accessible, and robust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Out of the Box
&lt;/h2&gt;

&lt;p&gt;Next.js 15 also optimizes the UX through automatic prefetching. When a &lt;code&gt;next/form&lt;/code&gt; component enters the viewport, Next.js begins prefetching the shared layout resources and the target page. This means that by the time the user actually interacts with the form, the transition feels nearly instantaneous.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Mutations with Server Actions
&lt;/h2&gt;

&lt;p&gt;While the primary use case for &lt;code&gt;next/form&lt;/code&gt; is navigation-based search, it also excels at handling mutations. You can pass a Server Action directly to the &lt;code&gt;action&lt;/code&gt; prop. This allows you to handle &lt;code&gt;POST&lt;/code&gt; requests seamlessly, pairing perfectly with &lt;code&gt;useFormStatus&lt;/code&gt; to provide real-time UI feedback (like disabling buttons or showing spinners) without needing to manage local state manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: Embrace Declarative Patterns
&lt;/h2&gt;

&lt;p&gt;It is time to stop writing imperative code for behaviors that the browser already provides natively. By leveraging &lt;code&gt;next/form&lt;/code&gt;, we can write cleaner, more maintainable code that is inherently more accessible and performant.&lt;/p&gt;

&lt;p&gt;Have you started migrating your search and filter UI to &lt;code&gt;next/form&lt;/code&gt; yet? It’s time to clean up that boilerplate and let the framework do the heavy lifting.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
