<?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: Srikar Phani Kumar Marti</title>
    <description>The latest articles on DEV Community by Srikar Phani Kumar Marti (@mspk97).</description>
    <link>https://dev.to/mspk97</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%2F2519231%2F04ef8b8c-91fd-4ba1-a9b7-1e98f84baf6a.png</url>
      <title>DEV Community: Srikar Phani Kumar Marti</title>
      <link>https://dev.to/mspk97</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mspk97"/>
    <language>en</language>
    <item>
      <title>Debugging React Context Performance Issues: When and Why Components Re-render</title>
      <dc:creator>Srikar Phani Kumar Marti</dc:creator>
      <pubDate>Thu, 16 Jul 2026 11:34:11 +0000</pubDate>
      <link>https://dev.to/mspk97/debugging-react-context-performance-issues-when-and-why-components-re-render-183e</link>
      <guid>https://dev.to/mspk97/debugging-react-context-performance-issues-when-and-why-components-re-render-183e</guid>
      <description>&lt;p&gt;Ever had this happen? You change a value in your React Context Provider, expecting just a few components to update. But suddenly, half your app re-renders, and your performance tanks. You scratch your head and wonder: why is React context so slow? I’ve been there. This article pulls back the curtain on React Context's update mechanism, why components re-render aggressively, and how to tame the beast.&lt;/p&gt;

&lt;h2&gt;
  
  
  The React Context gotcha: Every consumer re-renders on update
&lt;/h2&gt;

&lt;p&gt;Imagine you have a &lt;code&gt;ThemeContext&lt;/code&gt; with a &lt;code&gt;theme&lt;/code&gt; value (say, "light" or "dark"). You wrap your app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ThemeContext&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="nx"&gt;theme&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;App&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;ThemeContext&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;Now, anywhere you call &lt;code&gt;useContext(ThemeContext)&lt;/code&gt;, React will re-render that component &lt;strong&gt;every time the &lt;code&gt;value&lt;/code&gt; changes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Sounds obvious? The surprise is that React does &lt;strong&gt;not&lt;/strong&gt; do a deep comparison on the context value. It just checks if the reference changed (with &lt;code&gt;Object.is&lt;/code&gt;). So if you provide a new object or primitive value, &lt;strong&gt;all consumers re-render.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here’s the kicker: even if your components only use part of the context, or even if the value they read is unchanged inside a new object, they still re-render.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why React doesn’t do partial updates inside context
&lt;/h2&gt;

&lt;p&gt;React Context is designed as a simple pub-sub model: context value changes notify &lt;strong&gt;all&lt;/strong&gt; consumers. React doesn’t track which consumer reads which part of the context value ,  it can’t know which slice matters to a component without explicit help.&lt;/p&gt;

&lt;p&gt;Under the hood, when the provider’s value changes (meaning the reference changes), React triggers an update for every subscribed consumer. This is a fast operation for a handful of components, but not when your context is huge or used widely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common anti-patterns that cause unnecessary re-renders
&lt;/h2&gt;

&lt;h2&gt;
  
  
  1. Passing inline objects or functions as &lt;code&gt;value&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Developers often write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ThemeContext&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;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;toggleTheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a new object and a new function on &lt;strong&gt;every render&lt;/strong&gt;, so the context value changes every time, even if &lt;code&gt;theme&lt;/code&gt; is the same.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Memoize the value with &lt;code&gt;useMemo&lt;/code&gt; and functions with &lt;code&gt;useCallback&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toggleTheme&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&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;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toggleTheme&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toggleTheme&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ThemeContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Provider&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/ThemeContext.Provider&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Using one big context for many unrelated values
&lt;/h2&gt;

&lt;p&gt;Storing lots of state in a single context causes any change to trigger re-renders everywhere. For example, a context with &lt;code&gt;{ user, theme, settings, notifications }&lt;/code&gt; means a theme change re-renders components that only care about user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Split your context into multiple smaller contexts, each responsible for a specific piece of state.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Deeply nested consumers that don’t memoize
&lt;/h2&gt;

&lt;p&gt;If you use &lt;code&gt;useContext&lt;/code&gt; inside deeply nested components without memoization, you may get more re-renders than necessary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Use &lt;code&gt;React.memo&lt;/code&gt; or &lt;code&gt;useMemo&lt;/code&gt; to avoid re-renders when props or context values haven’t changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Profiling context-related re-renders
&lt;/h2&gt;

&lt;p&gt;React DevTools Profiler is your friend here.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start recording.&lt;/li&gt;
&lt;li&gt;Trigger your context update.&lt;/li&gt;
&lt;li&gt;Look for components that re-render unexpectedly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You’ll often notice many components updating even if they don’t use the changed part of the context.&lt;/p&gt;

&lt;h2&gt;
  
  
  How React Context actually triggers updates
&lt;/h2&gt;

&lt;p&gt;Under the hood, React keeps a linked list of all consumers subscribed to the provider. When the context value changes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;React compares the new value with the old reference using &lt;code&gt;Object.is&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If they differ, React schedules an update for &lt;strong&gt;all consumers&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Each consumer re-renders and reads the new context value.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is no built-in mechanism to track which consumers care about which parts of the value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternatives and patterns to optimize context usage
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Use multiple contexts
&lt;/h2&gt;

&lt;p&gt;Split your state across several contexts. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UserContext&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ThemeContext&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SettingsContext&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This way, updating theme won’t cause user-related components to re-render.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use selectors with &lt;code&gt;useContextSelector&lt;/code&gt; (third-party)
&lt;/h2&gt;

&lt;p&gt;Libraries like &lt;a href="https://github.com/dai-shi/use-context-selector" rel="noopener noreferrer"&gt;&lt;code&gt;use-context-selector&lt;/code&gt;&lt;/a&gt; let you subscribe to specific slices of context values, so components re-render only when their selected slice changes.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useContextSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ThemeContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only components whose selected slice changes will re-render.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lift state closer to where it’s needed
&lt;/h2&gt;

&lt;p&gt;Sometimes, context is used for convenience but state can live closer to components that need it, reducing unnecessary propagations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memoize components and values
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;React.memo&lt;/code&gt;, &lt;code&gt;useMemo&lt;/code&gt;, and &lt;code&gt;useCallback&lt;/code&gt; to avoid re-renders caused by changing references.&lt;/p&gt;

&lt;h2&gt;
  
  
  When context re-renders are unavoidable
&lt;/h2&gt;

&lt;p&gt;Sometimes, your app is small enough or updates infrequent enough that simple memoization suffices. Premature optimization adds needless complexity.&lt;/p&gt;

&lt;p&gt;But if you spot lag, jank, or excessive re-renders tied to context, start here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Profile with React DevTools&lt;/li&gt;
&lt;li&gt;Check your context value references&lt;/li&gt;
&lt;li&gt;Split contexts&lt;/li&gt;
&lt;li&gt;Use selectors if needed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;React Context is incredibly useful but comes with this gotcha: &lt;strong&gt;any change to the context value triggers all consumers to re-render.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Understanding this helps you avoid performance pitfalls and write snappy apps. Next time your context update slows things down, you’ll know exactly where to look and what to fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  Helpful learning resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/" rel="noopener noreferrer"&gt;MDN Web Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/" rel="noopener noreferrer"&gt;web.dev performance guidance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://react.dev/" rel="noopener noreferrer"&gt;React documentation&lt;/a&gt;
Originally published at &lt;a href="https://blog.mspk.me/posts/debugging-react-context-performance-issues-when-and-why-components-re-render" rel="noopener noreferrer"&gt;Under The Hood&lt;/a&gt;.
Get the next deep dive in your inbox: &lt;a href="https://blog.mspk.me/#subscribe" rel="noopener noreferrer"&gt;subscribe to Under The Hood&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>performance</category>
      <category>context</category>
      <category>frontend</category>
    </item>
    <item>
      <title>HTTP QUERY is here: what changes for frontend data fetching?</title>
      <dc:creator>Srikar Phani Kumar Marti</dc:creator>
      <pubDate>Wed, 15 Jul 2026 11:22:57 +0000</pubDate>
      <link>https://dev.to/mspk97/http-query-is-here-what-changes-for-frontend-data-fetching-456j</link>
      <guid>https://dev.to/mspk97/http-query-is-here-what-changes-for-frontend-data-fetching-456j</guid>
      <description>&lt;p&gt;You’ve probably hit this frustrating moment: your frontend needs to fetch data with complex filters or queries, but GET just won’t cut it because your URL grows unwieldy or exceeds length limits. So you switch to POST, shove your JSON search parameters in the body, and call it a day.&lt;/p&gt;

&lt;p&gt;Except now your POST request is meant to be a safe, read-only fetch. But HTTP semantics say POST is neither safe nor idempotent. That kills caching on CDNs, disables certain browser optimizations, and confuses intermediaries. You’re stuck in a weird limbo.&lt;/p&gt;

&lt;p&gt;Enter HTTP QUERY ,  the new kid on the block, standardized by RFC 10008 in June 2026. It's safe and idempotent like GET but also lets you send a request body like POST. Sounds like the best of both worlds, right? Let’s dig into what this means for frontend data fetching and why you probably shouldn’t just flip the switch on all your POST /search routes tomorrow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is HTTP QUERY, exactly?
&lt;/h2&gt;

&lt;p&gt;HTTP QUERY is a new request method defined to address a long-standing gap: how to send complex, structured queries in a safe, cacheable way.&lt;/p&gt;

&lt;p&gt;GET requests are safe and idempotent, which means they’re perfect for fetching data. But their parameters live in the URL, which has length limits (around 2000 characters in many browsers) and poor ergonomics for deeply nested or complex JSON-like filters.&lt;/p&gt;

&lt;p&gt;POST requests let you send a rich body but are considered unsafe and non-idempotent by default. This kills caching on the CDN and browser layers, since intermediaries assume POST changes server state.&lt;/p&gt;

&lt;p&gt;HTTP QUERY combines the best of both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It’s &lt;strong&gt;safe&lt;/strong&gt; and &lt;strong&gt;idempotent&lt;/strong&gt;, so intermediaries treat it like GET.&lt;/li&gt;
&lt;li&gt;It supports a &lt;strong&gt;request body&lt;/strong&gt;, so you can send complex JSON queries without cramming them into a URL.&lt;/li&gt;
&lt;li&gt;It keeps your URLs clean and uncluttered.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why does safe + body matter?
&lt;/h2&gt;

&lt;p&gt;The HTTP spec has historically struggled with this. Safe methods (GET, HEAD) can’t have bodies according to some interpretations, so complex queries get shoved into URLs or POST bodies.&lt;/p&gt;

&lt;p&gt;That leads to practical downsides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cache busting&lt;/strong&gt;: CDNs and browsers ignore POST responses for caching, even when you know the request is read-only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proxy confusion&lt;/strong&gt;: Intermediaries treat POST as a possible state-changing method, so they don’t optimize or retry it safely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer confusion&lt;/strong&gt;: You have to choose between semantic correctness and practical needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;HTTP QUERY says: let’s have a safe, idempotent method that supports bodies, so everyone knows it’s a read-only fetch with complex input.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does this mean for frontend data fetching?
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Complex search filters get a first-class home
&lt;/h2&gt;

&lt;p&gt;Say you’re building a search UI with filters like date ranges, nested facets, or fuzzy matching. Encoding these in query strings quickly becomes a nightmare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/search?dateFrom=2024-01-01&amp;amp;dateTo=2024-01-31&amp;amp;filter[category]=books&amp;amp;filter[price][min]=10&amp;amp;filter[price][max]=50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ugly, error-prone, and brittle. Plus, some browsers or proxies might truncate the URL.&lt;/p&gt;

&lt;p&gt;With HTTP QUERY, you can move this entire JSON payload into the request body cleanly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;QUERY&lt;/span&gt; &lt;span class="nn"&gt;/search&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dateFrom"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-01-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dateTo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-01-31"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"filter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"books"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps your URLs short and your request format consistent.&lt;/p&gt;

&lt;h2&gt;
  
  
  GraphQL-style queries get a simpler transport
&lt;/h2&gt;

&lt;p&gt;GraphQL requests often use POST because their queries live in the body. But that means you lose GET semantics, caching, and some CDN optimizations.&lt;/p&gt;

&lt;p&gt;HTTP QUERY lets you send a GraphQL query in the body while keeping the request safe and idempotent, opening doors for better caching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caching and CDN behavior improve
&lt;/h2&gt;

&lt;p&gt;Because HTTP QUERY is explicitly safe and idempotent, CDNs and browsers can cache these requests like GETs. That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Edge caches serve responses faster&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reduced server load&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Better offline and retry behavior&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But this depends on your CDN and browser supporting HTTP QUERY correctly ,  which is still a work in progress.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser and framework support is still catching up
&lt;/h2&gt;

&lt;p&gt;As of mid-2026, HTTP QUERY is brand new. Most browsers have started to support it at the network level, but APIs like &lt;code&gt;fetch()&lt;/code&gt; and &lt;code&gt;XMLHttpRequest&lt;/code&gt; are still rolling out support.&lt;/p&gt;

&lt;p&gt;Frameworks, HTTP clients, and intermediaries (proxies, CDNs) also need updates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;fetch()&lt;/strong&gt;: New versions accept &lt;code&gt;method: 'QUERY'&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Axios, superagent&lt;/strong&gt;: Libraries need to add explicit support.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDNs&lt;/strong&gt;: Providers must recognize QUERY as safe and cacheable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Until then, you may need fallbacks or polyfills.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why you shouldn’t blindly replace all POST /search calls
&lt;/h2&gt;

&lt;p&gt;It’s tempting to think: "Great, HTTP QUERY solves all my problems, I’ll just swap POST for QUERY everywhere!"&lt;/p&gt;

&lt;p&gt;But hold your horses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some POST /search endpoints trigger server-side side effects or logging that aren’t safe. Changing method semantics without audit risks bugs.&lt;/li&gt;
&lt;li&gt;Caching behavior depends on correct cache headers and CDN support; otherwise, you might see stale data or cache misses.&lt;/li&gt;
&lt;li&gt;Client and server libraries need to handle QUERY correctly, or you’ll get unexpected failures.&lt;/li&gt;
&lt;li&gt;Browsers and older environments might reject unknown methods or fail silently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Best practice? &lt;strong&gt;Use HTTP QUERY for new endpoints designed for safe, read-only queries with complex bodies.&lt;/strong&gt; For existing POST endpoints, evaluate method semantics carefully before switching.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to experiment with HTTP QUERY today
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check your HTTP clients&lt;/strong&gt;: Try &lt;code&gt;fetch('/search', { method: 'QUERY', body: JSON.stringify(payload) })&lt;/code&gt; in browsers with up-to-date support.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update your backend&lt;/strong&gt;: Make your server recognize and handle QUERY requests like GET but parse the body.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test CDN behavior&lt;/strong&gt;: Confirm your edge cache treats QUERY like GET for caching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor fallbacks&lt;/strong&gt;: Have fallbacks to POST or GET for clients or intermediaries lacking support.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What I learned debugging QUERY support in my apps
&lt;/h2&gt;

&lt;p&gt;I tried switching a complex filter endpoint from POST to QUERY in a staging app. It was smooth on the server side ,  minimal code changes. But the frontend fetch calls failed in older browsers. My CDN ignored QUERY requests initially, so no caching kicks in.&lt;/p&gt;

&lt;p&gt;Adding a feature detection layer and a fallback to POST for unsupported clients fixed the problem. Also, ensuring my server sent proper Cache-Control headers was critical to leverage caching.&lt;/p&gt;

&lt;p&gt;This showed me HTTP QUERY is promising but still early-stage. You’ll want to adopt gradually and monitor carefully.&lt;/p&gt;

&lt;h2&gt;
  
  
  So is HTTP QUERY the future?
&lt;/h2&gt;

&lt;p&gt;It feels like a neat, principled way to fix a longstanding HTTP awkwardness. For frontend devs wrestling with complex read-only queries, it means cleaner URLs, better caching, and clearer semantics.&lt;/p&gt;

&lt;p&gt;But like any new protocol feature, it needs ecosystem support, adoption, and time to mature.&lt;/p&gt;

&lt;p&gt;In the meantime, keep using POST for complex queries when needed, but watch HTTP QUERY’s progress. When tooling and infrastructure are ready, it’ll be a handy tool in your data fetching toolbox.&lt;/p&gt;

&lt;p&gt;And if nothing else, it’s nice to see the web’s foundations still evolving ,  one tiny method at a time.&lt;/p&gt;




&lt;p&gt;Originally published at &lt;a href="https://blog.mspk.me/posts/http-query-is-here-what-changes-for-frontend-data-fetching" rel="noopener noreferrer"&gt;Under The Hood&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Get the next deep dive in your inbox: &lt;a href="https://blog.mspk.me/#subscribe" rel="noopener noreferrer"&gt;subscribe to Under The Hood&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>http</category>
      <category>frontend</category>
      <category>datafetching</category>
      <category>web</category>
    </item>
    <item>
      <title>New Newsletter Alert: Under The Hood Has a New Home</title>
      <dc:creator>Srikar Phani Kumar Marti</dc:creator>
      <pubDate>Tue, 14 Jul 2026 10:49:57 +0000</pubDate>
      <link>https://dev.to/mspk97/new-newsletter-alert-under-the-hood-has-a-new-home-23n4</link>
      <guid>https://dev.to/mspk97/new-newsletter-alert-under-the-hood-has-a-new-home-23n4</guid>
      <description>&lt;p&gt;I have wanted this one to feel a little loud, so here it is: &lt;strong&gt;Under The Hood has a new home.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For a while, I was publishing on Substack. It was convenient, familiar, and honestly a pretty good place to get started. But at some point I wanted the newsletter to feel more like mine. Not just another page inside someone else's platform, but a real owned space where the archive, design, subscribers, and publishing flow all live together.&lt;/p&gt;

&lt;p&gt;So I moved.&lt;/p&gt;

&lt;p&gt;I ported my old Substack posts into this new newsletter, and that turned out to be more interesting than I expected. There is something weirdly satisfying about watching an archive move from one system into another: titles, dates, cover images, old essays, little formatting quirks, all of it getting cleaned up and finding a new shape. It felt less like a migration and more like unpacking boxes after moving into a new place.&lt;/p&gt;

&lt;p&gt;The stack is intentionally simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next.js for the site and admin workflow&lt;/li&gt;
&lt;li&gt;Postgres on Neon for posts, subscribers, publishing state, and platform records&lt;/li&gt;
&lt;li&gt;Prisma for the database layer&lt;/li&gt;
&lt;li&gt;Resend for newsletter email flows&lt;/li&gt;
&lt;li&gt;Vercel for hosting&lt;/li&gt;
&lt;li&gt;A small private admin app for drafting, publishing, syndication, and notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is also a lightweight content pipeline behind the scenes, mostly so I can keep the writing process organized: ideas, drafts, publishing status, platform links, and the occasional reminder when something needs attention. I do not want the tooling to become the story, but I do want the system to make publishing easier to keep doing.&lt;/p&gt;

&lt;p&gt;And that is the big difference between building a custom newsletter and just posting on LinkedIn or another feed.&lt;/p&gt;

&lt;p&gt;A social post is borrowed attention. It can travel fast, but it lives inside someone else's rules, ranking system, and interface. A newsletter and owned archive are slower in a good way. They are easier to return to. They are easier to search. They can have their own voice, structure, and rhythm. They do not disappear into the feed ten minutes after you publish them.&lt;/p&gt;

&lt;p&gt;I will still post on LinkedIn and other platforms when it makes sense. Those places are useful for discovery. But this site is the source of truth now. The full essays live here. The archive lives here. The subscriber list starts here.&lt;/p&gt;

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

&lt;p&gt;This first post is mostly a marker in the ground: the new newsletter is live, the old posts have been moved over, and the next batch of writing starts from here.&lt;/p&gt;

&lt;p&gt;If you have suggestions for what I should write next, or if there is something from the old Substack archive you want me to revisit, please leave a comment wherever you found this post or send the suggestion my way. I am going to start building the next backlog from here.&lt;/p&gt;

&lt;p&gt;And if you want future deep dives in your inbox, please subscribe. I would love for you to come along.&lt;/p&gt;




&lt;p&gt;Originally published at &lt;a href="https://blog.mspk.me/posts/new-newsletter-alert" rel="noopener noreferrer"&gt;Under The Hood&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Get the next deep dive in your inbox: &lt;a href="https://blog.mspk.me/#subscribe" rel="noopener noreferrer"&gt;subscribe to Under The Hood&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>newsletter</category>
      <category>substack</category>
      <category>publishing</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Optimizing React Suspense: How Concurrent Mode Handles Data Fetching and Rendering</title>
      <dc:creator>Srikar Phani Kumar Marti</dc:creator>
      <pubDate>Mon, 13 Jul 2026 10:55:48 +0000</pubDate>
      <link>https://dev.to/mspk97/optimizing-react-suspense-how-concurrent-mode-handles-data-fetching-and-rendering-5efc</link>
      <guid>https://dev.to/mspk97/optimizing-react-suspense-how-concurrent-mode-handles-data-fetching-and-rendering-5efc</guid>
      <description>&lt;p&gt;You know that awkward moment when your Suspense fallback flashes on the screen for just a split second before your real content pops in? Sometimes it feels like the loading spinner is playing peekaboo, and you wonder if you’re doing something wrong.&lt;/p&gt;

&lt;p&gt;Spoiler: React’s Concurrent Mode isn’t broken. Suspense is designed to juggle data fetching and rendering in a way that feels smooth ,  if you understand what it’s doing behind the scenes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The familiar pain: flickering loading states
&lt;/h2&gt;

&lt;p&gt;Imagine you have a React component that fetches user data. You wrap it in a &lt;code&gt;&amp;lt;Suspense fallback={&amp;lt;Spinner /&amp;gt;}&amp;gt;&lt;/code&gt;. When the data isn’t ready, the spinner shows. When the data arrives, the real UI replaces it.&lt;/p&gt;

&lt;p&gt;But sometimes you see the spinner flash, then the content appears, then maybe the spinner shows again briefly. This flicker isn’t just a UI glitch ,  it’s React’s scheduler at work.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;h2&gt;
  
  
  React’s Scheduler and Priorities: The secret sauce
&lt;/h2&gt;

&lt;p&gt;Concurrent Mode introduced a sophisticated scheduler inside React. Instead of rendering everything synchronously, React breaks work into small units and assigns priorities.&lt;/p&gt;

&lt;p&gt;When data fetching suspends, React marks that work as low priority. It shows the fallback immediately ,  because that’s the quickest thing to do. Meanwhile, React keeps trying to fetch data and prepare the real UI.&lt;/p&gt;

&lt;p&gt;The scheduler can pause, resume, or even abandon work if higher priority tasks come in (like user input). This flexibility lets React keep the app responsive, but it also means your Suspense fallback might show or hide multiple times as React juggles work.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Suspense boundaries manage concurrent rendering
&lt;/h2&gt;

&lt;p&gt;Suspense boundaries are like gates controlling what the user sees. When a component suspends (e.g., waiting for a Promise), React doesn’t throw an error ,  it throws the Promise up the tree to the nearest Suspense boundary.&lt;/p&gt;

&lt;p&gt;That boundary catches it and shows the fallback UI.&lt;/p&gt;

&lt;p&gt;But here’s the catch: React doesn’t block the entire tree. With Concurrent Mode, it continues working on other parts of the UI that don’t depend on the suspended data.&lt;/p&gt;

&lt;p&gt;This means multiple Suspense boundaries can show independent loading states, and content can appear incrementally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data fetching under the hood: how React knows when to show what
&lt;/h2&gt;

&lt;p&gt;React Suspense for data fetching usually relies on throwing Promises from your components ,  a pattern popularized by libraries like Relay and React Cache.&lt;/p&gt;

&lt;p&gt;When a component tries to read data that isn’t ready, it throws a Promise. React catches it, shows the fallback, and retries the render when the Promise resolves.&lt;/p&gt;

&lt;p&gt;This retry is scheduled with the scheduler’s priority rules. If the Promise resolves quickly, React can skip showing the fallback at all, resulting in zero loading flicker.&lt;/p&gt;

&lt;p&gt;If it takes longer, the fallback shows up to give the user feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Suspense fallback flickers ,  and how to fix it
&lt;/h2&gt;

&lt;p&gt;The flicker often happens because the Promise resolves just after React commits the fallback UI.&lt;/p&gt;

&lt;p&gt;React can’t know for sure if the data will be ready immediately or soon, so it shows the fallback first. Then it re-renders the content. This is a tradeoff between showing feedback early and avoiding unnecessary spinners.&lt;/p&gt;

&lt;p&gt;To reduce flicker:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use techniques like &lt;strong&gt;delayMs&lt;/strong&gt; and &lt;strong&gt;timeoutMs&lt;/strong&gt; in libraries like React Suspense’s experimental helpers to avoid showing fallback UI for very fast loads.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cache data aggressively so that components can read synchronous results immediately.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Break your UI into smaller Suspense boundaries so React can show partial content as soon as it's ready.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use &lt;code&gt;startTransition&lt;/code&gt; from React 18 to mark data loading as low priority, allowing React to prioritize user interactions.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What actually happens during a Suspense update
&lt;/h2&gt;

&lt;p&gt;Let’s walk through a concrete example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your component tries to read user data.&lt;/li&gt;
&lt;li&gt;The data isn’t ready, so it throws a Promise.&lt;/li&gt;
&lt;li&gt;React catches this, marks the component as suspended, and shows the fallback.&lt;/li&gt;
&lt;li&gt;The Promise resolves.&lt;/li&gt;
&lt;li&gt;React schedules a low priority update to retry rendering the component.&lt;/li&gt;
&lt;li&gt;React renders the component with the data, replaces the fallback.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 3 → step 6 can happen quickly or with noticeable delay, depending on network and cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concurrent Mode juggling multiple updates
&lt;/h2&gt;

&lt;p&gt;Concurrent Mode also means React can interrupt a render mid-way.&lt;/p&gt;

&lt;p&gt;If a high priority update arrives (like user input), React can pause the Suspense update, respond to the user, then resume rendering.&lt;/p&gt;

&lt;p&gt;This behavior makes apps feel snappy but adds complexity to Suspense timing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Suspense is still evolving
&lt;/h2&gt;

&lt;p&gt;React Suspense and Concurrent Mode are a work in progress. The React team continuously improves the scheduler heuristics to minimize flicker and improve perceived performance.&lt;/p&gt;

&lt;p&gt;Meanwhile, understanding these internals helps you design data fetching and UI boundaries that fit React’s model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Next time your Suspense fallback flashes unexpectedly, don’t panic. It’s React juggling priorities to give your users the best experience.&lt;/p&gt;

&lt;p&gt;With some tuning ,  smarter caching, strategic Suspense boundaries, and &lt;code&gt;startTransition&lt;/code&gt; ,  you can help React show your UI at just the right moment.&lt;/p&gt;

&lt;p&gt;Suspense isn’t magic, but it’s a clever balancing act. Knowing what happens under the hood is the first step to mastering it.&lt;/p&gt;




&lt;p&gt;Originally published at &lt;a href="https://blog.mspk.me/posts/optimizing-react-suspense-how-concurrent-mode-handles-data-fetching-and-rendering" rel="noopener noreferrer"&gt;Under The Hood&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Get the next deep dive in your inbox: &lt;a href="https://blog.mspk.me/#subscribe" rel="noopener noreferrer"&gt;subscribe to Under The Hood&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>react</category>
      <category>suspense</category>
      <category>performance</category>
      <category>javascript</category>
    </item>
    <item>
      <title>React Server Components: Under the Hood of Streaming and Client Boundaries</title>
      <dc:creator>Srikar Phani Kumar Marti</dc:creator>
      <pubDate>Sat, 11 Jul 2026 20:24:08 +0000</pubDate>
      <link>https://dev.to/mspk97/react-server-components-under-the-hood-of-streaming-and-client-boundaries-57po</link>
      <guid>https://dev.to/mspk97/react-server-components-under-the-hood-of-streaming-and-client-boundaries-57po</guid>
      <description>&lt;p&gt;You’ve probably hit that moment where your React app feels sluggish, or where splitting code between client and server logic gets messy fast. React Server Components (RSC) promise to fix that by letting you run components on the server while streaming their output to the client ,  all without losing interactivity. But how does this actually work under the hood?&lt;/p&gt;

&lt;p&gt;I dug into the mechanics to understand why the new streaming format feels so fluid and how React knows when to run what.&lt;/p&gt;

&lt;h2&gt;
  
  
  The familiar pain: mixing server and client logic
&lt;/h2&gt;

&lt;p&gt;Before RSC, if you wanted to fetch data on the server and then render it on the client, you usually did something like data fetching in &lt;code&gt;getServerSideProps&lt;/code&gt;, then rendering with client-side hooks or hydration. It worked but had downsides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You send the full HTML only after data arrives, blocking first paint.&lt;/li&gt;
&lt;li&gt;Client code bundles bloat with data-fetching logic.&lt;/li&gt;
&lt;li&gt;Hydration can cause flickers or UI mismatches.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;React Server Components flip this by letting you write components that run entirely on the server ,  sending only serialized UI instructions to the client. But it’s not just a magic serialization trick.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streaming the UI: chunking the response
&lt;/h2&gt;

&lt;p&gt;When you request an RSC-enabled React app, the server doesn’t wait to render the whole tree. Instead, it starts streaming chunks of serialized component data immediately.&lt;/p&gt;

&lt;p&gt;Imagine the server is assembling a puzzle but sends you pieces as soon as they’re ready, not the whole picture at once. React serializes each component’s output into a special format ,  basically a structured JSON that encodes elements, props, and where client boundaries are.&lt;/p&gt;

&lt;p&gt;This stream arrives to the browser, where React’s client runtime incrementally parses each chunk and merges it into the current UI.&lt;/p&gt;

&lt;p&gt;That means your users start seeing content way faster, because React never waits for the entire tree before flushing something.&lt;/p&gt;

&lt;h2&gt;
  
  
  Client boundaries: the handshake between server and client
&lt;/h2&gt;

&lt;p&gt;A core concept is the &lt;strong&gt;client boundary&lt;/strong&gt; ,  where a React Server Component hands off to a client component. Under the hood, React marks these boundaries explicitly in the serialized stream.&lt;/p&gt;

&lt;p&gt;Here’s what happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Server renders a subtree and encounters a client component.&lt;/li&gt;
&lt;li&gt;Instead of serializing that client component’s UI, it inserts a placeholder with a unique ID.&lt;/li&gt;
&lt;li&gt;The client runtime sees the placeholder, fetches the necessary client code bundle asynchronously, and then hydrates that component in-place.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This handshake means server and client code stay separate, but React stitches them together seamlessly.&lt;/p&gt;

&lt;h2&gt;
  
  
  How React serializes components
&lt;/h2&gt;

&lt;p&gt;The serialization format React uses isn’t just plain JSON. It’s a custom encoding that preserves React elements’ shape and references, plus some metadata:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Component type identifiers&lt;/li&gt;
&lt;li&gt;Props, including special handling for functions and client references&lt;/li&gt;
&lt;li&gt;Boundary markers that tell the client runtime where to hydrate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, if a prop is a client function, it gets replaced by a reference ID, so the client can wire up event handlers correctly after hydration.&lt;/p&gt;

&lt;p&gt;This encoding is efficient and incremental ,  React streams each component or subtree as it’s ready, so your app can render progressively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streaming versus traditional SSR
&lt;/h2&gt;

&lt;p&gt;You might ask, isn’t server-side rendering streaming already a thing?&lt;/p&gt;

&lt;p&gt;Yes, but with traditional SSR, the server streams fully rendered HTML. React Server Components stream serialized React elements instead, which are then rendered on the client. This gives React more control to incrementally hydrate only the parts marked as client boundaries.&lt;/p&gt;

&lt;p&gt;It also means your server components never send any client JavaScript or event handlers directly, just the instructions needed to build the UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architectural implications: clean split of concerns
&lt;/h2&gt;

&lt;p&gt;Because server components can’t contain client-only code, your data fetching, secret keys, and heavy logic stay safely on the server.&lt;/p&gt;

&lt;p&gt;Meanwhile, client components handle interactivity, hooks, and browser-only APIs. The explicit boundaries mean you can reason about what runs where without messy hacks.&lt;/p&gt;

&lt;p&gt;On top of that, streaming lets your app show meaningful UI faster, improving perceived performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned debugging a mismatch
&lt;/h2&gt;

&lt;p&gt;I ran into a tricky bug when I mixed a client component inside a server subtree but forgot to mark it properly. React’s stream sent a placeholder, but the client runtime never fetched the corresponding JS bundle.&lt;/p&gt;

&lt;p&gt;Result? A blank spot in the UI.&lt;/p&gt;

&lt;p&gt;The fix was to ensure I imported client components with a special directive (&lt;code&gt;'use client'&lt;/code&gt;) so React could identify and serialize the boundary correctly.&lt;/p&gt;

&lt;p&gt;This highlighted how crucial those boundaries are ,  the entire streaming and hydration dance depends on them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;React Server Components are more than just a new API ,  they’re a streaming protocol that rethinks how server and client communicate. By serializing component trees incrementally and marking client boundaries explicitly, they let you split logic cleanly and deliver UI faster.&lt;/p&gt;

&lt;p&gt;If you’ve felt friction between server and client code in React apps, digging into how RSC streams and hydrates might inspire new ways to architect your projects ,  and build apps that feel snappier without swallowing your CPU or bandwidth.&lt;/p&gt;




&lt;p&gt;Originally published at &lt;a href="https://blog.mspk.me/posts/react-server-components-under-the-hood-of-streaming-and-client-boundaries" rel="noopener noreferrer"&gt;Under The Hood&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Get the next deep dive in your inbox: &lt;a href="https://blog.mspk.me/#subscribe" rel="noopener noreferrer"&gt;subscribe to Under The Hood&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>react</category>
      <category>servercomponents</category>
      <category>streaming</category>
      <category>frontendarchitecture</category>
    </item>
    <item>
      <title>I built a backend platform that generates REST APIs from a schema — no code, no server setup</title>
      <dc:creator>Srikar Phani Kumar Marti</dc:creator>
      <pubDate>Fri, 29 May 2026 04:09:40 +0000</pubDate>
      <link>https://dev.to/mspk97/i-built-a-backend-platform-that-generates-rest-apis-from-a-schema-no-code-no-server-setup-184g</link>
      <guid>https://dev.to/mspk97/i-built-a-backend-platform-that-generates-rest-apis-from-a-schema-no-code-no-server-setup-184g</guid>
      <description>&lt;p&gt;Every side project I've shipped starts the same way: I have a frontend idea, and I immediately have to stop and go build a backend for it.&lt;/p&gt;

&lt;p&gt;Not because the backend is hard. Because it's &lt;em&gt;tedious&lt;/em&gt;. The same patterns, every time. Define a model. Wire up routes. Handle errors. Write docs nobody reads. Set up auth. Repeat.&lt;/p&gt;

&lt;p&gt;At some point I stopped asking "how do I build this backend faster" and started asking "why am I building it at all?"&lt;/p&gt;

&lt;p&gt;That question became &lt;a href="https://crudly.org" rel="noopener noreferrer"&gt;Crudly&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually does
&lt;/h2&gt;

&lt;p&gt;You create a project, add a collection (think: a database table with a name and fields), and Crudly instantly gives you a live REST API at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;https://api.crudly.org/v1/{your-project}/{collection-name}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GET, POST, PUT, DELETE — all working, all authenticated, all documented. You didn't write a single line of backend code.&lt;/p&gt;

&lt;p&gt;Here's what that looks like in practice. I created a project called &lt;code&gt;sample&lt;/code&gt;, added a &lt;code&gt;todos&lt;/code&gt; collection, and within about 30 seconds I had:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List all todos&lt;/span&gt;
curl &lt;span class="s2"&gt;"https://api.crudly.org/v1/sample/todos"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt;

&lt;span class="c"&gt;# Create a todo&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.crudly.org/v1/sample/todos"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"title": "Repair Watch", "done": false}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No Express. No Postgres migrations. No deploy step.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture decision that makes this possible
&lt;/h2&gt;

&lt;p&gt;Crudly doesn't generate code. There's no Express app being scaffolded and deployed per user. That approach sounds appealing until you think about what it costs — cold starts, isolated infra per project, deployment latency, and a maintenance nightmare.&lt;/p&gt;

&lt;p&gt;Instead, the API server uses &lt;strong&gt;dynamic runtime routing&lt;/strong&gt;. Every request to &lt;code&gt;api.crudly.org/v1/{project}/{collection}&lt;/code&gt; hits the same handler. At request time, it reads your schema configuration, validates the request against it, and serves or persists data accordingly.&lt;/p&gt;

&lt;p&gt;The schema is the source of truth. The routes don't exist as code — they're resolved at runtime from what you defined in the dashboard.&lt;/p&gt;

&lt;p&gt;This is the same pattern that makes tools like Hasura and PostgREST compelling, except Crudly's surface area is intentionally smaller. You're not writing GraphQL. You're not pointing it at your own Postgres instance. You create a collection, you get endpoints. That's the whole contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  What ships with it
&lt;/h2&gt;

&lt;p&gt;The thing I kept running into with minimal API tools is that they solve one problem and leave you to figure out everything adjacent. Crudly ships the full surface:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Playground&lt;/strong&gt; — A browser-based HTTP client (Postman style) built into the dashboard. Select your collection, pick a method, fire a request. I built this because switching to Postman or Insomnia mid-flow kills momentum. The response comes back formatted, with status codes and timing.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0xssbschdzw2frk7slu8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0xssbschdzw2frk7slu8.png" alt="Playground" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto-generated docs&lt;/strong&gt; — Every collection gets docs that reflect the actual schema. They update when the schema changes. The curl examples use your real endpoint URLs. No Swagger YAML to maintain.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flrwnflf7l018z9ip6vzi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flrwnflf7l018z9ip6vzi.png" alt="Auto Generated Docs" width="799" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Request logs&lt;/strong&gt; — Real-time traffic visible from the dashboard, filterable by method, status, endpoint, and date. Useful enough that I've caught integration bugs in client apps just from watching the log stream.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwrh5n5nombbenwzrujjk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwrh5n5nombbenwzrujjk.png" alt="Request Logs" width="799" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Webhooks&lt;/strong&gt; — POST callbacks on create, update, or delete events per collection. If you're integrating with n8n, Zapier, or your own service — this is how you wire it up without polling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API key auth&lt;/strong&gt; — Read-only and read-write keys per project. Generate and revoke from the dashboard. Keys go in the &lt;code&gt;Authorization: Bearer&lt;/code&gt; header.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Admin Panel&lt;/strong&gt; — A generated data dashboard. Browse records, create entries, edit, delete. Useful during development and for non-technical stakeholders who need to manage content.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I didn't build (deliberately)
&lt;/h2&gt;

&lt;p&gt;No custom logic. No middleware hooks. No computed fields. No joins across collections.&lt;/p&gt;

&lt;p&gt;If you need those things, you need a real backend and you should build one. Crudly is not trying to be Firebase or Supabase. It targets a specific use case: you have a frontend, you need persistent data with a REST interface, and you want it running in under five minutes.&lt;/p&gt;

&lt;p&gt;The constraint is the feature. Every time I was tempted to add "just a bit of custom logic support," I asked whether it would break that five-minute promise. Usually it would.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it actually fits
&lt;/h2&gt;

&lt;p&gt;The use cases where Crudly earns its keep:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prototyping and MVPs&lt;/strong&gt; — Wire up a real API before you've decided whether the product is worth building a proper backend for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend demos and portfolios&lt;/strong&gt; — Stop mocking data in JSON files. Ship something that actually persists.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internal tools&lt;/strong&gt; — A quick admin data store for a side dashboard, without standing up another service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hackathons&lt;/strong&gt; — The entire backend setup takes less time than arguing about which framework to use.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Crudly is live at &lt;a href="https://crudly.org" rel="noopener noreferrer"&gt;crudly.org&lt;/a&gt;. Free plan available. Create a project, add a collection, hit the endpoint — it takes about two minutes to get to a working API call.&lt;/p&gt;

&lt;p&gt;If you run into anything broken or have a use case it doesn't handle, I'm interested. Still actively building this.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>backend</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>I Ran AI Models Directly in the Browser and Measured What It Did to Core Web Vitals</title>
      <dc:creator>Srikar Phani Kumar Marti</dc:creator>
      <pubDate>Sun, 17 May 2026 07:37:49 +0000</pubDate>
      <link>https://dev.to/mspk97/i-ran-ai-models-directly-in-the-browser-and-measured-what-it-did-to-core-web-vitals-4adj</link>
      <guid>https://dev.to/mspk97/i-ran-ai-models-directly-in-the-browser-and-measured-what-it-did-to-core-web-vitals-4adj</guid>
      <description>&lt;p&gt;Everyone is shipping AI features. Sentiment analysis on user input, speech recognition without sending audio to a server, image classification that never leaves the device. The privacy pitch is real, the latency pitch is real. But nobody's asking the obvious question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does running a neural network in the browser actually cost the user?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I decided to find out. I built a benchmark harness, ran four quantized models in Chrome stable, and measured the impact on Core Web Vitals — specifically INP, the metric Google now uses to rank your site.&lt;/p&gt;

&lt;p&gt;Here's what I found.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;The test uses &lt;a href="https://huggingface.co/docs/transformers.js" rel="noopener noreferrer"&gt;Transformers.js&lt;/a&gt; — the library that lets you run Hugging Face models directly in the browser via WebAssembly. All models were loaded in INT8 quantized format (q8) to reflect real production conditions.&lt;/p&gt;

&lt;p&gt;Four models, chosen to cover different architectures and modalities:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Params&lt;/th&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Architecture&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DistilBERT&lt;/td&gt;
&lt;td&gt;66M&lt;/td&gt;
&lt;td&gt;Sentiment analysis&lt;/td&gt;
&lt;td&gt;Encoder (6 layers)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BERT-base&lt;/td&gt;
&lt;td&gt;110M&lt;/td&gt;
&lt;td&gt;Feature extraction&lt;/td&gt;
&lt;td&gt;Encoder (12 layers)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Whisper Tiny&lt;/td&gt;
&lt;td&gt;39M&lt;/td&gt;
&lt;td&gt;Speech recognition&lt;/td&gt;
&lt;td&gt;Encoder-Decoder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MobileViT-S&lt;/td&gt;
&lt;td&gt;5.7M&lt;/td&gt;
&lt;td&gt;Image classification&lt;/td&gt;
&lt;td&gt;Vision Transformer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The benchmark harness is live at &lt;strong&gt;&lt;a href="https://benchmark.mspk.me" rel="noopener noreferrer"&gt;benchmark.mspk.me&lt;/a&gt;&lt;/strong&gt; and open source at &lt;strong&gt;&lt;a href="https://github.com/srikarphanikumar/cwv-ai-benchmark" rel="noopener noreferrer"&gt;github.com/srikarphanikumar/cwv-ai-benchmark&lt;/a&gt;&lt;/strong&gt;. Run it yourself.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is INP and Why Does It Matter?
&lt;/h2&gt;

&lt;p&gt;INP (Interaction to Next Paint) replaced First Input Delay as Google's interactivity metric in March 2024. It measures how long it takes for the browser to respond to a user interaction — a click, a tap, a keypress — and paint the result.&lt;/p&gt;

&lt;p&gt;Google's thresholds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Good&lt;/strong&gt;: under 200ms&lt;/li&gt;
&lt;li&gt;⚠️ &lt;strong&gt;Needs Improvement&lt;/strong&gt;: 200–500ms&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Poor&lt;/strong&gt;: over 500ms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;INP affects your search ranking. More importantly, it affects whether users feel your app is responsive or broken.&lt;/p&gt;

&lt;p&gt;When you run neural network inference on the browser's main thread, you're blocking it. That means if a user clicks something while inference is running, their click won't be processed until the model finishes. That delay IS your INP.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Results
&lt;/h2&gt;

&lt;p&gt;Here's the full table from Chrome stable on an Apple M-series MacBook Pro, 16GB RAM:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Load Time&lt;/th&gt;
&lt;th&gt;Avg Inference&lt;/th&gt;
&lt;th&gt;INP&lt;/th&gt;
&lt;th&gt;INP Class&lt;/th&gt;
&lt;th&gt;Mem Δ&lt;/th&gt;
&lt;th&gt;Mem Pressure&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DistilBERT&lt;/td&gt;
&lt;td&gt;7.85s&lt;/td&gt;
&lt;td&gt;25.1ms ±0.5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;27.8ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Good&lt;/td&gt;
&lt;td&gt;+59.6MB&lt;/td&gt;
&lt;td&gt;2.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BERT-base&lt;/td&gt;
&lt;td&gt;6.07s&lt;/td&gt;
&lt;td&gt;83.3ms ±1.5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;85.0ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⚠️ Needs Improvement&lt;/td&gt;
&lt;td&gt;+65.3MB&lt;/td&gt;
&lt;td&gt;4.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Whisper Tiny&lt;/td&gt;
&lt;td&gt;6.71s&lt;/td&gt;
&lt;td&gt;496.9ms ±6.2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;540.3ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ Poor&lt;/td&gt;
&lt;td&gt;+123.9MB&lt;/td&gt;
&lt;td&gt;7.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MobileViT-S&lt;/td&gt;
&lt;td&gt;1.15s&lt;/td&gt;
&lt;td&gt;66.7ms ±1.0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;75.6ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⚠️ Needs Improvement&lt;/td&gt;
&lt;td&gt;+37.0MB&lt;/td&gt;
&lt;td&gt;8.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Surprising Findings
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Parameter count doesn't predict INP
&lt;/h3&gt;

&lt;p&gt;Whisper Tiny has only 39M parameters — the fewest of any model tested. It also produces the worst INP at 540.3ms, more than 19x worse than DistilBERT which has 66M parameters.&lt;/p&gt;

&lt;p&gt;The culprit is architecture, not size. Whisper is an encoder-decoder model. It doesn't process the full input in a single forward pass — it runs an &lt;strong&gt;autoregressive decode loop&lt;/strong&gt;, generating output tokens one at a time. Each iteration blocks the main thread. The total blocking time accumulates regardless of how aggressively you quantize the weights.&lt;/p&gt;

&lt;p&gt;This means &lt;strong&gt;no amount of quantization will fix Whisper's INP on the main thread&lt;/strong&gt;. It's an architectural constraint, not a tuning problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. MobileViT-S loads 6x faster but still misses "Good"
&lt;/h3&gt;

&lt;p&gt;MobileViT-S loads in 1.15s compared to 6–8 seconds for the text models. That's a huge UX win for initial load. But its INP of 75.6ms puts it in "Needs Improvement" territory despite having only 5.7M parameters.&lt;/p&gt;

&lt;p&gt;Vision transformer inference carries disproportionate cost relative to parameter count in WASM environments. Something to watch if you're building image classification features.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Memory pressure ≠ memory delta
&lt;/h3&gt;

&lt;p&gt;MobileViT-S has the lowest absolute memory consumption (+37MB) but the &lt;strong&gt;highest memory pressure at 8.0%&lt;/strong&gt;. That 37MB represents a larger fraction of the available JS heap than you'd expect — with implications for mid-range Android devices where heap limits are much tighter.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Means for Your Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;If you're building with encoder-only text models (DistilBERT class):&lt;/strong&gt;&lt;br&gt;
You're fine on the main thread. 27.8ms INP is negligible. Trigger inference directly on user interactions without worrying about CWV degradation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you're using larger encoder models (BERT-base class):&lt;/strong&gt;&lt;br&gt;
Don't trigger inference synchronously on interactions. At 85ms, stacking this with other main thread work risks crossing 200ms. Move it to a post-interaction background step — run inference after you've already painted the response.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you're using any encoder-decoder model (Whisper, T5, BART, etc.):&lt;/strong&gt;&lt;br&gt;
You &lt;strong&gt;must&lt;/strong&gt; offload to a Web Worker. This isn't an optimization — it's a requirement. The main thread will be blocked for hundreds of milliseconds no matter what you do. Transformers.js supports Web Worker execution natively:&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;pipeline&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;@xenova/transformers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Run in a Web Worker to avoid blocking main thread&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transcriber&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;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;automatic-speech-recognition&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Xenova/whisper-tiny&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;If you're using vision transformers:&lt;/strong&gt;&lt;br&gt;
Test on actual mobile hardware before shipping. The memory pressure numbers on an M-series Mac will look very different on a mid-range Android.&lt;/p&gt;




&lt;h2&gt;
  
  
  Limitations to Know
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TBT couldn't be captured in the deployed environment.&lt;/strong&gt; The Long Tasks API isn't available in cross-origin deployed contexts — only in locally-served or Chrome DevTools Protocol environments. The INP measurements are real, but the full main thread blocking profile requires a different setup to measure properly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;All numbers are from high-end hardware.&lt;/strong&gt; An Apple M-series Mac is not the median global web user's device. INP values on mid-range Android will be significantly higher — potentially 3–5x. The relative ordering of models should hold, but don't use these absolute numbers as production thresholds for mobile.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;The benchmark is live and open source. Run it on your device, your network conditions, your hardware profile. Export the results as JSON or CSV.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live benchmark&lt;/strong&gt;: &lt;a href="https://benchmark.mspk.me" rel="noopener noreferrer"&gt;benchmark.mspk.me&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source code&lt;/strong&gt;: &lt;a href="https://github.com/srikarphanikumar/cwv-ai-benchmark" rel="noopener noreferrer"&gt;github.com/srikarphanikumar/cwv-ai-benchmark&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full paper&lt;/strong&gt;: arXiv link coming soon&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you run it on a mid-range Android or a low-end device and want to share the numbers, I'd love to see them — that's exactly the follow-on data this research needs.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;DistilBERT is the only model that stays in Google's "Good" INP range on the main thread&lt;/li&gt;
&lt;li&gt;Whisper Tiny is "Poor" despite being the smallest model — architecture beats quantization&lt;/li&gt;
&lt;li&gt;Encoder-decoder models require Web Worker offloading — no exceptions&lt;/li&gt;
&lt;li&gt;Parameter count is a bad proxy for browser inference cost&lt;/li&gt;
&lt;li&gt;Memory pressure on mobile is a separate concern from memory consumption&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The era of client-side AI is here. Now we need to measure what it actually costs.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>webvitals</category>
      <category>corewebvitals</category>
    </item>
  </channel>
</rss>
