<?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: Roman</title>
    <description>The latest articles on Forem by Roman (@rbobr).</description>
    <link>https://forem.com/rbobr</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%2F1441742%2Fc482bfd6-a12c-48df-8356-ac03aaac010c.png</url>
      <title>Forem: Roman</title>
      <link>https://forem.com/rbobr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/rbobr"/>
    <language>en</language>
    <item>
      <title>Measuring the Real Impact of a Performance Refactor (Not Just Lighthouse)</title>
      <dc:creator>Roman</dc:creator>
      <pubDate>Tue, 28 Oct 2025 10:05:22 +0000</pubDate>
      <link>https://forem.com/rbobr/measuring-the-real-impact-of-a-performance-refactor-not-just-lighthouse-3lg0</link>
      <guid>https://forem.com/rbobr/measuring-the-real-impact-of-a-performance-refactor-not-just-lighthouse-3lg0</guid>
      <description>&lt;p&gt;Every team I’ve worked with has had this moment:&lt;br&gt;&lt;br&gt;
Someone ships a “massive performance refactor.”&lt;/p&gt;

&lt;p&gt;They post a screenshot of Lighthouse:&lt;br&gt;&lt;br&gt;
🌈 &lt;em&gt;“We went from 67 → 95!”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Everyone celebrates… and nothing changes.&lt;br&gt;&lt;br&gt;
Conversion doesn’t move. Bounce rates don’t drop. Users don’t even notice.&lt;/p&gt;

&lt;p&gt;That’s when you realize: &lt;strong&gt;Lighthouse isn’t the finish line. It’s a lab test.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Real performance is what happens when real users use your app — on bad devices, slow networks, and bloated pages full of third-party scripts.&lt;/p&gt;

&lt;p&gt;Let’s talk about what &lt;em&gt;lab tests&lt;/em&gt; really are, how they differ from &lt;em&gt;real-world testing&lt;/em&gt;, and how to actually measure performance impact that matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lighthouse Isn’t the Finish Line — It’s a Lab Test
&lt;/h2&gt;

&lt;p&gt;Lighthouse, WebPageTest, or PageSpeed Insights are what we call &lt;strong&gt;lab tests&lt;/strong&gt; —&lt;br&gt;&lt;br&gt;
synthetic, controlled, reproducible environments that tell you how your app &lt;em&gt;should&lt;/em&gt; perform under ideal conditions.&lt;/p&gt;

&lt;p&gt;They’re run in Google’s Chrome datacenters or your local environment, usually with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single device type (often high-end desktop or emulated mobile)&lt;/li&gt;
&lt;li&gt;A fixed network profile (Fast 3G or 4G)&lt;/li&gt;
&lt;li&gt;A cold cache (first load)&lt;/li&gt;
&lt;li&gt;No background scripts, extensions, or user data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That makes them &lt;strong&gt;consistent&lt;/strong&gt; — great for detecting regressions.&lt;br&gt;&lt;br&gt;
But also &lt;strong&gt;isolated&lt;/strong&gt; — bad for measuring reality.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Four Major Types of Performance Tests
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Tools&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;🧪 &lt;strong&gt;Lab Tests&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Controlled, reproducible environment. Usually one device/network setup.&lt;/td&gt;
&lt;td&gt;Detecting regressions, comparing builds, CI checks.&lt;/td&gt;
&lt;td&gt;Lighthouse, WebPageTest, Calibre CI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🌍 &lt;strong&gt;Field Tests (RUM)&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Data collected from real users via browser APIs.&lt;/td&gt;
&lt;td&gt;Measure real-world experience (LCP, INP, CLS).&lt;/td&gt;
&lt;td&gt;Perfume.js, Vercel Analytics, SpeedCurve, Datadog RUM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⚙️ &lt;strong&gt;Synthetic Monitoring&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Automated tests that simulate users at intervals from multiple regions.&lt;/td&gt;
&lt;td&gt;Detecting live performance drops or regressions in production.&lt;/td&gt;
&lt;td&gt;Checkly, Pingdom, Uptrends, Datadog Synthetics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🧭 &lt;strong&gt;A/B Performance Experiments&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Compare old vs optimized builds or feature flags under real traffic.&lt;/td&gt;
&lt;td&gt;Measuring the &lt;em&gt;business impact&lt;/em&gt; of optimizations.&lt;/td&gt;
&lt;td&gt;LaunchDarkly, Optimizely, custom rollouts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  Why You Need Both Lab and Real Data
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lab tests&lt;/strong&gt; are like a medical check-up — they show how healthy your app looks in a clean environment.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RUM and synthetic data&lt;/strong&gt; are the real patient data — they show how your app behaves in the wild.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You need both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lab&lt;/strong&gt; to catch regressions early.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RUM/Synthetic&lt;/strong&gt; to confirm real impact.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without RUM, you’re optimizing for the lab, not the user.&lt;br&gt;&lt;br&gt;
Without lab tests, you can’t prevent accidental slowdowns.&lt;/p&gt;




&lt;h3&gt;
  
  
  Example: When the Lab Lies
&lt;/h3&gt;

&lt;p&gt;Imagine your team ships a huge performance refactor — lazy loading, image compression, and bundle splitting.&lt;br&gt;&lt;br&gt;
Lighthouse score jumps from 70 → 95.&lt;br&gt;&lt;br&gt;
Everyone cheers.&lt;/p&gt;

&lt;p&gt;Then you check RUM data a week later:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Median LCP barely moved.&lt;/li&gt;
&lt;li&gt;INP got worse on low-end Android devices.&lt;/li&gt;
&lt;li&gt;Conversion rate didn’t change.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Because &lt;strong&gt;Lighthouse runs on a clean, high-end environment&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Your real users run on throttled CPUs, 3G networks, or under thermal throttling on mobile.&lt;br&gt;&lt;br&gt;
Those factors dominate actual experience far more than your code improvements.&lt;/p&gt;

&lt;p&gt;In other words — the lab said your app was healthy, but the &lt;em&gt;patients&lt;/em&gt; are using different hardware.&lt;/p&gt;

&lt;p&gt;That’s the gap between &lt;strong&gt;synthetic success&lt;/strong&gt; and &lt;strong&gt;real-world performance&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Lab tools show potential; real data shows impact.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1. Stop Treating Synthetic Metrics as Success
&lt;/h2&gt;

&lt;p&gt;Lab tests are useful — but only as &lt;strong&gt;early indicators&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
They show regressions, not user experience.&lt;/p&gt;

&lt;p&gt;If you ship a refactor that scores 95 but feels identical, you didn’t make the app faster — you just optimized for a benchmark.&lt;/p&gt;

&lt;p&gt;The real goal isn’t to improve a score.&lt;br&gt;&lt;br&gt;
It’s to make your app &lt;em&gt;feel faster for real people&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2. Define What “Fast” Actually Means for Your Product
&lt;/h2&gt;

&lt;p&gt;Performance is not absolute — it’s contextual.&lt;/p&gt;

&lt;p&gt;Ask yourself:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“What does &lt;em&gt;fast&lt;/em&gt; mean for our users?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For an e-commerce site:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Time from landing → product visible (LCP)&lt;/li&gt;
&lt;li&gt;Time to first interaction (INP)&lt;/li&gt;
&lt;li&gt;Time to checkout confirmation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a SaaS dashboard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chart render time after data load&lt;/li&gt;
&lt;li&gt;Response time between user actions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a content site:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Perceived load time&lt;/li&gt;
&lt;li&gt;Scroll latency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the moments users actually &lt;em&gt;feel&lt;/em&gt;.&lt;br&gt;&lt;br&gt;
Before you refactor, define &lt;strong&gt;what metric equals “user happiness”&lt;/strong&gt; in your context.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3. Measure with Real-User Data (RUM)
&lt;/h2&gt;

&lt;p&gt;Synthetic tests show potential; &lt;strong&gt;RUM (Real User Monitoring)&lt;/strong&gt; shows reality.&lt;/p&gt;

&lt;p&gt;Use libraries like &lt;a href="https://github.com/Zizzamia/perfume.js" rel="noopener noreferrer"&gt;Perfume.js&lt;/a&gt; or analytics such as Vercel Analytics, SpeedCurve, or Datadog RUM.&lt;/p&gt;

&lt;p&gt;Measure from actual browsers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LCP (Largest Contentful Paint)&lt;/strong&gt; — the time when the &lt;strong&gt;largest visible element&lt;/strong&gt; in the viewport is painted.

&lt;ul&gt;
&lt;li&gt;It’s not “main content,” but whichever element is &lt;em&gt;largest&lt;/em&gt; at render time.
&lt;/li&gt;
&lt;li&gt;Valid LCP candidates include:&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; elements
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;image&amp;gt;&lt;/code&gt; inside &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Background images via &lt;code&gt;url()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Video poster images
&lt;/li&gt;
&lt;li&gt;Block-level text elements (&lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;, etc.)
&lt;/li&gt;
&lt;li&gt;LCP represents when the &lt;strong&gt;most significant visual element&lt;/strong&gt; becomes visible — a strong proxy for perceived load time.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;INP (Interaction to Next Paint)&lt;/strong&gt; — measures how quickly the page responds to user input during the entire session.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;CLS (Cumulative Layout Shift)&lt;/strong&gt; — quantifies how much visible content unexpectedly moves while loading.&lt;/li&gt;

&lt;/ul&gt;




&lt;h3&gt;
  
  
  How Google Actually Measures INP
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;INP (Interaction to Next Paint)&lt;/strong&gt; is the newest Core Web Vital replacing FID (First Input Delay).&lt;br&gt;&lt;br&gt;
Unlike FID, which captured only the &lt;em&gt;first&lt;/em&gt; input, INP observes &lt;strong&gt;all interactions&lt;/strong&gt; (clicks, taps, keypresses) throughout a session and reports the worst one — the longest delay between input and the next frame rendered.&lt;/p&gt;

&lt;p&gt;Google collects this data progressively from &lt;strong&gt;real Chrome users&lt;/strong&gt; through the &lt;strong&gt;Chrome User Experience Report (CrUX)&lt;/strong&gt; and browser telemetry.&lt;br&gt;&lt;br&gt;
Each session contributes anonymized samples aggregated by domain, producing real-world performance data visible in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PageSpeed Insights → Field Data tab&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CrUX Dashboard (BigQuery / Looker Studio)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Good INP ≤ 200 ms (P75)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Needs improvement 200–500 ms&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Poor &amp;gt; 500 ms&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That &lt;strong&gt;75th percentile (P75)&lt;/strong&gt; is the line Google Search uses when evaluating Core Web Vitals for ranking.&lt;br&gt;&lt;br&gt;
So even if Lighthouse shows a perfect 100, CrUX field data may still rank your site as &lt;em&gt;slow&lt;/em&gt; if your real users experience higher interaction latency.&lt;/p&gt;




&lt;p&gt;Then break it down by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Device&lt;/strong&gt; (desktop vs low-end mobile)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Region / network&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App version&lt;/strong&gt; (old vs new)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;RUM gives you percentile data (P75, P95) across real users — the difference between “it’s fast on my machine” and “it’s fast for 80 % of our audience.”&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4. Create an A/B Test for Performance
&lt;/h2&gt;

&lt;p&gt;If you want to know whether your performance work &lt;em&gt;mattered&lt;/em&gt;, you need a controlled comparison.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Set up an A/B or gradual rollout:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Group A → old build&lt;/li&gt;
&lt;li&gt;Group B → optimized build&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then compare:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web Vitals (LCP, INP, CLS)&lt;/li&gt;
&lt;li&gt;Business metrics (conversion, retention, bounce)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your app got “faster” but conversion didn’t move → you probably optimized something users didn’t care about.&lt;/p&gt;

&lt;p&gt;Performance work must always connect to &lt;em&gt;user or business outcomes&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5. Correlate Technical Metrics with Business Outcomes
&lt;/h2&gt;

&lt;p&gt;Real impact = when you can say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Reducing LCP from 4 s → 2 s improved checkout completion by 3 %.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s a win you can show to both engineers and product managers.&lt;/p&gt;

&lt;p&gt;How to get there:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Capture performance metrics via RUM.&lt;/li&gt;
&lt;li&gt;Log key business events (purchase, signup, retention).&lt;/li&gt;
&lt;li&gt;Correlate them in your analytics system.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Even a simple scatter plot (LCP vs conversion rate) can reveal patterns.&lt;br&gt;&lt;br&gt;
That’s how you go from “we think it’s faster” to “we know it’s paying off.”&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6. Close the Feedback Loop in CI/CD
&lt;/h2&gt;

&lt;p&gt;Once you know what matters, automate it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;Lighthouse CI&lt;/strong&gt; or &lt;strong&gt;Calibre&lt;/strong&gt; to detect regressions in PRs.&lt;/li&gt;
&lt;li&gt;Add &lt;strong&gt;RUM dashboards&lt;/strong&gt; visible to everyone (Datadog, Grafana, Vercel Analytics).&lt;/li&gt;
&lt;li&gt;Set alerts for &lt;em&gt;real&lt;/em&gt; metrics, not just lab ones (e.g. &lt;code&gt;LCP P75 &amp;gt; 3 s&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal isn’t to chase 100/100 — it’s to &lt;em&gt;never regress&lt;/em&gt; on what truly affects users.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7. Communicate Results Like an Engineer, Not a Salesperson
&lt;/h2&gt;

&lt;p&gt;The biggest mistake is presenting performance wins as vanity metrics:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“We improved Lighthouse by 20 points!”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No.&lt;br&gt;&lt;br&gt;
Say this instead:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“We reduced LCP by 35 %, and users now see the product 1.2 s sooner. Checkout conversions went up 2.8 %.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s how you make performance work visible and valuable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 8. Recognize When “Faster” Stops Paying Off
&lt;/h2&gt;

&lt;p&gt;Every optimization has diminishing returns.&lt;/p&gt;

&lt;p&gt;If your app already loads in ~2 s on average devices, shaving another 200 ms won’t move metrics — but fixing &lt;em&gt;layout shifts&lt;/em&gt; or &lt;em&gt;input delay&lt;/em&gt; might.&lt;/p&gt;

&lt;p&gt;The goal isn’t perfection; it’s &lt;em&gt;perceived speed.&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Real users care about “feels fast,” not “measured fast.”&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lighthouse = lab test&lt;/strong&gt;, not success metric.
&lt;/li&gt;
&lt;li&gt;Combine &lt;strong&gt;Lab + RUM + A/B&lt;/strong&gt; for complete performance visibility.
&lt;/li&gt;
&lt;li&gt;Define &lt;em&gt;what fast means&lt;/em&gt; for your users.
&lt;/li&gt;
&lt;li&gt;Correlate technical and business outcomes.
&lt;/li&gt;
&lt;li&gt;Once users stop noticing slowness, you’re done.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Performance is the easiest thing to brag about and the hardest thing to prove.&lt;br&gt;&lt;br&gt;
You can’t screenshot real impact — you have to measure it.&lt;/p&gt;

&lt;p&gt;The next time you optimize something, ask yourself:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Did this make my users’ experience faster — or just my metrics look better?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s the difference between a performance &lt;em&gt;refactor&lt;/em&gt; and a performance &lt;em&gt;result.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Things to Remember When You Test Your React App</title>
      <dc:creator>Roman</dc:creator>
      <pubDate>Wed, 17 Sep 2025 06:53:22 +0000</pubDate>
      <link>https://forem.com/rbobr/things-to-remember-when-you-test-your-react-app-584e</link>
      <guid>https://forem.com/rbobr/things-to-remember-when-you-test-your-react-app-584e</guid>
      <description>&lt;p&gt;Many teams I have worked with had the same problem:&lt;br&gt;
They had tests, but nobody trusted them.&lt;/p&gt;

&lt;p&gt;The coverage report was green. The CI pipeline was slow and running for a while to pass your PR check. And yes, bugs still slipped to production - while developers quietly skipped running tests because they knew half of them were flaky or meaningless.&lt;/p&gt;

&lt;p&gt;That’s the real danger: once team stops trusting tests, the whole test suite becomes dead weight, it’s just extra code to maintain which gives you zero confidence.&lt;/p&gt;

&lt;p&gt;And everyone learned the same lesson: &lt;strong&gt;a test suite that isn’t trusted is even worse than having no tests at all.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;So how do we avoid that? What makes tests &lt;em&gt;trustworthy&lt;/em&gt; instead of just &lt;em&gt;existing&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;Here are some lessons I’ve learned the hard way.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Coverage Doesn’t Equal Quality&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I’ve been on projects where hitting “90% coverage” was treated like a milestone. The dashboard turned green, the manager smiled, and… we still shipped broken features.&lt;/p&gt;

&lt;p&gt;Why? Because coverage only measures &lt;strong&gt;lines executed&lt;/strong&gt;, not whether a test proves the app works.&lt;/p&gt;

&lt;p&gt;I’ll give you an example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;renders the login form&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LoginForm&lt;/span&gt; &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input[name='email']&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&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;That test executes almost every line in the component, so coverage shoots up. But if someone removed the submit logic tomorrow, it would still pass. The suite looks great on paper - and yet the feature is broken.&lt;/p&gt;

&lt;p&gt;What you really need are &lt;strong&gt;intention-driven tests&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submits user login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSubmit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LoginForm&lt;/span&gt; &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSubmit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;fireEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByPlaceholderText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/email/i&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;fireEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByPlaceholderText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/password/i&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&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;fireEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/submit/i&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test doesn’t just run code. It protects the &lt;strong&gt;user intention&lt;/strong&gt;: if I type credentials and hit submit, does the app behave correctly?&lt;/p&gt;

&lt;p&gt;Coverage is a useful metric, but it’s not the goal. Trust comes from testing &lt;strong&gt;meaning, not lines.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How You Select Element - Matters
&lt;/h2&gt;

&lt;p&gt;Another hidden trust killer (and performance) is &lt;strong&gt;how you select elements&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Early tests often relied on brittle selectors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.btn-primary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem? CSS class changes → test fails, even though the UI still works.&lt;/p&gt;

&lt;p&gt;Or overusing everywhere &lt;code&gt;data-testid&lt;/code&gt; attributes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit-btn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Does it work - yes? But I often see how people keep brining huge constants file into their bundles with contains and creates necessary values for test ids.&lt;/p&gt;

&lt;p&gt;That’s why React Testing Library recommends semantic queries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;getByRole → “there’s a button”&lt;/li&gt;
&lt;li&gt;getByLabelText → “the input is labeled Email”&lt;/li&gt;
&lt;li&gt;getByText → “the user can read this text”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It forces you to test the UI the way a user interacts with it.&lt;br&gt;
And again - that’s what builds trust.&lt;/p&gt;
&lt;h2&gt;
  
  
  What (and When) to Mock
&lt;/h2&gt;

&lt;p&gt;This is where many teams lose trust in their tests. Mocking is powerful, but it also comes with great responsibility.&lt;/p&gt;

&lt;p&gt;Mock too little → tests hit the network or the database → slow, flaky, unreliable.&lt;br&gt;
Mock too much → you’re not testing reality anymore, just your own fakes.&lt;/p&gt;

&lt;p&gt;The rules I follow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;External systems and network&lt;/strong&gt; → always mock (network calls, DB, filesystems, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pure utilities&lt;/strong&gt; → don’t mock (just use directly, they directly affect you output and expectations)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internal modules / components&lt;/strong&gt; → here you need to be careful. You need to keep balance here to avoid creating gap between what you need to test and how your app actually runs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once we had and issue on a project where the whole authService module was mocked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../authService&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;mockResolvedValue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fake&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test suite  was always passing, but one day we got critical production issue with authorization management. None of the tests caught it - because test wasn’t using the actual &lt;code&gt;authService&lt;/code&gt; , it was testing it’s own mock, and when output of service changes, everything was still green.&lt;/p&gt;

&lt;p&gt;The solution? Don’t mock the service itself. Mock the &lt;strong&gt;external source the service depends on&lt;/strong&gt;. For example, intercepting fetch calls with &lt;strong&gt;MSW&lt;/strong&gt;. You still avoid real network calls, but you keep your real service logic intact.&lt;/p&gt;

&lt;p&gt;That’s the key: &lt;strong&gt;mock at system boundaries, not inside your app’s core.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Unit vs Integration Tests (Do We Still Believe in the Pyramid?)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Remember the “testing pyramid”? Tons of unit tests at the bottom, fewer integration tests in the middle, and a few e2e tests on top.&lt;/p&gt;

&lt;p&gt;In React projects, that pyramid often collapsed, We ended up with thousands of tiny unit tests for hooks and buttons… but they don’t really catch bugs. They don’t capture the whole picture of what is going on, they live in their small incapsulated world.&lt;/p&gt;

&lt;p&gt;Most failures I’ve seen happen &lt;strong&gt;between units:&lt;/strong&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data not passed correctly.&lt;/li&gt;
&lt;li&gt;Validation breaking.&lt;/li&gt;
&lt;li&gt;API responses mishandled.&lt;/li&gt;
&lt;li&gt;Promises unhandled.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s why many developers now lean toward the &lt;strong&gt;Testing Trophy:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unit tests for pure logic&lt;/li&gt;
&lt;li&gt;Integration tests for flows (user fills form → API is called → success message shows)&lt;/li&gt;
&lt;li&gt;e2e tests for critical paths&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I won’t say the pyramid is dead - but in practice, an &lt;strong&gt;integration-first mindset&lt;/strong&gt; has given me way more trust than an ocean of isolated unit tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Snapshot Tests: A False Safety Net&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Snapshots felt like magic at first. One line of code, instant coverage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toMatchSnapshot&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In reality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Snapshots broke constantly on minor markup changes.&lt;/li&gt;
&lt;li&gt;Diffs were huge and unreadable.&lt;/li&gt;
&lt;li&gt;Developers started clicking “update snapshot” without looking.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At that point, the tests weren’t protecting anything. They were just busywork.&lt;/p&gt;

&lt;p&gt;The truth is simple: &lt;strong&gt;snapshots don’t prove behavior.&lt;/strong&gt; They don’t tell you if the app actually works for the user.&lt;/p&gt;

&lt;p&gt;You’ll always get more value from writing explicit assertions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;heading&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/welcome/i&lt;/span&gt; &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Determinism vs Flakiness&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I’ve watched test suites collapse under flakiness. Once developers stop trusting the suite, they stop running it. And from there, it dies.&lt;/p&gt;

&lt;p&gt;The culprits are always the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real timers (setTimeout, setInterval)&lt;/li&gt;
&lt;li&gt;Random values (UUIDs, Math.random)&lt;/li&gt;
&lt;li&gt;Async race conditions&lt;/li&gt;
&lt;li&gt;Overuse of waitFor with arbitrary timeouts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cure is determinism:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Freeze time (vi.setSystemTime).&lt;/li&gt;
&lt;li&gt;Mock randomness (vi.mock("uuid")).&lt;/li&gt;
&lt;li&gt;Use fake timers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s not about making tests “less real.” It’s about making them &lt;strong&gt;reliable&lt;/strong&gt;. If a test sometimes passes and sometimes fails, it’s worse than no test at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Flaky Tests: Detect and Eliminate&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Once flaky tests creep in, trust goes out the window. That’s why finding and fixing them fast is critical.&lt;/p&gt;

&lt;p&gt;Tools help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Jest → --detectOpenHandles, --listTestsByDuration&lt;/li&gt;
&lt;li&gt;Vitest → --slowTestThreshold&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But prevention is even better:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoid leaking state between tests.&lt;/li&gt;
&lt;li&gt;Don’t rely on arbitrary timeouts.&lt;/li&gt;
&lt;li&gt;Always wait for explicit conditions (like findByRole).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When I see a flaky test in CI, I don’t ignore it. I either quarantine it or fix it immediately. Because the longer a flaky test lives in main, the faster your entire suite loses credibility.&lt;/p&gt;

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

&lt;p&gt;The history of frontend testing is a history of &lt;strong&gt;false confidence&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Coverage numbers that didn’t mean safety.&lt;/li&gt;
&lt;li&gt;Snapshots that didn’t mean stability.&lt;/li&gt;
&lt;li&gt;Mocks that didn’t mean reality.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The lesson? Tests aren’t about numbers. They’re about &lt;strong&gt;trust&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A good test answers two questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If it fails, do I know something important is broken?&lt;/li&gt;
&lt;li&gt;If it passes, do I trust the feature works?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the answer is “no,” the test is noise.&lt;/p&gt;

&lt;p&gt;That’s what I remind myself every time I write tests. Because at the end of the day, I don’t want more tests. I want a suite my team actually believes in.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>jest</category>
      <category>react</category>
      <category>testing</category>
    </item>
    <item>
      <title>Self-Hosting Next.js: What You Gain (and Lose) vs Vercel</title>
      <dc:creator>Roman</dc:creator>
      <pubDate>Thu, 21 Aug 2025 09:07:02 +0000</pubDate>
      <link>https://forem.com/rbobr/self-hosting-nextjs-what-you-gain-and-lose-vs-vercel-4g8c</link>
      <guid>https://forem.com/rbobr/self-hosting-nextjs-what-you-gain-and-lose-vs-vercel-4g8c</guid>
      <description>&lt;p&gt;Next.js and Vercel often feel inseparable - the framework and the platform evolving side by side. For many developers, deploying to Vercel is the default: zero-config, instant CI/CD, and all the modern features baked in.&lt;/p&gt;

&lt;p&gt;Let’s be honest - at some point Next.js started to feel like a &lt;strong&gt;Vercel-first framework&lt;/strong&gt;, rather than a truly standalone one. &lt;/p&gt;

&lt;p&gt;But at the end of the day, we’re still able to run Next.js outside of Vercel in standalone mode, and deploy it anywhere Node.js is supported - AWS, GCP, Azure, or even bare metal. This remains a solid option for many companies that need to self-host for cost, compliance, security, or control reasons.&lt;/p&gt;

&lt;p&gt;So what do we actually gain, and what do we lose, when self-host Next.js instead of running it on Vercel?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Even Consider Self-Hosting?
&lt;/h2&gt;

&lt;p&gt;If Vercel works that well (and it is!), why move away then?&lt;br&gt;
The answer usually comes down to scale, compliance, and control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost predictability at scale&lt;/strong&gt; → Vercel’s usage-based pricing can spike unpredictably&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom caching&lt;/strong&gt; → use Redis, S3, DynamoDB, or hybrid cache strategies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise compliance&lt;/strong&gt; → data residency or vendor restrictions may block SaaS usage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep cloud integration and security&lt;/strong&gt; → private VPC databases, IAM roles, WAF, custom logging&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure control&lt;/strong&gt; → tune cold starts, multi-region routing, autoscaling rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yes, Vercel makes it easy to scale at globe from start, but it still does not give you that control over you infrastructure, regions to deploy, controlling CDN and etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Cost: The Short-Run vs Long-Run Trade-Off&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;In the short run → Vercel is often cheaper&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No DevOps team required&lt;/li&gt;
&lt;li&gt;No need to build CI/CD, release processes, or monitoring pipelines&lt;/li&gt;
&lt;li&gt;Pay-as-you-go pricing works well for &lt;strong&gt;unpredictable workloads&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;In the short run → Self-hosting is often more expensive&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You must build deployment pipelines (GitHub Actions, Terraform, Kubernetes)&lt;/li&gt;
&lt;li&gt;You need an infra/DevOps team to manage scaling, observability, and rollbacks&lt;/li&gt;
&lt;li&gt;Upfront complexity often eats away the “theoretical” savings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;In the long run → Self-hosting usually wins&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reserved capacity (EC2, ECS, K8s, or Lambda Savings Plans) drives down per-request costs&lt;/li&gt;
&lt;li&gt;Costs become &lt;strong&gt;predictable and controllable&lt;/strong&gt; - important for enterprises running steady high-traffic workloads&lt;/li&gt;
&lt;li&gt;You fully control networking and its cost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 &lt;strong&gt;Think of it like this:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vercel&lt;/strong&gt; = renting a fully furnished apartment. Easy to move in, but rent grows with usage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-hosting&lt;/strong&gt; = buying a house. Expensive up front, but you own and optimize every detail long-term.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What You Gain with Self-Hosting&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Full Control Over Infrastructure&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;On Vercel you get a black box. On your own infra, you decide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where builds run (AWS Lambda, ECS, Kubernetes, Cloud Run)&lt;/li&gt;
&lt;li&gt;How caching works (Redis, S3, DynamoDB)&lt;/li&gt;
&lt;li&gt;Which CDN powers your edge (CloudFront, Fastly, Cloudflare)&lt;/li&gt;
&lt;li&gt;Deployment and rollback strategies&lt;/li&gt;
&lt;li&gt;Regions to deploy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…and many other things.&lt;/p&gt;

&lt;p&gt;👉 I’ve covered &lt;a href="https://dev.to/rbobr/mastering-custom-cache-strategy-in-nextjs-4n6m"&gt;&lt;strong&gt;advanced caching strategies for self-hosted Next.js&lt;/strong&gt;&lt;/a&gt; (including custom cache handlers) in a separate article - check that out if you want to go deeper.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Integration with Your Cloud Stack&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Running Next.js inside AWS/GCP makes it seamless to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connect directly to &lt;strong&gt;private VPC databases&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;IAM roles&lt;/strong&gt; instead of API keys&lt;/li&gt;
&lt;li&gt;Plug into &lt;strong&gt;CloudWatch / Prometheus / Datadog&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Deploy &lt;strong&gt;multi-region replicas&lt;/strong&gt; behind Route53 or GCP Load Balancing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 With Vercel, you often need a separate backend service just to handle private resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Potentially Lower Costs at Scale&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;At high scale, self-hosting can be &lt;strong&gt;3–5x cheaper&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vercel Enterprise&lt;/strong&gt;: usage-based, priced per request + bandwidth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Lambda&lt;/strong&gt;: ~$0.20 per million requests + execution time., when &lt;strong&gt;Vercel&lt;/strong&gt; costs ~$.60 per million requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EC2/K8s&lt;/strong&gt;: fixed monthly cost, can be reduced further with reserved instances.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;50M requests/month&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On Vercel: ~$1,000+&lt;/li&gt;
&lt;li&gt;On AWS Lambda: $250–$400&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: these numbers are rough estimates. Actual costs vary depending on execution time, region, and instance size.&lt;/p&gt;

&lt;p&gt;But remember: these savings only show up once you’ve absorbed the DevOps overhead. That’s why self-hosting usually only pays off in the &lt;strong&gt;long run&lt;/strong&gt;, and mostly when you hit specific requirements - like sustained high load or global infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What You Lose with Self-Hosting&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Zero-Config DX&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Vercel: git push → deploy. And that’s it, no complex CI/CD, no scripts, no cdk or terraform.&lt;/p&gt;

&lt;p&gt;Self-host: you own CI/CD, cache warming, rollbacks, infra debugging - and you’ll need a DevOps team to manage it.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Built-In Features&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Vercel gives you out of the box:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Image optimization&lt;/li&gt;
&lt;li&gt;Edge middleware at global scale&lt;/li&gt;
&lt;li&gt;Analytics dashboards&lt;/li&gt;
&lt;li&gt;Global Infrastructure (it keeps growing)&lt;/li&gt;
&lt;li&gt;Full integration with git&lt;/li&gt;
&lt;li&gt;Easy to create different environments (dev, stage, prod, etc.) + preview envs&lt;/li&gt;
&lt;li&gt;Logging&lt;/li&gt;
&lt;li&gt;All Next.js features are integrated immediately into vercel (like redirects, rewrites, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On self-host you need to &lt;strong&gt;implement or configure all of this yourself&lt;/strong&gt;, and some features aren’t trivial to replicate quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Scaling Complexity&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Vercel makes scaling invisible.&lt;/p&gt;

&lt;p&gt;Self-hosting means handling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cold starts on Lambda / Cloud Functions&lt;/li&gt;
&lt;li&gt;Instances autoscaling&lt;/li&gt;
&lt;li&gt;Global replication and routing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;When to Choose Vercel vs Self-Hosting&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Stay on Vercel if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You want &lt;strong&gt;speed of iteration&lt;/strong&gt; and zero DevOps overhead&lt;/li&gt;
&lt;li&gt;Your workloads are mostly frontend + rendering, not heavy API/data crunching&lt;/li&gt;
&lt;li&gt;You’re okay with &lt;strong&gt;usage-based pricing&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Even at the &lt;strong&gt;enterprise level&lt;/strong&gt; (e-commerce, SaaS, marketing) - Vercel is a solid choice, &lt;em&gt;as long as&lt;/em&gt;:

&lt;ul&gt;
&lt;li&gt;You monitor costs carefully&lt;/li&gt;
&lt;li&gt;You offload heavy compute/data work to dedicated backend services&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Go self-hosted if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need &lt;strong&gt;predictable infra costs&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Your app requires &lt;strong&gt;custom caching or storage strategies&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Compliance or data residency block SaaS usage&lt;/li&gt;
&lt;li&gt;You already have a DevOps team and cloud expertise&lt;/li&gt;
&lt;li&gt;Your infrastructure already lives in a cloud provider, and you want your Next.js app to integrate tightly into that ecosystem&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Vercel is unbeatable for &lt;strong&gt;time-to-market and simplicity&lt;/strong&gt;, and it keeps growing, adding new features and trying to offer better pricing, so it’s definitely a solid choice for projects of any size.&lt;/p&gt;

&lt;p&gt;But if you need &lt;strong&gt;control, compliance, or predictable cost at scale&lt;/strong&gt;, self-hosting gives you the freedom to optimize every layer - from caching to scaling to infra costs.&lt;/p&gt;

&lt;p&gt;The real takeaway:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Vercel is great for building products fast&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Self-hosting is great for running them at scale on your own terms&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>vercel</category>
      <category>react</category>
    </item>
    <item>
      <title>The Hidden Performance Costs of React Server Components</title>
      <dc:creator>Roman</dc:creator>
      <pubDate>Fri, 15 Aug 2025 08:25:47 +0000</pubDate>
      <link>https://forem.com/rbobr/the-hidden-performance-costs-of-react-server-components-248f</link>
      <guid>https://forem.com/rbobr/the-hidden-performance-costs-of-react-server-components-248f</guid>
      <description>&lt;p&gt;React Server Components (RSC) are one of the most talked-about additions to React in recent years — and they’ve quickly become a core part of frameworks like Next.js (with App Router), Remix and other SSR React Frameworks.&lt;/p&gt;

&lt;p&gt;But let’s get somethings straight before we dive in:&lt;br&gt;
&lt;strong&gt;RSC is not a new standard, engine, or framework&lt;/strong&gt;.&lt;br&gt;
It is &lt;strong&gt;low-level React API&lt;/strong&gt; that enables you to render components entirely on the server, stream the to the client, and hydrate only the parts that need interactivity.&lt;/p&gt;

&lt;p&gt;Each framework (like Next.js, Remix, Hydrogen, etc.) builds its own opinionated implementation of RSC - deciding on key points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How server rendering is integrated&lt;/li&gt;
&lt;li&gt;How data is fetched&lt;/li&gt;
&lt;li&gt;How streaming works&lt;/li&gt;
&lt;li&gt;How server transfers data to browser&lt;/li&gt;
&lt;li&gt;How caching and revalidation scenarios are handled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…and much more.&lt;/p&gt;

&lt;p&gt;So if you are using the Next.js App Router, you are using &lt;strong&gt;Next’s&lt;/strong&gt; RSC implementation specifically, not just “React Server Components” in the abstract.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why do we need Server Components
&lt;/h2&gt;

&lt;p&gt;Server component aim to solve long-standing web application pain points which were progressing over the time and releases of new tools, frameworks and requirements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Less JavaScript shipped to the client&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Yes! The problem what we hear for decades and each new tool, framework convinces us that problem is finally solved! But the idea is straightforward - render everything on server and pass to client&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Direct server-side data access&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is still common issues and hot topic for lot’s of developers on “How should I fetch the data and how it affects my page rendering” or “Building Next.js internal API endpoints”, we no longer need it, since components can talk directly to databases, filesystems, or other APIs&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Faster initial loads via streaming&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No need to wait for full page rendering, specially when it contains some heavy parts, you can start sending your HTML chunk by chunk as soon as they are ready.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Improved developer experience&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can write UI and data fetching all together in single place, without worrying about serialization and fetching life cycles.&lt;/p&gt;

&lt;p&gt;💡 &lt;em&gt;Does this remind you of something?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Those old days when we just had an Express server with Handlebars (or PHP, or Rails) and rendering whole template on server and just sending it to client.&lt;br&gt;
The difference? Now it is just React - with streaming, selective hydration, and all the modern bells and whistles.&lt;/p&gt;
&lt;h2&gt;
  
  
  Server Components Trade-Offs
&lt;/h2&gt;

&lt;p&gt;As I already mentioned, you should keep an eye on exact implementation of server components for specific component, because implementation are uniq, there is no standard, so whatever was working for you in one framework might work absolutely differently in another framework.&lt;/p&gt;

&lt;p&gt;But besides that we need to consider RSC changes &lt;strong&gt;where&lt;/strong&gt; and &lt;strong&gt;how&lt;/strong&gt; rendering happens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every render of a Server Component happens on the server, not in the browser.&lt;/li&gt;
&lt;li&gt;Browser no longer receives compiled JavaScript with you page bundle which to render on the client - it receives a serialized “Flight” payload which is telling it how to rebuild UI (rsc specific format).&lt;/li&gt;
&lt;li&gt;Client Components still exist in bundle, but they form of hydration islands inside server-render tree.&lt;/li&gt;
&lt;li&gt;Crossing a Server &amp;lt;&amp;gt; Client boundary is serialization cost you pay every time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These differences bring &lt;strong&gt;new pitfalls&lt;/strong&gt; that we need to consider, and in the meantime it does not remove rest of the pitfalls what we had for SSR apps which we still need to consider as well.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Real Performance Costs of Server Components
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Flight Payload Overhead (Server &amp;lt;&amp;gt; Client Boundaries)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In RSC, only Client Components hydrate - so any props passed from Server to Client must be serialized onto a &lt;strong&gt;Flight payload.&lt;/strong&gt; Large or deeply nested props props bloat the payload and delay hydration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Large object crossing boundary → huge Flight payload&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ServerPage&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;bigData&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;getLargeDataset&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// thousands of rows&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ClientChart&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;bigData&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt; &lt;span class="c1"&gt;// sent in Flight payload&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Send only what’s needed&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ClientChart&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;summaryRows&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;&lt;strong&gt;Mental picture:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It’s like shipping an entire warehouse to the customer’s house when they only ordered a single chair — and having them unpack it before they can use anything.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Streaming Delays for long and heavy components. Suspense order matters!&lt;/strong&gt; &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;RSC supports streaming - sending UI chunks to the browser as they are ready.&lt;br&gt;
If a slow server component sites above others without a Suspense boundary, it blocks the whole stream until it finishes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ One slow server call holds up the entire page&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&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;slowFetch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MainContent&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&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;}&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Wrap slow sections in Suspense&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Header&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;Suspense&lt;/span&gt; &lt;span class="na"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Loading&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SlowSection&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;Suspense&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Mental picture:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Like waiting for the slowest dish at a restaurant before you’re allowed to eat anything.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Use client scope explosion&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Putting “use client” at the top of a big/shared file promotes that file (and often its imports) to a Client Component, erasing the “no JS for server code” benefit and inflating bundles. Which might be okay for some of the cases, but it still requires additional learning curve for developers and understanding application specifics.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Community support&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;RSC changes the rules — and many libraries still aren’t ready for it. Some require "use client" to function, which negates many of RSC’s benefits.&lt;br&gt;
Be prepared: your favorite library might not yet work well in a Server Component environment. For example, most CSS-in-JS frameworks (MUI, Chakra, Stitches, etc.) currently require "use client".&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;React Server Components open the door to faster, leaner, and more maintainable React applications - but they aren’t a silver bullet.&lt;/p&gt;

&lt;p&gt;They shift complexity from the browser to the server, introduce new serialization and streaming considerations, and require a different way of thinking about component boundaries.&lt;/p&gt;

&lt;p&gt;If you adopt RSC, keep a close eye on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Where&lt;/strong&gt; your Server ↔ Client boundaries are&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What&lt;/strong&gt; data you pass across them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How&lt;/strong&gt; you use Suspense for streaming&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Which&lt;/strong&gt; libraries truly work well in a server-first model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;RSC is a powerful tool - but like any tool, using it effectively means understanding both its benefits &lt;strong&gt;and&lt;/strong&gt; its trade-offs.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>react</category>
      <category>programming</category>
    </item>
    <item>
      <title>Mastering Custom Cache Strategy in Next.js</title>
      <dc:creator>Roman</dc:creator>
      <pubDate>Wed, 06 Aug 2025 08:07:30 +0000</pubDate>
      <link>https://forem.com/rbobr/mastering-custom-cache-strategy-in-nextjs-4n6m</link>
      <guid>https://forem.com/rbobr/mastering-custom-cache-strategy-in-nextjs-4n6m</guid>
      <description>&lt;p&gt;Everybody knows that Next.js has powerful built-in caching capabilities — it usually &lt;em&gt;just works&lt;/em&gt;. When deploying your app to Vercel or Netlify, you don’t need to worry about what’s happening under the hood: how caching is scaled, how it’s shared, or even where it lives (CDN or application cache).&lt;/p&gt;

&lt;p&gt;But there are still many cases where you need to run your Next.js application on a cloud provider (a.k.a. self-hosting) like AWS, Azure, or GCP. This comes with its own trade-offs. Now you — or your DevOps — are responsible for managing infrastructure, instances, scaling, failover, networking… and yes, caching.&lt;/p&gt;

&lt;p&gt;This article isn’t about deploying Next.js to the cloud — it’s about how you can &lt;strong&gt;build a custom cache strategy&lt;/strong&gt; that improves reliability, performance, and opens the door for advanced features like A/B testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Default Caching Behavior in Next.js&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Next.js uses the file system to cache rendered output (HTML, JSON, RSC), and supplements this with an in-memory cache for frequently accessed data.&lt;/p&gt;

&lt;p&gt;Sounds solid, but here are some real-world limitations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Auto-scaling&lt;/strong&gt;: Cache is tied to the local instance. When you scale up, each new instance has a different, empty cache.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistence&lt;/strong&gt;: If an instance is replaced (scale-in, crash, etc.), its cache is lost — recreating cache can be costly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fragmentation&lt;/strong&gt;: Businesses often want A/B testing. That means rendering and caching multiple versions of the same page for different user segments.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, to build a robust solution, you need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Share cache across instances&lt;/li&gt;
&lt;li&gt;✅ Persist cache between restarts&lt;/li&gt;
&lt;li&gt;✅ Fragment cache for feature buckets or A/B testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of that becomes possible with a custom cache strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Creating a Custom Cache Handler&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Next.js allows you to provide a custom &lt;code&gt;cacheHandler&lt;/code&gt;. You can configure this in your &lt;code&gt;next.config.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// next.config.js&lt;/span&gt;
&lt;span class="kr"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;cacheHandler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./cache-handler.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="c1"&gt;// disable default in-memory caching&lt;/span&gt;
  &lt;span class="na"&gt;cacheMaxMemorySize&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your &lt;code&gt;cache-handler.js&lt;/code&gt; file should expose class which will be created during &lt;strong&gt;each request&lt;/strong&gt; to your server, it is very important to remember that your class will be created for each request and not once node process. &lt;/p&gt;

&lt;p&gt;Let’s create simple example of custom in-memory cache class to see what it does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// cache-handler.js&lt;/span&gt;

&lt;span class="c1"&gt;// creating store outside, so it will be available on instance level&lt;/span&gt;
&lt;span class="c1"&gt;// and can be shared between different requests.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&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;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CacheHandler&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="nx"&gt;options&lt;/span&gt;&lt;span class="p"&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;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   
  &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// function to retrieve cache for the page&lt;/span&gt;
    &lt;span class="c1"&gt;// when returns null - then request will go to actual render function&lt;/span&gt;
    &lt;span class="c1"&gt;// otherwise when data in cache exists, will skip rendering the page&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&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;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&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;record&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;record&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="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// this will be triggered each time when next.js tries to write cache for the page&lt;/span&gt;
    &lt;span class="c1"&gt;// note: that for page router next.js caches only special types of pages, which are using ISR,&lt;/span&gt;
    &lt;span class="c1"&gt;// and for app router whenever your provide cache / revalidate properties&lt;/span&gt;
    &lt;span class="c1"&gt;// if you are using dynamic or SSR strategies it won't appear into cache handler.&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&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;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;lastModified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&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;tags&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;revalidateTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&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;store&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;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&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;store&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;key&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="nf"&gt;resetRequestCache&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// optional, This method resets the temporary in-memory cache&lt;/span&gt;
    &lt;span class="c1"&gt;// for a single request before the next request.&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CacheHandler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basically even this simple example shows how much power we receive and now we are able to solve all our issues and achieve. &lt;/p&gt;

&lt;h2&gt;
  
  
  Scaling &amp;amp; Persisting Cache
&lt;/h2&gt;

&lt;p&gt;I decided to combine the first two issues — cache sharing and persistence — because they’re pretty closely connected. In most real-world setups, the most effective solution is to move your cache into some form of shared storage. That could be S3 (or any blob storage), a shared file system, or even Redis if you prefer in-memory caching. The idea is simple: make your cache available outside the individual instance.&lt;/p&gt;

&lt;p&gt;This way, all of your instances — old or new — can access the same centralized cache. So even when auto-scaling kicks in and new instances spin up, they’re not starting from scratch. They just pick up where the others left off. Problem solved.&lt;/p&gt;

&lt;p&gt;Here’s a minimal example of a custom cache handler that reads and writes directly to S3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;S3Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PutObjectCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GetObjectCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DeleteObjectCommand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-s3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;S3Client&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CacheHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&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;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Now we can read data directly from s3 and if there is no data&lt;/span&gt;
    &lt;span class="c1"&gt;// then next.js will render it and call `set` method to write cache&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getObject&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_BUCKET_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transformToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;putObject&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_BUCKET_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// optional, but nice to have if you want to serve response directly from s3,&lt;/span&gt;
            &lt;span class="c1"&gt;// so CDN will know how to cache response.&lt;/span&gt;
            &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;revalidate&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;CacheControl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`max-age=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;revalidate&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;That’s it! Super straightforward, and yet we’ve already solved a lot. And what’s even better — we now have full control. You can extend this however you like and build a caching solution that fits your app’s architecture and scaling setup perfectly.&lt;/p&gt;

&lt;p&gt;Now for my favorite part: let’s make it A/B testing–friendly.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Fragmenting Cache for A/B Testing&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A/B testing is something most product teams want, and it’s actually pretty easy to support when you control your own cache handler.&lt;/p&gt;

&lt;p&gt;Here’s a quick way to fragment your cache per user bucket — so users in different experiments don’t share cache entries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// code stays the same ^^^&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CacheHandler&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="nx"&gt;options&lt;/span&gt;&lt;span class="p"&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;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;getRequestBucket&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// we can have a function which will identify user's a/b test bucket based on request&lt;/span&gt;
        &lt;span class="c1"&gt;// we can use this.options and context which will have all necessary data about request and route&lt;/span&gt;
        &lt;span class="c1"&gt;// and return our value&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cookie&lt;/span&gt; &lt;span class="o"&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;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bucket_a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a&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;b&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentBucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRequestBucket&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;// That's it, now you are able to fragment your cache&lt;/span&gt;
        &lt;span class="c1"&gt;// and read data for particular cache segment &lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cacheKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;currentBucket&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;
        &lt;span class="c1"&gt;//...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&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;currentBucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRequestBucket&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;// Same here, just we want to write unique key for our segment&lt;/span&gt;
        &lt;span class="c1"&gt;// so we can read it later on &lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cacheKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;currentBucket&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;
        &lt;span class="c1"&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;Easy! Wow we’ve got separate cache storage per experiment group. No more cache collisions between multiple experiments!&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;So now we’ve solved all the problems we started with. Cache is shared across instances, persistent across restarts, and even segmented per user group for A/B testing. And the best part — it’s all under your control.&lt;/p&gt;

&lt;p&gt;Of course, real-world implementations can be more complex, especially when you throw in edge caching, CDN rules, or enterprise-scale observability. But the core idea remains simple: &lt;strong&gt;own your caching strategy&lt;/strong&gt;, and you’ll unlock a ton of flexibility and performance.&lt;/p&gt;

&lt;p&gt;And remember, there are only two hard things in programming:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Naming things&lt;/li&gt;
&lt;li&gt;Managing cache 😅&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every strategy comes with trade-offs, but with this approach, you’ve got the tools to make the best choice for your setup. Happy caching!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>react</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Why You Should Use Zod Instead of TypeScript Alone</title>
      <dc:creator>Roman</dc:creator>
      <pubDate>Fri, 01 Aug 2025 09:47:20 +0000</pubDate>
      <link>https://forem.com/rbobr/why-you-should-use-zod-instead-of-typescript-alone-427b</link>
      <guid>https://forem.com/rbobr/why-you-should-use-zod-instead-of-typescript-alone-427b</guid>
      <description>&lt;p&gt;Have you ever encountered a situation where your TypeScript-powered application, despite having perfect type definitions, unexpectedly broke at runtime due to an unexpected API change or malformed user input? You're not alone. While TypeScript significantly improves developer experience by catching type errors during compile time, it does not protect you at runtime. TypeScript doesn't fix your data - it just makes the problems easier to see. But in order to simplify our lives and protect at runtime, we can combine it with zod (typescript + zod = ❤️).&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Zod?
&lt;/h2&gt;

&lt;p&gt;From their website:&lt;br&gt;
”TypeScript-first schema validation with static type inference by &lt;a href="https://x.com/colinhacks" rel="noopener noreferrer"&gt;&lt;strong&gt;@colinhacks&lt;/strong&gt;&lt;/a&gt;”&lt;/p&gt;

&lt;p&gt;Unlike TypeScript, which checks types only at compile-time, Zod ensures data adheres to defined schemas at runtime, preventing unexpected errors or misleading data.&lt;/p&gt;

&lt;p&gt;Additionally, Zod offers various integrations with many tools, starting with simple schema validations, to complex forms, APIs, and database schemas validations, which helps you to protect your schema and data type at all stages of development.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why TypeScript Alone Isn’t Enough
&lt;/h2&gt;

&lt;p&gt;Yes, I know, it won’t be new for your or it won’t surprise you. TypeScript offers type safety only during build/compile phase. It still helps reduce errors and improve documentation — so even when revisiting old code, you can make a fairly confident guess about what it’s doing 🙂. However, at runtime, we’re back to plain JavaScript—dynamic, flexible, but without any built-in type safety, making our apps vulnerable to unexpected errors.&lt;/p&gt;

&lt;p&gt;Here is quick example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Profile&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
 &lt;span class="nx"&gt;name&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;description&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;imageSrc&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getProfile&lt;/span&gt; &lt;span class="o"&gt;=&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;Profile&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/profile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="c1"&gt;// Generally there is no guarantee that it will match the interface.&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;profile&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;getProfile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How Zod Complements TypeScript
&lt;/h2&gt;

&lt;p&gt;Zod provides explicit runtime validation, which allows to make sure that your data’s shape is still what you expect and avoid surprising errors:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ProfileSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
 &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
 &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
 &lt;span class="na"&gt;imageSrc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;ProfileSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getProfile&lt;/span&gt; &lt;span class="o"&gt;=&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;Profile&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/profile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="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;profile&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;ProfileSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Throws an error if validation failed.&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;profile&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;getProfile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;So with Zod’s usage we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Less boilerplate - fewer manual checks, cleaner code&lt;/li&gt;
&lt;li&gt;Automatic type inference - instantly reflects schema updates&lt;/li&gt;
&lt;li&gt;Easy to maintain and scale - centralized schema definitions&lt;/li&gt;
&lt;li&gt;Simplified advanced error handling - clearer error outputs for debugging&lt;/li&gt;
&lt;li&gt;Integration across your entire stack - from forms to APIs to database interactions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In real world you can integrate it with pretty much anything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Form validation: e.g. integrate with React Hook Forms for validation&lt;/li&gt;
&lt;li&gt;API Routes: validate incoming requests and outgoing response formats&lt;/li&gt;
&lt;li&gt;External API integrations: check response structure from services like supabase, or stripe, .etc&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;BodySchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;
 &lt;span class="c1"&gt;// at this moment we can safely proceed with valid request data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useForm&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-hook-form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;zodResolver&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;@hookform/resolvers/zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;MyForm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;formState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useForm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;zodResolver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formSchema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&lt;/span&gt;&lt;span class="dl"&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;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;formState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;formState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'submit'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance Impact
&lt;/h2&gt;

&lt;p&gt;Overall Zod adds minimal overhead - practically unnoticeable in most real-world applications,&lt;br&gt;
its simplicity and reliability easily justify the minimal performance cost — but keep in mind your specific use case, especially when dealing with deeply nested or large JSON structures.&lt;/p&gt;

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

&lt;p&gt;So combining both Zod and TypeScript provides compile-time and runtime safety, enhancing reliability and confidence of the application and us as developers. If you're looking for a way to solve runtime safety issues, Zod is an excellent solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Links &amp;amp; Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod Official Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://react-hook-form.com/get-started#SchemaValidation" rel="noopener noreferrer"&gt;React Hook Form Integration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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