<?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>Forem: Yogesh Yadav</title>
    <description>The latest articles on Forem by Yogesh Yadav (@yogeshyadav).</description>
    <link>https://forem.com/yogeshyadav</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F415748%2F48df07d1-ab7c-442c-835a-95295cb8452a.jpg</url>
      <title>Forem: Yogesh Yadav</title>
      <link>https://forem.com/yogeshyadav</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/yogeshyadav"/>
    <language>en</language>
    <item>
      <title>Frontend Performance That Actually Moves the Needle</title>
      <dc:creator>Yogesh Yadav</dc:creator>
      <pubDate>Thu, 16 Apr 2026 20:01:05 +0000</pubDate>
      <link>https://forem.com/codescoop/frontend-performance-that-actually-moves-the-needle-41b7</link>
      <guid>https://forem.com/codescoop/frontend-performance-that-actually-moves-the-needle-41b7</guid>
      <description>&lt;p&gt;In this article we'll cover why Lighthouse scores alone don't tell the full story, what metrics actually matter at scale, how real user monitoring changes the way you think about performance, and the optimizations that have the most impact on platforms serving millions of users.&lt;/p&gt;

&lt;p&gt;Lighthouse is a great tool. I'm not here to tell you to ignore it. But I've seen teams chase a perfect Lighthouse score while their real users were experiencing 4-second load times on mid-range android devices with a 4G connection.&lt;/p&gt;

&lt;p&gt;The score looked great. The experience wasn't.&lt;/p&gt;

&lt;p&gt;When you're building for 10 million users, performance stops being about a number in a report. It becomes about real people on real devices with real network conditions. And the gap between a lab score and what your users actually feel is wider than most developers realize.&lt;/p&gt;

&lt;p&gt;This article is about closing that gap.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lighthouse Scores Are Lab Data, Not Reality
&lt;/h2&gt;

&lt;p&gt;Lighthouse runs in a controlled environment. Throttled CPU, simulated network, a clean browser with no extensions, no cached data, no background tabs. That's not how your users browse.&lt;/p&gt;

&lt;p&gt;Your users are on a 3-year-old phone with 15 browser tabs open, on a train with patchy network, while your JavaScript is fighting with a background app for CPU time.&lt;/p&gt;

&lt;p&gt;This is why a 90+ Lighthouse score can still result in a poor user experience. The lab doesn't lie, but it only tells you part of the truth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lab data:&lt;/strong&gt; what Lighthouse gives you - is useful for catching regressions and tracking trends over time. But it should never be your only signal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Field data:&lt;/strong&gt; what your real users experience - is where performance work actually pays off.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Metrics That Actually Matter at Scale
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Core Web Vitals
&lt;/h3&gt;

&lt;p&gt;Google's Core Web Vitals are the closest thing we have to a standardized set of user-centric performance metrics. Three of them matter most:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LCP - Largest Contentful Paint:&lt;/strong&gt; How long does it take for the largest visible element to render? For most platforms this is a hero image, a video thumbnail or a headline. This is what the user perceives as "the page loaded."&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Target: under 2.5 seconds.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;INP - Interaction to Next Paint:&lt;/strong&gt; How quickly does the page respond after a user interaction? Click a button, tap a menu, submit a form - how long before the page visually responds? This replaced FID (First Input Delay) in 2024 and is a much better measure of real interactivity.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Target: under 200 milliseconds.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CLS - Cumulative Layout Shift&lt;/strong&gt; How much does the page jump around while loading? Ads loading late, images without dimensions, fonts swapping - these all contribute to CLS. On a content-heavy platform this can quietly destroy the user experience.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Target: under 0.1.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;These three metrics directly impact SEO ranking and user retention. At scale, a 0.1 improvement in CLS or a 500ms reduction in LCP translates to measurable engagement and conversion improvements.&lt;/p&gt;

&lt;h3&gt;
  
  
  TTFB - Time to First Byte
&lt;/h3&gt;

&lt;p&gt;Before the browser can render anything, it needs a response from the server. TTFB measures that wait time. High TTFB usually points to server-side issues - slow API responses, no CDN or unoptimized server rendering.&lt;/p&gt;

&lt;p&gt;On platforms with a global audience, CDN configuration alone can cut TTFB from 800ms to under 100ms for a significant portion of your users.&lt;/p&gt;

&lt;h3&gt;
  
  
  TTI - Time to Interactive
&lt;/h3&gt;

&lt;p&gt;When can the user actually use the page? Not just see it, but interact with it without the UI freezing. This is where JavaScript bundle size and execution time have the most direct impact.&lt;/p&gt;

&lt;p&gt;A page that looks loaded but isn't responding to clicks is one of the most frustrating experiences a user can have.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real User Monitoring - The Signal You Can't Ignore
&lt;/h2&gt;

&lt;p&gt;If you're only running Lighthouse, you're flying partially blind. Real User Monitoring (RUM) captures performance data from actual user sessions and sends it back to you.&lt;/p&gt;

&lt;p&gt;The difference is significant. With RUM you can see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How performance varies by device type, browser and geography&lt;/li&gt;
&lt;li&gt;Which pages have the worst real-world LCP or INP&lt;/li&gt;
&lt;li&gt;How performance degrades over time as your codebase grows&lt;/li&gt;
&lt;li&gt;What percentage of your users are experiencing poor performance right now&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tools like Datadog RUM, SpeedCurve, Mux Data or even the free Chrome User Experience Report (CrUX) give you this visibility.&lt;/p&gt;

&lt;p&gt;On a platform serving millions of users, even if 5% of your users are experiencing poor performance, that's 500,000 people having a bad time. RUM makes that visible. Lighthouse doesn't.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Optimizations That Actually Move the Needle
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. JavaScript Bundle Size
&lt;/h3&gt;

&lt;p&gt;This is almost always the biggest lever. JavaScript is the most expensive resource on the web - it has to be downloaded, parsed, and executed before it does anything useful.&lt;/p&gt;

&lt;p&gt;Code splitting is non-negotiable at scale. Every route should load only the JavaScript it needs.&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;// Instead of importing everything upfront&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;HeavyComponent&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;./HeavyComponent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// Load it only when needed&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HeavyComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./HeavyComponent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Audit your bundle regularly. Tools like &lt;code&gt;webpack-bundle-analyzer&lt;/code&gt; or &lt;code&gt;vite-bundle-visualizer&lt;/code&gt; will show you exactly what's in your bundle and where the weight is coming from. You will almost always find something surprising.&lt;/p&gt;

&lt;p&gt;Third-party scripts are usually the worst offenders. Analytics, chat widgets, ad scripts - these are often loaded synchronously and block rendering. Load them async or defer them entirely until after the page is interactive.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Image Optimization
&lt;/h3&gt;

&lt;p&gt;Images are the largest assets on most pages. Getting this wrong has a direct impact on LCP.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use modern formats. WebP is widely supported and significantly smaller than JPEG or PNG. AVIF is even better where supported.&lt;/li&gt;
&lt;li&gt;Always set explicit width and height on images. This prevents layout shift and helps the browser allocate space before the image loads.&lt;/li&gt;
&lt;li&gt;Use lazy loading for images below the fold.&lt;/li&gt;
&lt;li&gt;Serve appropriately sized images. Don't serve a 2000px wide image to a 400px wide mobile screen.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"thumbnail.webp"&lt;/span&gt;
  &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"400"&lt;/span&gt;
  &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"225"&lt;/span&gt;
  &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt;
  &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Video thumbnail"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a platform with a large content library, image optimization alone can reduce page weight by 40–60%.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Critical Rendering Path
&lt;/h3&gt;

&lt;p&gt;The browser has to download your HTML, parse it, discover CSS and JavaScript, download those, parse them and then render the page. Every step in that chain is an opportunity to either speed things up or slow things down.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Inline critical CSS - the styles needed to render above-the-fold content - directly in the HTML. This eliminates a render-blocking network request for the initial view.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Preload key resources the browser won't discover until late in the parsing process.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"preload"&lt;/span&gt; &lt;span class="na"&gt;as=&lt;/span&gt;&lt;span class="s"&gt;"font"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/fonts/main.woff2"&lt;/span&gt; &lt;span class="na"&gt;crossorigin&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"preload"&lt;/span&gt; &lt;span class="na"&gt;as=&lt;/span&gt;&lt;span class="s"&gt;"image"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/hero.webp"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Defer non-critical JavaScript. If a script doesn't need to run before the page is interactive, it shouldn't block rendering.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Caching Strategy
&lt;/h3&gt;

&lt;p&gt;I covered this in depth in the previous article in this series, but it's worth mentioning here because caching is one of the highest-impact performance optimizations available to you.&lt;/p&gt;

&lt;p&gt;Repeat visitors on a well-cached platform can load pages almost entirely from cache. No network requests for static assets, no server round trips for unchanged resources. The performance improvement for returning users is dramatic.&lt;/p&gt;

&lt;p&gt;If you haven't read the &lt;a href="https://dev.to/codescoop/frontend-caching-done-right-2lem"&gt;caching article&lt;/a&gt; yet, it's worth going back to.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Reducing Main Thread Work
&lt;/h3&gt;

&lt;p&gt;INP and TTI both suffer when the main thread is busy. JavaScript execution, long tasks, layout recalculations - these all compete for the same thread that handles user interactions.&lt;/p&gt;

&lt;p&gt;A few things that help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Break up long tasks. Any task that takes more than 50ms can cause noticeable jank. Use setTimeout or scheduler.postTask to yield control back to the browser between chunks of work.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Avoid layout thrashing. Reading and writing to the DOM in alternating calls forces the browser to recalculate layout repeatedly. Batch your reads and writes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Move heavy computation off the main thread with Web Workers.&lt;br&gt;
&lt;/p&gt;&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;// Yielding to the browser between heavy tasks&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;processLargeDataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&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="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&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="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&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;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;100&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="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&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;
  
  
  Performance Budgets - Making It Stick
&lt;/h2&gt;

&lt;p&gt;One of the hardest parts of performance work at scale is keeping improvements from regressing over time. A performance budget solves this.&lt;/p&gt;

&lt;p&gt;A performance budget sets explicit limits on metrics like bundle size, LCP or TTI. If a pull request would push you over the budget, it fails the build.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resourceSizes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"resourceType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"script"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"budget"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;300&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="nl"&gt;"resourceType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"budget"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&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="nl"&gt;"timings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"metric"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"first-contentful-paint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"budget"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1500&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="nl"&gt;"metric"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"interactive"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"budget"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3500&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 performance on everyone's radar, not just the engineer who cared enough to optimize it once.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Scale Actually Teaches You About Performance
&lt;/h2&gt;

&lt;p&gt;Here's what I've learned from working on platforms at this size that you don't find in most performance guides:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Device distribution matters more than you think.&lt;/strong&gt; Your development machine is not representative of your users. Profile on a mid-range Android device and you will find issues you never knew existed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Geography matters.&lt;/strong&gt; A platform with a global audience needs a CDN strategy, not just a fast server in one region. Network latency from a distant origin server can add seconds to TTFB for users in certain regions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance degrades gradually.&lt;/strong&gt; Nobody ships a slow app intentionally. It gets slow one dependency, one feature, one third-party script at a time. Without a budget and regular monitoring, you won't notice until users are already complaining.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 80/20 rule applies.&lt;/strong&gt; A small number of pages usually account for the majority of your traffic. Find those pages, measure them obsessively and optimize them first. That's where your performance work will have the most impact.&lt;/p&gt;




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

&lt;p&gt;Lighthouse is a tool, not a goal. A green score means you've done the basics right. It doesn't mean your users are having a fast experience.&lt;/p&gt;

&lt;p&gt;The teams that get performance right at scale are the ones who measure what their real users experience, set budgets to prevent regression and focus their effort on the optimizations that actually move the needle for their specific platform and audience.&lt;/p&gt;

&lt;p&gt;Start with RUM. Find where your real users are struggling. Fix those things first.&lt;/p&gt;

&lt;p&gt;The Lighthouse score will follow.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have thoughts or questions on frontend performance? Drop them in the comments, always happy to discuss.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article is part of the &lt;strong&gt;Frontend at Scale&lt;/strong&gt; series.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>performance</category>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Frontend Caching Done Right</title>
      <dc:creator>Yogesh Yadav</dc:creator>
      <pubDate>Sun, 05 Apr 2026 06:39:19 +0000</pubDate>
      <link>https://forem.com/codescoop/frontend-caching-done-right-2lem</link>
      <guid>https://forem.com/codescoop/frontend-caching-done-right-2lem</guid>
      <description>&lt;p&gt;In this article we’ll cover how the browser cache and HTTP headers work, when and how to use stale-while-revalidate, how service workers give you programmatic control over caching, what you should never cache, and how cache invalidation works in practice. All of it from the perspective of building for platforms that can’t afford to get this wrong.&lt;/p&gt;

&lt;p&gt;Caching is one of those topics that every frontend developer thinks they understand, until they’re staring at a production issue where users are getting stale data, or worse, the server is getting hammered because nothing is being cached at all.&lt;/p&gt;

&lt;p&gt;I’ve worked on platforms handling more than 10 million active users. And I can tell you, caching stops being a “nice to have” the moment your scale starts growing. It becomes the difference between a platform that feels fast and one that quietly falls apart under load.&lt;/p&gt;

&lt;p&gt;This article is everything I’ve learned about frontend caching, written the way I wish someone had explained it to me early in my career.&lt;/p&gt;




&lt;h2&gt;
  
  
  First, Understand What You Are Actually Caching
&lt;/h2&gt;

&lt;p&gt;Before you touch a single HTTP header or write a line of service worker code, ask yourself one question.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the cost of serving stale data here?
&lt;/h3&gt;

&lt;p&gt;That question determines everything. Because caching is always a tradeoff between freshness and performance. The mistake most developers make is treating all resources the same way. They’re not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here’s how I think about it:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Static assets&lt;/strong&gt; (JS bundles, CSS, fonts, images) - these can be cached aggressively, sometimes forever, if you version them correctly.&lt;br&gt;
&lt;strong&gt;API responses&lt;/strong&gt; - depends entirely on how often the data changes and how much it matters if the user sees something slightly outdated.&lt;br&gt;
&lt;strong&gt;HTML documents&lt;/strong&gt; - usually should not be cached aggressively, especially for authenticated apps.&lt;br&gt;
&lt;strong&gt;User-specific data&lt;/strong&gt; - almost never cache this without thinking carefully.&lt;/p&gt;

&lt;p&gt;Get this mental model right first. Everything else follows from it.&lt;/p&gt;


&lt;h2&gt;
  
  
  Browser Cache and HTTP Headers
&lt;/h2&gt;

&lt;p&gt;The browser cache is your first and most powerful caching layer. It lives between the user and your server, and it’s controlled entirely through HTTP response headers.&lt;/p&gt;
&lt;h3&gt;
  
  
  Cache-Control
&lt;/h3&gt;

&lt;p&gt;This is the header you’ll use the most. Here’s what the key directives actually mean:&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;Cache-Control: max-age=31536000, immutablety
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;max-age&lt;/code&gt; tells the browser how many seconds to keep this resource before considering it stale. &lt;code&gt;immutable&lt;/code&gt; tells the browser not to bother revalidating it even on a hard refresh, because the content will never change.&lt;/p&gt;

&lt;p&gt;Use this combination for versioned static assets, your JS bundles, CSS files, and images that have a content hash in the filename. Something like &lt;code&gt;main.a3f9c2.js&lt;/code&gt;. The hash changes every build, so the URL changes, so you never serve stale code. Cache it forever.&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;Cache-Control: no-cache
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Despite the name, this does not mean “don’t cache.” It means “cache it, but check with the server every time before using it.” The server can respond with a &lt;code&gt;304 Not Modified&lt;/code&gt; and the browser uses the cached version. No full download needed.&lt;/p&gt;

&lt;p&gt;Use this for your HTML documents. You want the browser to always check if there’s a new version, but still benefit from caching when nothing has changed.&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;Cache-Control: no-store
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This actually means “don’t cache.” Nothing is stored anywhere. Use this for sensitive data, authentication responses, anything you never want sitting in a cache.&lt;/p&gt;

&lt;h3&gt;
  
  
  ETag and Last-Modified
&lt;/h3&gt;

&lt;p&gt;These work alongside &lt;code&gt;Cache-Control&lt;/code&gt; for revalidation. When the browser asks "has this changed?", the server uses these to answer.&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;ETag: "abc123"
Last-Modified: Mon, 01 Jan 2024 00:00:00 GMT
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser sends back &lt;code&gt;If-None-Match: "abc123"&lt;/code&gt; or &lt;code&gt;If-Modified-Since&lt;/code&gt; on the next request. If nothing changed, the server returns 304 and saves the bandwidth of sending the full response again.&lt;/p&gt;

&lt;p&gt;At scale, these small savings add up to a significant reduction in server load.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stale-While-Revalidate
&lt;/h2&gt;

&lt;p&gt;This is one of the most underused caching strategies I’ve seen in frontend codebases, and it’s genuinely powerful.&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;Cache-Control: max-age=60, stale-while-revalidate=300
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s what this does. For the first 60 seconds, serve from cache, no questions asked. Between 60 and 300 seconds, serve the stale cached version immediately, but kick off a background request to revalidate it. After 300 seconds, it’s stale and must be revalidated before serving.&lt;/p&gt;

&lt;p&gt;The user gets an instant response. The cache gets updated in the background. No loading spinner, no waiting.&lt;/p&gt;

&lt;p&gt;This pattern is perfect for data that changes occasionally but doesn’t need to be real-time. Think navigation menus, configuration data, content that updates a few times a day. The user always gets a fast experience and the data stays reasonably fresh.&lt;/p&gt;

&lt;p&gt;You’ll also recognize this pattern from TanStack Query’s &lt;code&gt;staleTime&lt;/code&gt; and &lt;code&gt;gcTime&lt;/code&gt; configuration. The underlying idea is exactly the same, just applied at the JavaScript layer instead of the HTTP layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Service Workers - The Programmable Cache
&lt;/h2&gt;

&lt;p&gt;HTTP headers give you declarative control over caching. Service workers give you programmatic control. That’s a significant difference.&lt;/p&gt;

&lt;p&gt;A service worker sits between your app and the network, intercepting every request. You decide what happens with each one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;caches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;cachedResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cachedResponse&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;cachedResponse&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a simple cache-first strategy. Check the cache first, fall back to the network if nothing is found. For a platform serving millions of users, this means repeat visitors often never hit your server for static assets at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Caching Strategies with Service Workers
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cache First&lt;/strong&gt; - Serve from cache, fall back to network. Best for static assets that rarely change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Network First&lt;/strong&gt; - Try the network, fall back to cache if offline. Best for API data where freshness matters but offline support is needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stale While Revalidate&lt;/strong&gt; - Serve from cache immediately, update cache in background. Best for non-critical content where speed matters more than freshness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache Only&lt;/strong&gt; - Only serve from cache. Useful for assets you’ve pre-cached during install.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Network Only&lt;/strong&gt; - Always go to network. For requests that should never be cached, like analytics or payment endpoints.&lt;/p&gt;

&lt;p&gt;Pick your strategy per resource type, not globally. A single strategy for everything is almost always the wrong call.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pre-caching vs Runtime Caching
&lt;/h3&gt;

&lt;p&gt;Pre-caching happens when the service worker installs. You explicitly list assets to cache upfront.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;install&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;caches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addAll&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&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;/styles/main.css&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;/scripts/main.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Runtime caching happens dynamically as requests come in. You cache responses as they’re fetched, so frequently accessed resources end up in cache naturally over time.&lt;/p&gt;

&lt;p&gt;For most platforms, you want both. Pre-cache your critical shell, runtime cache everything else.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Not to Cache
&lt;/h2&gt;

&lt;p&gt;This is the part that trips people up. Caching the wrong things causes bugs that are genuinely hard to debug in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never aggressively cache:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication tokens or session data&lt;/li&gt;
&lt;li&gt;Payment and transaction endpoints&lt;/li&gt;
&lt;li&gt;User-specific personalization data&lt;/li&gt;
&lt;li&gt;Anything that changes per user or per session&lt;/li&gt;
&lt;li&gt;A/B test configurations if they need to be real-time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ve seen teams cache API responses that included user-specific entitlements. The result was users seeing content they shouldn’t have access to, or not seeing content they’d just purchased. At scale, that’s not just a bug. It’s a trust problem.&lt;/p&gt;

&lt;p&gt;When in doubt, don’t cache it, or use &lt;code&gt;no-cache&lt;/code&gt; so at least revalidation happens.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cache Invalidation - The Hard Part
&lt;/h2&gt;

&lt;p&gt;There’s a famous saying in computer science: there are only two hard things, naming things and cache invalidation.&lt;/p&gt;

&lt;p&gt;It’s funny because it’s true.&lt;/p&gt;

&lt;p&gt;For static assets, content-hashed filenames solve this completely. New deploy, new hash, new URL, new cache entry. Old one expires naturally.&lt;/p&gt;

&lt;p&gt;For API responses and service worker caches, you need a versioning strategy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CACHE_VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;activate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;caches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;cacheNames&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;cacheNames&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;CACHE_VERSION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;caches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&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="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every time you deploy, bump the cache version. The activate event cleans up old caches. Users get fresh data on their next visit without you having to manually purge anything.&lt;/p&gt;




&lt;h2&gt;
  
  
  Caching at Scale - What Actually Changes
&lt;/h2&gt;

&lt;p&gt;When you’re building for 10 million users, the fundamentals don’t change. But the consequences of getting it wrong are amplified significantly.&lt;/p&gt;

&lt;p&gt;A few things I’ve learned from operating at that scale:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measure before you optimize.&lt;/strong&gt; Use Chrome DevTools, Lighthouse, and your RUM (Real User Monitoring) data to understand where your actual cache hit rates are. Don’t guess.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CDN caching and browser caching are different layers.&lt;/strong&gt; Your CDN has its own cache headers, often separate from what the browser sees. Understand both. Misconfiguring your CDN can mean millions of users bypassing the browser cache entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache stampedes are real.&lt;/strong&gt; When a popular cached resource expires simultaneously for millions of users, they all hit your server at once. Stale-while-revalidate and jittered expiry times help prevent this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monitor your cache hit ratio.&lt;/strong&gt; If it’s low, you’re leaving performance on the table. If it’s too high and you’re seeing stale data complaints, your TTLs are too aggressive.&lt;/p&gt;




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

&lt;p&gt;Caching is not a set-it-and-forget-it feature. It’s an ongoing engineering decision that touches performance, correctness and user experience all at once.&lt;/p&gt;

&lt;p&gt;Start with HTTP headers and get those right. Layer in stale-while-revalidate for the right resources. Add service workers when you need offline support or more granular control. And always think about invalidation before you think about caching.&lt;/p&gt;

&lt;p&gt;The developers who get this right aren’t the ones who know the most cache directives. They’re the ones who ask the right question first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the cost of serving stale data here?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Answer that honestly for every resource, and the rest follows.&lt;/p&gt;




&lt;p&gt;Have thoughts or questions on frontend caching? Drop them in the comments, always happy to discuss.&lt;/p&gt;

&lt;p&gt;This article is part of the &lt;strong&gt;Frontend at Scale&lt;/strong&gt; series.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>caching</category>
      <category>javascript</category>
      <category>performance</category>
    </item>
    <item>
      <title>Writing SDKs That Don’t Make Other Developers Hate You</title>
      <dc:creator>Yogesh Yadav</dc:creator>
      <pubDate>Sun, 22 Mar 2026 18:30:00 +0000</pubDate>
      <link>https://forem.com/yogeshyadav/writing-sdks-that-dont-make-other-developers-hate-you-3dea</link>
      <guid>https://forem.com/yogeshyadav/writing-sdks-that-dont-make-other-developers-hate-you-3dea</guid>
      <description>&lt;p&gt;Lessons from building and maintaining an &lt;code&gt;NPM package&lt;/code&gt; that powers tier-0 OTT production apps across millions of users.&lt;/p&gt;

&lt;p&gt;When I started building a frontend SDK, one that would eventually be consumed across a fleet of tier-0 OTT production applications, collectively handling more than 10 million active users, I quickly realized something uncomfortable.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The hardest part wasn’t the code. It was every decision around the code.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;How you name a method. What you return when something goes wrong. Whether you force consumers to configure something they shouldn’t have to think about. These are the things that either make a developer’s day easier, or quietly ruin it at 2am during a production incident.&lt;/p&gt;

&lt;p&gt;This article is everything I wish I’d known before shipping that first version.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Your API Surface Is a Promise
&lt;/h2&gt;

&lt;p&gt;The moment another team installs your package and writes &lt;code&gt;import { authenticate } from '@your-org/sdk'&lt;/code&gt;, you've made a promise. That method name, its parameters, its return shape are now load-bearing walls in someone else's application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep the surface small&lt;/strong&gt;. Export only what needs to be public. Every extra export is a contract you now have to honour forever, or break with a major version bump.&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;// ❌ Exporting internal utilities "just in case"&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tokenParser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refreshQueue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buildAuthHeader&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Only expose what consumers actually need&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getUser&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If an internal utility leaks into the public API and someone starts depending on it, you can’t quietly remove it. You’ve created a hidden contract.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Name things from the consumer’s perspective, not yours.&lt;/strong&gt; You know how the internals work. They don’t, and they shouldn’t have to.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Sensible Defaults Are an Act of Respect
&lt;/h2&gt;

&lt;p&gt;Every required config option you add is a question your SDK is asking the consumer to answer. Sometimes that’s necessary. Most of the time, it isn’t.&lt;/p&gt;

&lt;p&gt;Ask yourself for every config field: &lt;em&gt;does the developer actually know what to set here, and does it vary meaningfully across use cases?&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Forcing the consumer to configure things they shouldn't care about&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sdk&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;AuthSDK&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;tokenStorageStrategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;memory&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;refreshBuffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;retryAttempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;cookieSameSite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lax&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Sane defaults. Override only when needed.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sdk&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;AuthSDK&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-client-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The defaults should represent the most secure, most correct behaviour out of the box. The consumer shouldn’t have to research best practices just to get started safely.&lt;/p&gt;

&lt;p&gt;In our Authentication SDK, access tokens are stored in memory by default, not localStorage, not sessionStorage. That’s the right call for XSS resistance. A developer shouldn’t have to know about XSS attack surfaces to benefit from that decision. They just get it.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Error Messages Are Documentation
&lt;/h2&gt;

&lt;p&gt;When something goes wrong in your SDK, you have a few seconds of the developer’s attention. Use 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="c1"&gt;// ❌ An error that tells you nothing useful&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="s1"&gt;Auth failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ An error that tells you what happened, why, and what to check&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="s1"&gt;[AuthSDK] Token refresh failed: the refresh token has expired or is invalid. &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;This usually happens after a long inactive session. Call sdk.login() to re-authenticate.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things that make error messages genuinely useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prefix with your SDK name:&lt;/strong&gt; when this surfaces in a Sentry report, developers know immediately where to look.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explain the why, not just the what:&lt;/strong&gt; “invalid token” is useless; “token expired after 24 hours of inactivity” is actionable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suggest a fix:&lt;/strong&gt; if there’s a clear resolution path, just say it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For errors that consumers are expected to handle, create typed error classes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TokenExpiredError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[AuthSDK] Access token has expired. Call sdk.refreshToken() or sdk.login().&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TokenExpiredError&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NetworkError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;statusCode&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;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[AuthSDK] Network request failed with status &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NetworkError&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the consumer can do this cleanly:&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;TokenExpiredError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&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;
  
  
  4. TypeScript Types Are the First Layer of Documentation
&lt;/h2&gt;

&lt;p&gt;Your types aren’t just for type safety. They are the first thing a developer reads about your SDK, before they open a single doc page.&lt;/p&gt;

&lt;p&gt;If your types are vague, your SDK feels unreliable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Vague types that tell the consumer nothing&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Types that tell the full story at a glance&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AuthConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="cm"&gt;/** Defaults to 'memory'. Use 'cookie' only for SSR environments. */&lt;/span&gt;
  &lt;span class="nx"&gt;tokenStorage&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;memory&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;cookie&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="cm"&gt;/** Called automatically when access token expires */&lt;/span&gt;
  &lt;span class="nx"&gt;onTokenRefresh&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AuthResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;
  &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="c1"&gt;// Unix timestamp&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AuthConfig&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AuthResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the JSDoc comments inside the interface. That’s the next point.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Documentation-as-Code: Write It Where Developers Actually Read It
&lt;/h2&gt;

&lt;p&gt;Developers don’t always open the docs site. They hover over a function in VS Code and read the tooltip. That’s your real documentation surface.&lt;/p&gt;

&lt;p&gt;Write JSDoc comments directly in your type definitions and function signatures:&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="cm"&gt;/**
 * Authenticates the user and stores the session.
 *
 * @param config - Authentication configuration
 * @returns Resolved with the authenticated user and token details
 *
 * @throws {TokenExpiredError} If the existing session has expired
 * @throws {NetworkError} If the auth endpoint is unreachable
 *
 * @example
 * const { user } = await sdk.authenticate({ clientId: 'abc123' })
 * console.log(user.email)
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AuthConfig&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AuthResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;@example&lt;/code&gt; block is particularly valuable. When a developer is evaluating whether your SDK does what they need, a concrete working example in the tooltip closes that question in 5 seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Versioning Is a Communication Tool
&lt;/h2&gt;

&lt;p&gt;A version number carries meaning. When you bump it, you’re telling your consumers something.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Patch (1.0.x):&lt;/strong&gt; bug fix, nothing changes for them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minor (1.x.0):&lt;/strong&gt; new capability, fully backwards compatible&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Major (x.0.0)&lt;/strong&gt;: something they depend on changed, they need to act&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When we had a breaking change in our authentication flow, we did three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Bumped the major version&lt;/li&gt;
&lt;li&gt;Added a clear migration guide in the CHANGELOG&lt;/li&gt;
&lt;li&gt;Kept the old method working for one minor version, with a console warning pointing to the new one
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** @deprecated Use sdk.login() instead. Will be removed in v4.0. */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;authenticate&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;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;[AuthSDK] authenticate() is deprecated. Use login() instead.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;login&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;Deprecation warnings are a courtesy your consumers will remember.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. What Changes in Your SDK Can Break Production - Know That Weight
&lt;/h2&gt;

&lt;p&gt;When your package is used across tier-0 applications serving millions of users, a broken release isn’t just embarrassing. It’s a real incident. Here’s what that forces you to discipline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never release without testing in a consumer app.&lt;/strong&gt; Snapshot tests in your own repo aren’t enough. Use a real integration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lock peer dependencies carefully.&lt;/strong&gt; If your SDK assumes React 17 but the consumer has 18, that mismatch will surface at the worst possible time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Changelogs are not optional.&lt;/strong&gt; Every release should have one, even patch bumps. Consumers need to know what changed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don’t put secrets or environment-specific logic in the SDK core.&lt;/strong&gt; Config should come from the consumer. Your SDK should be environment-agnostic.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;The best SDK is one that feels obvious. Where a developer can sit down with it for the first time and get something working without reading a single doc page, because the API told them what to do, the defaults were sensible, and the error message told them what they got wrong.&lt;/p&gt;

&lt;p&gt;That’s a hard bar to hit. But every time you name a method from the consumer’s perspective, write a JSDoc example, or turn a cryptic error into a useful one, you get closer.&lt;/p&gt;

&lt;p&gt;The developers consuming your SDK are trusting you with their production apps. That trust is worth protecting.&lt;/p&gt;




&lt;p&gt;Have thoughts or questions on SDK design? Drop them in the comments, always happy to discuss.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>npm</category>
      <category>sdk</category>
    </item>
    <item>
      <title>Explore the lexical Environment &amp; Environment Record in Javascript 2021</title>
      <dc:creator>Yogesh Yadav</dc:creator>
      <pubDate>Wed, 17 Aug 2022 07:23:00 +0000</pubDate>
      <link>https://forem.com/codescoop/what-has-changed-in-lexical-environment-as-per-ecmascript-2021-2bjb</link>
      <guid>https://forem.com/codescoop/what-has-changed-in-lexical-environment-as-per-ecmascript-2021-2bjb</guid>
      <description>&lt;p&gt;Let's first understand the &lt;code&gt;Lexical Environment&lt;/code&gt; &amp;amp; &lt;code&gt;Environment Record&lt;/code&gt; as per different versions of ECMAScript Specification.&lt;/p&gt;

&lt;p&gt;From &lt;a href="https://262.ecma-international.org/6.0/" rel="noopener noreferrer"&gt;&lt;strong&gt;ES2015&lt;/strong&gt;&lt;/a&gt; till &lt;a href="https://262.ecma-international.org/11.0/" rel="noopener noreferrer"&gt;&lt;strong&gt;ES2020&lt;/strong&gt;&lt;/a&gt; Specification:-&lt;/p&gt;

&lt;h2&gt;Lexical Environment: &lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A lexical environment is a &lt;strong&gt;specification type&lt;/strong&gt; used to define the association of Identifiers to specific variables and functions, based upon the lexical nesting structure of your code.&lt;/li&gt;
&lt;li&gt;A lexical environment consists of two components:

&lt;ol&gt;
&lt;li&gt;
&lt;h4&gt;Environment Record&lt;/h4&gt; It records the &lt;strong&gt;identifier bindings&lt;/strong&gt; that are created within the scope of its associated Lexical Environment. It is referred to as the Lexical Environment's EnvironmentRecord.&lt;/li&gt;
&lt;li&gt;
&lt;h4&gt;Outer Reference&lt;/h4&gt; A reference to outer environment (null in the global environment).&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A conceptual view using pseudo-code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;executioncontext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;environmentRecord&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="c1"&gt;// storage&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;identifier&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="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;identifier&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="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// reference to the parent environment&lt;/span&gt;
    &lt;span class="nl"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; - The &lt;code&gt;[[Environment]]&lt;/code&gt; created inside Execution Context is of &lt;code&gt;type&lt;/code&gt; &lt;strong&gt;Lexical Environment&lt;/strong&gt; &lt;br&gt;
 &lt;a href="https://262.ecma-international.org/11.0/#sec-ecmascript-function-objects" rel="noopener noreferrer"&gt;[refer ES2020]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foeq4770whr2mksmrwt0s.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%2Foeq4770whr2mksmrwt0s.png" alt="Lexical Environment in Global Scope" width="800" height="418"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://262.ecma-international.org/12.0/" rel="noopener noreferrer"&gt;&lt;strong&gt;12th Edition ECMAScript2021&lt;/strong&gt;&lt;/a&gt; Specification:&lt;/p&gt;

&lt;h2&gt;Environment Record &lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A Environment Record is a &lt;strong&gt;specification type&lt;/strong&gt; used to define the association of Identifiers to specific variables and functions, based upon the lexical nesting structure of your code.&lt;/li&gt;
&lt;li&gt;Every Environment Record has one component:

&lt;ol&gt;
&lt;li&gt;
&lt;h4&gt;Outer Reference&lt;/h4&gt; An &lt;code&gt;[[OuterEnv]]&lt;/code&gt; field, which is either null or a reference to an outer Environment Record.
A conceptual view using pseudo-code:
&lt;/li&gt;
&lt;/ol&gt;
&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="nx"&gt;executioncontext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// storage&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;identifier&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="nx"&gt;value&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="nx"&gt;identifier&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="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// reference to the parent environment&lt;/span&gt;
    &lt;span class="na"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; - The &lt;code&gt;[[Environment]]&lt;/code&gt; created inside Execution Context is of &lt;code&gt;type&lt;/code&gt; &lt;strong&gt;Environment Record&lt;/strong&gt; &lt;a href="https://262.ecma-international.org/12.0/#sec-ecmascript-function-objects" rel="noopener noreferrer"&gt;[refer ES2021]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F06g8v67qzlgcds5i22fk.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%2F06g8v67qzlgcds5i22fk.png" alt="Environment Record in Global Scope" width="800" height="399"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Let's also understand the &lt;code&gt;Structure of execution context&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt; Execution Context: &lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;An execution context is a specification device that is used to track the runtime evaluation of the code.&lt;/li&gt;
&lt;li&gt;To keeps the track of execution progress of its associated code, it needs various &lt;strong&gt;state components&lt;/strong&gt; like &lt;code&gt;LexicalEnvironment&lt;/code&gt;, &lt;code&gt;VariableEnvironment&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In pseudo-code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;ExecutionContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;VariableEnvironment&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="na"&gt;LexicalEnvironment&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="c1"&gt;// other components&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;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Till ES2020&lt;/th&gt;
&lt;th&gt;From ES2021&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;- The &lt;code&gt;LexicalEnvironment component&lt;/code&gt; and &lt;code&gt;VariableEnvironment component&lt;/code&gt; of an execution context are always &lt;strong&gt;Lexical Environments&lt;/strong&gt; &lt;a href="https://262.ecma-international.org/11.0/#table-23" rel="noopener noreferrer"&gt;[refer ES2020]&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;- The &lt;code&gt;LexicalEnvironment component&lt;/code&gt; and &lt;code&gt;VariableEnvironment&lt;/code&gt; components of an execution context are always &lt;strong&gt;Environment Records&lt;/strong&gt; &lt;a href="https://262.ecma-international.org/12.0/#table-23" rel="noopener noreferrer"&gt;[refer ES2021]&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt; Summary &lt;/h2&gt;

&lt;p&gt;Let's have a quick recap of all the steps we perform in the above code snippet.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In ECMAScript2021, the &lt;code&gt;[[environment]]&lt;/code&gt; which is created inside the execution context is of &lt;code&gt;type&lt;/code&gt; &lt;strong&gt;Environment Record&lt;/strong&gt; instead of Lexical Environment.&lt;/li&gt;
&lt;li&gt;So, The &lt;code&gt;LexicalEnvironment component&lt;/code&gt; and &lt;code&gt;VariableEnvironment components&lt;/code&gt; of an execution context are always &lt;strong&gt;Environment Records&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Wrap Up!!&lt;/h2&gt;

&lt;p&gt;Thank you for your time!! Let's connect to learn and grow together.&lt;br&gt;
&lt;a href="https://github.com/deltanode" rel="noopener noreferrer"&gt;Github&lt;/a&gt; &lt;a href="https://twitter.com/yogesh_yadv" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>computerscience</category>
    </item>
  </channel>
</rss>
