<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shubhajit Chatterjee</title>
    <description>The latest articles on DEV Community by Shubhajit Chatterjee (@shubhajit_chatterjee).</description>
    <link>https://dev.to/shubhajit_chatterjee</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%2F2040226%2Fb55f4bcd-9b83-4690-a2ae-1c4d9c95e3de.png</url>
      <title>DEV Community: Shubhajit Chatterjee</title>
      <link>https://dev.to/shubhajit_chatterjee</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shubhajit_chatterjee"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>Shubhajit Chatterjee</dc:creator>
      <pubDate>Sat, 16 May 2026 12:46:52 +0000</pubDate>
      <link>https://dev.to/shubhajit_chatterjee/-2kj</link>
      <guid>https://dev.to/shubhajit_chatterjee/-2kj</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/shubhajit_chatterjee/how-i-used-claude-code-and-git-worktrees-to-ship-2-features-and-fix-3-bugs-in-one-day-325n" class="crayons-story__hidden-navigation-link"&gt;How I used Claude Code and git worktrees to ship 2 features and fix 3 bugs in one day&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/shubhajit_chatterjee" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2040226%2Fb55f4bcd-9b83-4690-a2ae-1c4d9c95e3de.png" alt="shubhajit_chatterjee profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/shubhajit_chatterjee" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Shubhajit Chatterjee
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Shubhajit Chatterjee
                
              
              &lt;div id="story-author-preview-content-3682883" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/shubhajit_chatterjee" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2040226%2Fb55f4bcd-9b83-4690-a2ae-1c4d9c95e3de.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Shubhajit Chatterjee&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/shubhajit_chatterjee/how-i-used-claude-code-and-git-worktrees-to-ship-2-features-and-fix-3-bugs-in-one-day-325n" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 16&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/shubhajit_chatterjee/how-i-used-claude-code-and-git-worktrees-to-ship-2-features-and-fix-3-bugs-in-one-day-325n" id="article-link-3682883"&gt;
          How I used Claude Code and git worktrees to ship 2 features and fix 3 bugs in one day
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/claude"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;claude&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/git"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;git&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/shubhajit_chatterjee/how-i-used-claude-code-and-git-worktrees-to-ship-2-features-and-fix-3-bugs-in-one-day-325n" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;2&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/shubhajit_chatterjee/how-i-used-claude-code-and-git-worktrees-to-ship-2-features-and-fix-3-bugs-in-one-day-325n#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>How I used Claude Code and git worktrees to ship 2 features and fix 3 bugs in one day</title>
      <dc:creator>Shubhajit Chatterjee</dc:creator>
      <pubDate>Sat, 16 May 2026 12:05:19 +0000</pubDate>
      <link>https://dev.to/shubhajit_chatterjee/how-i-used-claude-code-and-git-worktrees-to-ship-2-features-and-fix-3-bugs-in-one-day-325n</link>
      <guid>https://dev.to/shubhajit_chatterjee/how-i-used-claude-code-and-git-worktrees-to-ship-2-features-and-fix-3-bugs-in-one-day-325n</guid>
      <description>&lt;p&gt;I want to share an interesting workflow I experimented with recently while working with Claude Code and git worktrees.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;I had a lot on my plate with deadlines approaching. I had to fix 3 bugs and complete 2 feature implementations.&lt;/p&gt;

&lt;p&gt;And it was not just implementation work. I had to write new unit tests, update existing test cases, manually verify everything, and make sure fixing one thing was not breaking another.&lt;/p&gt;

&lt;p&gt;Initially it did not feel like too much. But once I properly analysed everything that needed to be done, I realised the approaching deadline could easily turn this into a mess if I handled it poorly.&lt;/p&gt;

&lt;p&gt;I wanted to experiment that day.&lt;/p&gt;

&lt;h2&gt;
  
  
  How would I have approached this earlier
&lt;/h2&gt;

&lt;p&gt;Normally I would have started with one task, completed it, raised a PR, and then moved on to the next one.&lt;/p&gt;

&lt;p&gt;If I got PR comments, I would stash or commit my current work, switch back to the other branch, fix the comments, and iterate on that again. Then I would come back to my original task and continue implementing.&lt;/p&gt;

&lt;p&gt;This process itself is not necessarily bad. Always moving fast is not the solution.&lt;/p&gt;

&lt;p&gt;The real problem was that these were new implementations. And in my experience, implementing something new usually requires significantly more thinking, exploration, and iteration. If I spiralled on one task, it could easily consume a huge amount of time while everything else remained blocked behind it.&lt;/p&gt;

&lt;p&gt;This would have easily taken me 3-4 days.&lt;/p&gt;

&lt;h2&gt;
  
  
  The moment of realisation
&lt;/h2&gt;

&lt;p&gt;I paused for a while that day and went through multiple possible approaches in my head.&lt;/p&gt;

&lt;p&gt;I cannot work on all of them in parallel myself. That is just a fact.&lt;/p&gt;

&lt;p&gt;But AI can. But the problem was how do I actually make this practical?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Should I run multiple Claude Code sessions and ask each one to handle a different task&lt;/strong&gt;&lt;br&gt;
But that would quickly become a mess. All of them editing the same working directory and modifying overlapping files was guaranteed to create conflicts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Then I thought, what if I ask Claude Code to not make edits and only work in memory?&lt;/strong&gt;&lt;br&gt;
But that was not practical either. I could not properly verify anything, test changes, or realistically iterate on the implementation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;What if I clone the repository multiple times and run separate Claude Code sessions in each copy?&lt;/strong&gt;&lt;br&gt;
That probably would have worked. But it felt extremely clunky and difficult to manage.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At that point I decided to simply ask Claude Code itself how to solve this problem.&lt;/p&gt;

&lt;p&gt;That was the moment it introduced me to &lt;strong&gt;git worktrees&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  So What are git worktrees
&lt;/h2&gt;

&lt;p&gt;I had never used git worktrees before this. I vaguely knew they existed but never had a reason to try them seriously.&lt;/p&gt;

&lt;p&gt;Interestingly, I didn’t even manually set this up myself initially. Claude Code already knew how to work with git worktrees. I simply asked it to help me create a parallel workflow for multiple tasks in the same repository and it handled most of the git setup.&lt;/p&gt;

&lt;p&gt;The idea is actually pretty simple.&lt;/p&gt;

&lt;p&gt;Normally, we work on one branch at a time. Switching between tasks means stashing changes, checking out another branch, rebuilding context, running the project again, and mentally reconnecting with what we were doing.&lt;/p&gt;

&lt;p&gt;Git worktrees solve this differently.&lt;/p&gt;

&lt;p&gt;Instead of switching branches in the same directory, you create multiple working directories attached to the same repository. Each directory has its own branch checked out independently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git worktree add ../project-feature-1 feature/feature-1
git worktree add ../project-bug-1 fix/bug-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this, I effectively had five copies of the same project open at once — each isolated to its own task.&lt;/p&gt;

&lt;p&gt;One thing I liked here is that I didn’t need to become a git worktree expert before trying this workflow. Claude Code already understood the setup pretty well and guided most of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running Claude Code in parallel
&lt;/h2&gt;

&lt;p&gt;I opened a separate Claude Code session in each worktree directory. I am no expert in prompts.&lt;/p&gt;

&lt;p&gt;For the bugs, I gave each session proper context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what the issue was&lt;/li&gt;
&lt;li&gt;how to reproduce it&lt;/li&gt;
&lt;li&gt;what I suspected the root cause might be&lt;/li&gt;
&lt;li&gt;what I had already checked&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the features, I described the expected behaviour, edge cases, and which parts of the codebase would probably be involved.&lt;/p&gt;

&lt;p&gt;Then I let them run in parallel.&lt;/p&gt;

&lt;p&gt;But honestly, this was not some magical “everything worked perfectly” workflow.&lt;/p&gt;

&lt;p&gt;Managing five parallel sessions was mentally exhausting initially. There was a lot of context switching. One session would ask for clarification while another had already generated code that needed verification while another was going in the wrong direction entirely.&lt;/p&gt;

&lt;p&gt;The interesting part was learning how to drive the sessions properly.&lt;/p&gt;

&lt;p&gt;If Claude Code was drifting away from what I wanted, I had to steer it back with more precise instructions. Sometimes the difference between a useful output and a useless one was simply giving better constraints or explaining the intent more clearly.&lt;/p&gt;

&lt;p&gt;Over time, I noticed something unexpected, it started becoming easier.&lt;/p&gt;

&lt;p&gt;The first hour felt chaotic. Later it started feeling surprisingly natural.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually became the bottleneck
&lt;/h2&gt;

&lt;p&gt;At some point I realised the bottleneck had shifted.&lt;/p&gt;

&lt;p&gt;Claude Code was generating implementations faster than I could comfortably review and verify them.&lt;/p&gt;

&lt;p&gt;I was moving between worktrees continuously:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;checking diffs&lt;/li&gt;
&lt;li&gt;testing fixes&lt;/li&gt;
&lt;li&gt;reproducing bugs&lt;/li&gt;
&lt;li&gt;validating edge cases&lt;/li&gt;
&lt;li&gt;correcting prompts&lt;/li&gt;
&lt;li&gt;answering follow-up questions from another session&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The limiting factor was no longer typing speed.&lt;/p&gt;

&lt;p&gt;It was my ability to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;make decisions quickly&lt;/li&gt;
&lt;li&gt;maintain context across multiple tasks&lt;/li&gt;
&lt;li&gt;verify correctness without losing focus&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That felt very different from normal development work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification
&lt;/h2&gt;

&lt;p&gt;I manually verified everything. Every bug fix I reproduced the original issue first, confirmed it was gone, checked for obvious regressions. Every feature I tested the happy path and the edge cases I actually cared about.&lt;/p&gt;

&lt;p&gt;This would have become more easier with better agent harness. I learnt about this later, maybe will write about this as well in some future blogs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;I was able to ship all 5 requirements with production quality in a single day. That genuinely made me very happy.&lt;br&gt;
The entire workflow felt fascinating to me, so I thought I should share the experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I work now
&lt;/h2&gt;

&lt;p&gt;Now, it’s not like I always run things in parallel. Honestly, even after getting familiar with the workflow, it is still mentally exhausting sometimes. But now I know that if I ever need to move fast, there is actually a practical way to do it.&lt;/p&gt;

&lt;p&gt;One thing this experiment made very obvious to me is how important understanding the codebase becomes in this kind of workflow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The better I understood the codebase, the easier it became to guide Claude Code properly.
&lt;/li&gt;
&lt;li&gt;The better I understood the bugs, the faster I could identify when an implementation was incorrect.
&lt;/li&gt;
&lt;li&gt;The clearer my instructions were, the better the parallel execution worked.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That shift was probably the most interesting part of the entire experiment.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>git</category>
    </item>
    <item>
      <title>Building a single active tab experience</title>
      <dc:creator>Shubhajit Chatterjee</dc:creator>
      <pubDate>Tue, 05 May 2026 05:13:26 +0000</pubDate>
      <link>https://dev.to/shubhajit_chatterjee/building-a-single-active-tab-experience-2k4b</link>
      <guid>https://dev.to/shubhajit_chatterjee/building-a-single-active-tab-experience-2k4b</guid>
      <description>&lt;p&gt;I recently had a requirement where, if a user opens the app in multiple browser tabs, only one tab can be active at a time, and the rest are locked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Requirements
&lt;/h2&gt;

&lt;p&gt;Here are the core requirements as I see them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;Uniquely identify&lt;/em&gt; each tabs&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Store&lt;/em&gt; the active tab somewhere&lt;/li&gt;
&lt;li&gt;If a user &lt;em&gt;opens a new tab&lt;/em&gt;, lock it if there is already an active tab&lt;/li&gt;
&lt;li&gt;If the &lt;em&gt;active tab is closed&lt;/em&gt;, detect that and inform the other tabs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Sounds simple enough, doesn't it? Take a moment and think about how you'd solve this. It's more interesting than it looks.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Edge Cases
&lt;/h2&gt;

&lt;p&gt;Let’s think through some edge cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Next active tab&lt;/strong&gt; - If the user closes the active tab and multiple tabs are locked, which one should become active?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale active tab&lt;/strong&gt; - If the user closes the browser and returns later, how do we ensure that the previously stored active tab is not considered?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Race condition&lt;/strong&gt; - If two tabs are opened at the same time, how do you handle the race condition?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Not so simple anymore, right?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Possible Solutions
&lt;/h2&gt;

&lt;p&gt;Let's explore some solutions that I came across.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local Storage
&lt;/h3&gt;

&lt;p&gt;The most obvious solution is &lt;code&gt;localStorage&lt;/code&gt;. Here is how the flow would look like - &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate a unique identifier for each tab&lt;/li&gt;
&lt;li&gt;Store the first tab as active in &lt;code&gt;localStorage&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;When a new tab is opened, check the storage for any active tab.&lt;/li&gt;
&lt;li&gt;If one exists, lock the new tab, otherwise, make it active&lt;/li&gt;
&lt;li&gt;Subscribe to storage events to react to active tab changes.&lt;/li&gt;
&lt;li&gt;When the active tab is closed, remove it from storage so another tab can become active.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The above does work and new tabs are correctly locked.&lt;/p&gt;

&lt;p&gt;Now let's visit the edge cases - &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Next active tab&lt;/strong&gt; - We can store the order in which the tabs are opened, and then based on the order, we can choose to select the next tab. This adds complexity, as we must also remove closed tabs from the order array. We can store this in &lt;code&gt;localStorage&lt;/code&gt; as well.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale active tab&lt;/strong&gt; - You might think that we can use &lt;code&gt;beforeunload&lt;/code&gt; event and remove it from the &lt;code&gt;localStorage&lt;/code&gt;. But the beforeunload event is &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event#usage_notes" rel="noopener noreferrer"&gt;not that reliable&lt;/a&gt;. It won't fire in scenarios like a &lt;strong&gt;system crash&lt;/strong&gt; or an &lt;strong&gt;abrupt OS shutdown&lt;/strong&gt;, leaving stale data in &lt;code&gt;localStorage&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Race condition&lt;/strong&gt; - Honestly, I couldn't find a clean way to handle this with &lt;code&gt;localStorage&lt;/code&gt; alone.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Broadcast Channel
&lt;/h3&gt;

&lt;p&gt;The flow here is almost identical to the &lt;code&gt;localStorage&lt;/code&gt; approach, with two differences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We use a &lt;code&gt;BroadcastChannel&lt;/code&gt; to communicate directly between tabs&lt;/li&gt;
&lt;li&gt;Instead of storing state in &lt;code&gt;localStorage&lt;/code&gt;, each tab maintains its own in-memory copy of the active tab and the order array, kept in sync via &lt;code&gt;BroadcastChannel&lt;/code&gt; messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is how the flow would be like -&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate a unique identifier for each tab&lt;/li&gt;
&lt;li&gt;When a new tab opens, broadcast a message to ask for the current state&lt;/li&gt;
&lt;li&gt;An existing tab responds with the active tab and the order array&lt;/li&gt;
&lt;li&gt;Based on the response, decide whether to lock the new tab or make it active&lt;/li&gt;
&lt;li&gt;When the active tab is closed, broadcast a message so other tabs can update their in-memory state and elect the next active tab&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now let's revisit the edge cases —&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Next active tab&lt;/strong&gt; - Same as before, we maintain the order array in memory and pick the next one when the active tab closes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale active tab&lt;/strong&gt; - Interestingly, this is partially better. Since state is in-memory, it dies with the browser — no stale data persists to the next session. However, we still rely on &lt;code&gt;beforeunload&lt;/code&gt; to notify other tabs, which is not that reliable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Race condition&lt;/strong&gt; - Same problem. Still no clean solution here.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;A step forward, but still does not covers everything we need.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Web Locks API
&lt;/h2&gt;

&lt;p&gt;Both approaches we discussed earlier got us close, but we were essentially building a locking mechanism from scratch. What if the browser already had one?&lt;/p&gt;

&lt;p&gt;This is where the &lt;strong&gt;Web Locks API&lt;/strong&gt; really shines. Instead of building a locking mechanism from scratch, we can use a browser-provided one that already handles coordination between tabs, including queuing and race conditions.&lt;/p&gt;

&lt;p&gt;In our case, the application itself becomes the shared resource. We can define a lock name, say &lt;code&gt;dev:app&lt;/code&gt;, and use it to ensure that only one tab can acquire it at a time.&lt;/p&gt;

&lt;p&gt;Here is how a typical flow would look like - &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When a tab is opened, use the &lt;strong&gt;Web Locks API&lt;/strong&gt; to acquire the lock.&lt;/li&gt;
&lt;li&gt;If the lock is acquired, set the current tab to active.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's all. Everything else is handled by the browser.&lt;br&gt;
Here’s how this looks in React, though the same idea applies across frameworks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setActive&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dev:app&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="nf"&gt;setActive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="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;active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;This&lt;/span&gt; &lt;span class="nx"&gt;tab&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;This&lt;/span&gt; &lt;span class="nx"&gt;tab&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's visit the edge cases for this -&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Next active tab&lt;/strong&gt; - The browser handles assigning the next active tab. When the active tab is closed, the lock is released and the browser automatically assigns it to the next tab in the queue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale active tab&lt;/strong&gt; - Since the &lt;strong&gt;Web Locks API&lt;/strong&gt; doesn't persist any state, there is no stale data to worry about. When the browser is closed, all locks are released automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Race condition&lt;/strong&gt; - The browser handles this gracefully. When multiple tabs try to acquire the lock at the same time, the browser ensures only one tab gets it, queuing the rest.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;What started as a simple requirement turned into a useful reminder that sometimes the real challenge is not in the implementation, but in knowing what the platform already provides. Instead of building our own coordination logic, we can rely on the Web Locks API to handle it in a much more reliable way.&lt;/p&gt;

&lt;p&gt;If you find yourself building a locking mechanism from scratch in the browser, stop and check if the &lt;strong&gt;Web Locks API&lt;/strong&gt; fits. Chances are, it does.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>browser</category>
    </item>
    <item>
      <title>Performant Web Apps</title>
      <dc:creator>Shubhajit Chatterjee</dc:creator>
      <pubDate>Sun, 08 Sep 2024 12:15:00 +0000</pubDate>
      <link>https://dev.to/shubhajit_chatterjee/performant-web-apps-2n8m</link>
      <guid>https://dev.to/shubhajit_chatterjee/performant-web-apps-2n8m</guid>
      <description>&lt;p&gt;Imagine a world where dynamic web applications are so responsive, they feel as fast as static sites served directly from a user’s local system. That level of speed would create a seamless and delightful user experience.&lt;/p&gt;

&lt;p&gt;But is that even possible? Dynamic apps need to load HTML, CSS, JavaScript, retrieve data, render the UI, and often pull in more resources as users interact. So how do we achieve snappy performance, especially when screens depend heavily on server data? A “local-first” approach might help, but it’s not always feasible—and not every manager will agree to it.&lt;/p&gt;

&lt;p&gt;I’ll share strategies I’ve learned from blogs, X (formerly Twitter), and YouTube videos to help close the gap and improve the user experience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We will discuss the following here&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Problem&lt;/li&gt;
&lt;li&gt;Possible Cause&lt;/li&gt;
&lt;li&gt;
Possible Solutions

&lt;ul&gt;
&lt;li&gt;Poorly written data fetching or async code&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fetch-as-you-render&lt;/code&gt; &amp;amp; &lt;code&gt;render-as-you-fetch&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Before jumping into performance strategies, let’s define the problem. The perfect user experience depends on factors like network speed, server latency, and data processing.&lt;/p&gt;

&lt;p&gt;Our goal is simple: reduce loading states. We won’t focus on code optimization, data structures, or algorithms. Instead, we’ll explore solutions for SPAs that work across frameworks and can be extended to SSR apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Possible Causes
&lt;/h2&gt;

&lt;p&gt;One major reason apps feel slow is the network waterfall. This is very well explained on &lt;a href="https://remix.run/" rel="noopener noreferrer"&gt;Remix’s&lt;/a&gt; homepage. I encourage everyone to check it out if not already done. But how does a traditional app cause this? Common reasons I’ve seen in many codebases include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Poorly written data fetching or async code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Fetch-as-you-render&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Decoupled components that fetch their own data, leading to more &lt;code&gt;fetch-as-you-render&lt;/code&gt; scenarios&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thus the above problem statement can be rewritten as &lt;em&gt;reducing network waterfall&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Possible Solutions
&lt;/h2&gt;

&lt;p&gt;Tools these days are very good and when used together can handle some of the above scenarios if used properly.&lt;/p&gt;

&lt;p&gt;Let's see some ways to resolve some of the mentioned above&lt;/p&gt;

&lt;h3&gt;
  
  
  Poorly written data fetching or async code
&lt;/h3&gt;

&lt;p&gt;Let’s take a look at this function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchUserData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postsResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/posts/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&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;posts&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;postsResponse&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;commentsResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/comments/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&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;comments&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;commentsResponse&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;posts&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 function fetches a user’s posts and comments based on userId. However, it causes a network waterfall because the comments request waits for the posts request to finish.&lt;/p&gt;

&lt;p&gt;We can improve it by fetching both at the same time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchUserData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/posts/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&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;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/comments/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&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;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;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="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;comments&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By using &lt;code&gt;Promise.all&lt;/code&gt;, both API calls happen in parallel, removing the waterfall delay.&lt;/p&gt;

&lt;p&gt;There are many methods available on Promise that can be useful in different scenarios&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Not all API calls can be parallelized—some may depend on the results of others—but many don’t. For dependent requests, this optimization doesn’t apply, and further strategies would depend on specific cases (which we won’t cover here).&lt;/p&gt;

&lt;p&gt;The example above doesn’t handle errors, as it’s just for demonstration.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;fetch-as-you-render&lt;/code&gt; &amp;amp; &lt;code&gt;render-as-you-fetch&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is an interesting one and is very very common in codebases. So, what is &lt;code&gt;fetch-as-you-render&lt;/code&gt; and how does it cause delayed UI?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fetch-as-you-render&lt;/code&gt; is caused when data is fetched inside a component and the Fetch-as-you-render happens when data fetching is triggered inside a component, often during the render phase or in lifecycle methods like useEffect. This is common across many frontend frameworks such as &lt;code&gt;Solid&lt;/code&gt;, &lt;code&gt;Svelte&lt;/code&gt;, and &lt;code&gt;Vue&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Imagine you have three nested components, each fetching its own data. This creates a scenario similar to poorly written data fetching: each component’s data fetch starts only after the component is rendered or mounted. This not only delays the rendering but can also cause additional delays as each fetch waits for the previous one to complete.&lt;/p&gt;

&lt;p&gt;Let look at a piece of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;UserDataViewer&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;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;fetchUser&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;_user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="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;isLoading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Posts&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;UserCommentsViewer&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Posts&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setPost&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Post&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;fetchUserPosts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_posts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="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;isLoading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PostsViewer&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt; /&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;show&lt;/span&gt; &lt;span class="nx"&gt;Posts&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;UserCommentsViewer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setComments&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Comment&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;fetchUserComments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_comments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setComments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_comments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="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;isLoading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Comments&lt;/span&gt; &lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;; /&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;show&lt;/span&gt; &lt;span class="nx"&gt;comments&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The provided code demonstrates the &lt;code&gt;fetch-as-you-render&lt;/code&gt; pattern, which can lead to delayed UI updates. Here’s how:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;UserDataViewer&lt;/code&gt;: It fetches user data and waits for it to complete before rendering the &lt;code&gt;Posts&lt;/code&gt; and &lt;code&gt;UserCommentsViewer&lt;/code&gt; component.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Posts&lt;/code&gt; &amp;amp; &lt;code&gt;UserCommentsViewer&lt;/code&gt;: Once &lt;code&gt;UserDataViewer&lt;/code&gt; has fetched and set the user data, &lt;code&gt;Posts&lt;/code&gt; fetches posts based on the user ID.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this setup, each component fetches its data only after the previous component’s data is available, causing a cascading delay. Each fetch operation waits for the component to render or mount, leading to slower overall page load times.&lt;/p&gt;

&lt;p&gt;Fetching data per component is a common pattern to have colocation but can lead to inefficiencies. Libraries like &lt;code&gt;@tanstack/react-router&lt;/code&gt; and &lt;code&gt;SWR&lt;/code&gt; simplify the process, but the &lt;code&gt;fetch-as-you-render issue&lt;/code&gt; remains. Hoisting data fetching to a parent component helps, but what if that parent component is rendered within another component? How can we further improve this approach?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Render-as-you-fetch&lt;/code&gt; is a interesting pattern that can improve this situation. This can have its own blog, maybe I will write this in more details some other time, but let's directly jump to a solution.&lt;/p&gt;

&lt;p&gt;Most frameworks provide data loaders integrated with their routers. For example, &lt;code&gt;react-router-dom&lt;/code&gt; has &lt;a href="https://reactrouter.com/en/main/route/loader" rel="noopener noreferrer"&gt;loaders&lt;/a&gt; and &lt;a href="https://reactrouter.com/en/main/route/action" rel="noopener noreferrer"&gt;actions&lt;/a&gt;, &lt;code&gt;@tanstack/react-router&lt;/code&gt; offers &lt;a href="https://tanstack.com/router/latest/docs/framework/react/guide/data-loading" rel="noopener noreferrer"&gt;loaders&lt;/a&gt;, &lt;code&gt;@solidjs/router&lt;/code&gt; uses a &lt;a href="https://docs.solidjs.com/solid-router/reference/load-functions/load" rel="noopener noreferrer"&gt;load function&lt;/a&gt;, and &lt;code&gt;@sveltejs/kit&lt;/code&gt; includes &lt;a href="https://kit.svelte.dev/docs/load" rel="noopener noreferrer"&gt;load function&lt;/a&gt;. Let’s explore how we can leverage &lt;code&gt;react-router-dom&lt;/code&gt; and its loader and composition to improve data fetching efficiency.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loader&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;userId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchUser&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;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/posts/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&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;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/comments/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&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;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;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="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;comments&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;UserDataViewer&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;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;comments&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLoaderData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Posts&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;UserCommentsViewer&lt;/span&gt; &lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above eliminates the waterfall effect by fetching data in the loader and passing it to the component as props. While this approach blocks user navigation until the data is fetched, you can adjust this behavior using &lt;code&gt;Await&lt;/code&gt;, &lt;code&gt;defer&lt;/code&gt;, and &lt;code&gt;Suspense&lt;/code&gt; components. For more details, &lt;a href="https://reactrouter.com/en/main/guides/deferred" rel="noopener noreferrer"&gt;refer to the guide in the react-router-dom documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But this above code has one problem, the data fetching is completely moved to loaders, and the colocation with component is lost. What if I want to drop &lt;code&gt;UserCommentsViewer&lt;/code&gt; in another component in another route? I have to make sure that the data loader their fetches the data and correctly pass it to component, I cannot expect that I drop a component and it just works.&lt;/p&gt;

&lt;p&gt;Let's improve this further using &lt;code&gt;@tanstack/react-query&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loader&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;userId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ensureQueryData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getPostsQueryOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ensureQueryData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getCommentsQueryOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;UserDataViewer&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;userId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLoaderData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Suspense&lt;/span&gt; &lt;span class="nx"&gt;fallback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;}&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Posts&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Suspense&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Suspense&lt;/span&gt; &lt;span class="nx"&gt;fallback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;}&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;UserCommentsViewer&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Suspense&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Posts&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&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="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getPostsSuspenseQueryOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* The actual code to render */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;UserCommentsViewer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&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="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getPostsSuspenseQueryOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* The actual code to render */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code tackles the waterfall issue while keeping the data fetching colocated with the component. The above code works as follows -&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Loader Function&lt;/strong&gt;: Instead of just letting the components fetch their own data, the loader triggers the actual api call parallely. This prevents the waterfall. The code above will also render the route without waiting for the data to be fetched, this can be modified by awaiting them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Colocated Data Fetching&lt;/strong&gt;: The &lt;code&gt;Posts&lt;/code&gt; and &lt;code&gt;UserCommentsViewer&lt;/code&gt; components use &lt;code&gt;useQuery&lt;/code&gt; to access the already-fetched or ongoing fetch data. Since the loader has done the heavy lifting, these components can render themselves without worrying about the waterfall.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Another great thing about these components is that they’re self-contained. You can drop them anywhere in your React app, and they’ll work just fine. Of course, if you move the component, the route loader will need the prefetch logic too, but that’s easily handled.&lt;/p&gt;

&lt;p&gt;There’s another cool benefit to using loaders for prefetching. Most routing libraries can prefetch data when a link is hovered over or rendered, meaning the data is ready before the user even clicks the link. This taps into the &lt;code&gt;render-as-you-fetch&lt;/code&gt; pattern, where the component already has the data by the time it renders, eliminating the need for fetch calls inside the component.&lt;/p&gt;

&lt;p&gt;I personally find this approach clean and efficient. I stumbled upon this while digging through the &lt;code&gt;@tanstack/react-router&lt;/code&gt; docs. You’ll find a similar implementation in the &lt;a href="https://github.com/solidjs/solid-hackernews/blob/tanstack/src/routes/stories/%5Bid%5D.tsx" rel="noopener noreferrer"&gt;Solid.js repo for Hacker News&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Of course, there are plenty of other reasons why web apps might feel slow, and frameworks are constantly rolling out new solutions. But these approaches are relatively easy to incorporate into existing codebases and can make a noticeable difference.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hey everyone! This is my first blog, and I’d love to hear your suggestions and feedback to help me improve. I wrote this to share some solutions I’ve come across while working on codebases and tutorials, where I’ve noticed these common issues pop up. Let me know what you think!&lt;/p&gt;
&lt;/blockquote&gt;

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