<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[tech-blogs]]></title><description><![CDATA[tech-blogs]]></description><link>https://tusharmotwani.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/689c31554cd27c69d8aefe44/12a34b37-1d88-4f95-9041-c65d627cffab.png</url><title>tech-blogs</title><link>https://tusharmotwani.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Mon, 21 Sep 2026 20:52:56 GMT</lastBuildDate><atom:link href="https://tusharmotwani.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Why I stopped hating React Server Components]]></title><description><![CDATA[When I first migrated to next js from react, I was frustrated by the idea of having components named differently as "client" or "server" components.
And a rule book of how to use what kind of componen]]></description><link>https://tusharmotwani.hashnode.dev/why-i-stopped-hating-react-server-components</link><guid isPermaLink="true">https://tusharmotwani.hashnode.dev/why-i-stopped-hating-react-server-components</guid><category><![CDATA[Next.js]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Server side rendering]]></category><category><![CDATA[react server components]]></category><dc:creator><![CDATA[Tushar Motwani]]></dc:creator><pubDate>Sat, 19 Sep 2026 17:36:05 GMT</pubDate><content:encoded><![CDATA[<p>When I first migrated to next js from react, I was frustrated by the idea of having components named differently as "client" or "server" components.</p>
<p>And a rule book of how to use what kind of component, like not having listeners and hooks in the server components, adding the top directive "use client" to explicitly mention client components, calling the DB directly from server component and all.</p>
<p>Like, I mean why do we need that at all?</p>
<p>As a react developer, I was habitual with the way of webapps operating completely on the client.</p>
<p>Apps that get built at client, js bundles execute at client and even data fetches happen from the client.</p>
<p>But when I got introduced with the metrices like build time (js bundle execution) at the client, SEO, FCP and all, and how they differ in the classic SPAs and Next js server components, I realised that something was really off working that way.</p>
<h2>How do classic SPAs work?</h2>
<p>At the classic Single Page Applications like React, everything happens on client like fetching the js bundle, building the app shell, executing the javascript and fetching the data.</p>
<p>In a typical client-rendered SPA, the application cannot start its own data fetching and rendering until enough JavaScript has been downloaded and executed to bootstrap the application.</p>
<p>When you visit the app for the first time, the server sends you a raw html with no content, but a useful script which looks something similar too:</p>
<pre><code class="language-html">&lt;!doctype html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;link rel="stylesheet" href="/assets/index-a1b2c3.css"&gt;
    &lt;link rel="modulepreload" href="/assets/vendor-9f8e7d.js"&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id="root"&gt;&lt;/div&gt;
    /* script executes on client to load the content */
    &lt;script type="module" src="/assets/index-4d5e6f.js"&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>This HTML has nothing, no content, no elements in the body (except the root) to show at the client browser.</p>
<p>What it does have is a useful script tag with a JS file. The browser then loads this javascript file, compiles and executes it to render the app shell or what we call as layout of the app. The loaders or skeleton or static elements appear by now.</p>
<p>Means for a component like this:</p>
<pre><code class="language-typescript">import { useEffect, useState } from "react";

export default function App() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() =&gt; {
    fetch("http://api.data.com/user")
      .then((res) =&gt; res.json())
      .then((data) =&gt; setData(data))
      .finally(() =&gt; setLoading(false));
  }, []);

  if (loading) {
    return (
        &lt;h2&gt;User info&lt;/h2&gt;
        &lt;p&gt;Loading...&lt;/p&gt;
    );
  }

  return (
    &lt;h2&gt;User info&lt;/h2&gt;
    &lt;p&gt;{data.userName}&lt;/p&gt;
  );
}
</code></pre>
<p>Once the JS is loaded, compiled and executed at the browser, you will have the heading as "User info" and the "Loading..." text appear on your browser.</p>
<p>And once the data fetch is completed, you will the actual data in place replaced by the "Loading..." text or any kind of skeleton or spinner you have.</p>
<p>But we have got a major problem with this, because of these steps:</p>
<ul>
<li><p>HTML sent to client</p>
</li>
<li><p>JS bundle sent to client</p>
</li>
<li><p>Browser loads, complies and executes JavaScript</p>
</li>
<li><p>Data fetching occurs</p>
</li>
</ul>
<p>You see the actual data fetch occurs at the fourth step, once the JS is loaded, complied and executed on the client.</p>
<p>And if the JS bundles are huge or the browser network is slow, this may take a few seconds and until then user sees a blank screen on his browser which is enough for him to feel that the app is broken.</p>
<h2>React Server Components</h2>
<p>The way that the server components work is that you can have your async functions calling the database or api directly from the component without any useEffect hook.</p>
<p>Like the similar code block for data fetching above can be rewritten in server components as:</p>
<pre><code class="language-typescript">// app/dashboard/profile/page.tsx

async function getData() {
  const res = await fetch("http://api.data.com/user");
  return res.json();
}

export default async function ProfilePage() {
  const data = await getData();

  return (
    &lt;&gt;
      &lt;h2&gt;User info&lt;/h2&gt;
      &lt;p&gt;{data.userName}&lt;/p&gt;
    &lt;/&gt;
  );
}
</code></pre>
<p>And of course for the loading component, you can have a loading.tsx too:</p>
<pre><code class="language-typescript">// app/dashboard/profile/loading.tsx:

export default function Loading() {
  return (
    &lt;&gt;
      &lt;h2&gt;My Dashboard&lt;/h2&gt;
      &lt;p&gt;Loading...&lt;/p&gt;
    &lt;/&gt;
  );
}
</code></pre>
<p>or a tag for a better suspense boundary.</p>
<p>Now the cool thing with server components is, they are built at server. The data fetching or processing occurs much faster there.</p>
<p>And there is way less Javascript bundle size to get loaded, compiled and executed on the client if you can mark the components as "client" or "server" correctly.</p>
<p>Also, your app is way more faster, has less FCP time (if you have your suspense boundaries placed correctly).</p>
<p>RSC + streaming lets the server start sending useful UI while slower parts are still resolving.</p>
<p>And since the server components, never get re-executed on the client, that means they shouldn't and can't be hydrated <em>(Hydration is the process of taking server-rendered HTML and attaching React's client-side behavior to it so that it becomes interactive</em>. <em>It includes attaching listeners, events to your functions or hooks),</em> and that's exactly the reason why you can't have listeners (like onClick) and hooks (like useState, useEffect) on your server components.</p>
<p>The biggest advantage of this is the size of the JS bundle that will now get executed at the client is less as we have less components to built and hydrated at the client (marked as client components).</p>
]]></content:encoded></item><item><title><![CDATA[Why Your Next.js Navigations Feel Slow]]></title><description><![CDATA[You click a dashboard link. Nothing changes. You wait. And then suddenly the entire page appears.
The database query might only take a couple of seconds. But from the user's perspective, the applicati]]></description><link>https://tusharmotwani.hashnode.dev/why-your-next-js-navigations-feel-slow</link><guid isPermaLink="true">https://tusharmotwani.hashnode.dev/why-your-next-js-navigations-feel-slow</guid><category><![CDATA[Next.js]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[react server components]]></category><dc:creator><![CDATA[Tushar Motwani]]></dc:creator><pubDate>Mon, 14 Sep 2026 17:45:59 GMT</pubDate><content:encoded><![CDATA[<p>You click a dashboard link. Nothing changes. You wait. And then suddenly the entire page appears.</p>
<p>The database query might only take a couple of seconds. But from the user's perspective, the application feels frozen.</p>
<p>The problem isn't necessarily Server Components.</p>
<p>The problem is letting an async Server Component block the user from seeing any feedback.</p>
<p>I can route around a web app, observe the slow navigations and almost for every such app, I can tell you that it is built using next server components.</p>
<p>But honestly, this isn't a problem with the Next.js team or Server Components themselves. The problem is how we build applications with them. We bring the same mindset we use when handling async operations in client components, without thinking about how server-side async work affects navigation UX.</p>
<h2>Understanding the Problem</h2>
<p>The server components in next can be async, they can have multiple async operations inside them before the component is completely ready to be served to the client.</p>
<p>That's why they are built at server, they need (or are at least meant) to perform some async operations before they are ready to be rendered on the client.</p>
<p>Like a profile page needs to call the database and fetch the logged in user's info to display it inside the component. So your /app/profile/page.tsx may look like:</p>
<pre><code class="language-typescript">export default async function page() {
 // have the db call:
 const user = await db.users.find(session.userId) // ---&gt; ASYNC

 return (
    &lt;div&gt;{user.username}&lt;/div&gt;
 )
}
</code></pre>
<p>Now the db call above is an async operation, it takes time probably a few milliseconds but still that's some time, and to be honest if you have large data operations, complex queries, it can take around 3-4 seconds or more.</p>
<p>And since this is a server component which means it builds on server, when your user clicks profile tab, it takes time to build at the server and it takes over the page only when it is completely ready. So your app is sitting there unresponsive to the user in the time, the page is being ready.</p>
<p>The UI is unresponsive on the user click, there's no way for the user to know that his action is registered and now the page is being ready.</p>
<p>And that is an awful UX. I click on a page and I see no response, that's terrible, and suddenly the page is ready on my screen, that's even worse.</p>
<p>Remember the problem is not the db call taking time, it will eventually as your app grows, you can optimise upon it but still there is some time taken. The actual problem, not responding the user that the requested content is "loading".</p>
<h2>How next renders the react tree</h2>
<p>To have the solution, and more importantly to understand why this happens, we first need to get into how the react tree is rendered with your app.</p>
<p>Let's take a standard next app which looks something like this:</p>
<pre><code class="language-javascript">my-next-app/
│
├── src/
│   ├── app/
│   │   ├── layout.tsx
│   │   ├── page.tsx
│   │   | 
│   │   │
│   │   └── dashboard/
│   │       ├── layout.tsx
│   │       └── page.tsx --&gt; // async server component (calls db)
</code></pre>
<p>Here in this app, the page.tsx inside dashboard is the one that is an async server component, it fetches the data from DB, and renders on the page, so the page is avaliable at "/dashboard".</p>
<p>Now this page is routed inside dashboard which is further routed inside the root app, so effectively the page is at: "my-next-app/app/dashboard".</p>
<p>A classic app/layout.tsx is something like:</p>
<pre><code class="language-typescript">export default function RootLayout({
  children,
}: Readonly&lt;{
  children: React.ReactNode;
}&gt;) {
  return (
    &lt;html lang="en"&gt;
      &lt;body className="min-h-full flex flex-col"&gt;
          {children} 
      &lt;/body&gt;
    &lt;/html&gt;
  );
}
</code></pre>
<p>This app/layout covers app/page and similarly the dashboard/layout covers the dashboard/page and looks something like:</p>
<pre><code class="language-typescript">// dashboard/layout.tsx:

export default function RootLayout({
  children,
}: Readonly&lt;{
  children: React.ReactNode;
}&gt;) {
  return (
    &lt;html lang="en"&gt;
      &lt;body className="min-h-full flex flex-col"&gt;
        &lt;Sidebar /&gt;
        &lt;main&gt;
          {children} 
        &lt;/main&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  );
}
</code></pre>
<p>where in our case, the dashboard/page.tsx looks somthing like:</p>
<pre><code class="language-typescript">// dashboard/page.tsx:

export default async function page() {
 // have the db call:
 const user = await db.users.find(session.userId) // ---&gt; ASYNC

 return (
    &lt;div&gt;{user.username}&lt;/div&gt;
 )
}
</code></pre>
<p>To render <code>/dashboard</code>, Next.js composes the layouts and page belonging to that route into a React tree.</p>
<p>So effectively to reach /app/dashboard/page.tsx, the tree collects /app/layout.tsx, /app/page.tsx and /dashboard/layout.tsx.</p>
<p>So building the page "/app/dashboard/page.tsx", the react tree looks something like:</p>
<pre><code class="language-typescript">&lt;app/layout&gt; // -&gt; root layout
    &lt;html&gt;
        &lt;head&gt;
            .....
	    &lt;/head&gt;
        &lt;body&gt;
            &lt;app/dashboard/layout&gt; // --&gt; dashboard layout
                &lt;Sidebar&gt; // --&gt; sidebar
                &lt;main&gt;
                    &lt;app/dashboard/page&gt; // --&gt; dashboard page
                &lt;/main&gt;
            &lt;/ app/dashboard/layout&gt;
        &lt;/body&gt;
    &lt;/html&gt;
&lt;/ app/layout&gt;
</code></pre>
<blockquote>
<p><em><strong>A small note:</strong></em> <em>This is a simplified way of looking at what's happening. Next.js and React have a much more complex rendering and streaming system under the hood, but that's a topic for another article. For now, all we need to understand is that async work can leave the user staring at an unchanged screen if we don't give them a loading or Suspense fallback.</em></p>
</blockquote>
<p>Note, everything except the dashboard page (app/dashboard/page.tsx) is static and available at build time, so next js is actually ready to render the tree and display the content as soon as someone navigates to your dashboard page but since the dashboard page itself is an async server component and it needs time to get resolved, and literally from the user's perspectve, it feels like a hang.</p>
<p>Means, I have clicked on the dashboard and there is no visual response in my screen indicating that my action has been registered which is actually the fact and the page is being ready, but who's gonna tell me that.</p>
<p>This also means, if the route doesn't have to wait for server-side async work before showing its content, the user can see the UI much sooner.</p>
<p>The static pages, not having any async server side operation to display their content can be available instantly as the user navigates to them.</p>
<p>But wait, does that means there's no way to get the server component work that way?</p>
<p>No, there's a way and that's pretty simple that most of us think.</p>
<p>When the UI you're navigating to depends on async work, you need a boundary that gives Next/React something to show while that work is pending. Next allows us to pass a static component to render as a fallback, until our actual async component or page is being resolved.</p>
<p>Now, there are a couple of ways to do that.</p>
<p>One very simple way is to add a loading.tsx at the same level of your page.tsx:</p>
<pre><code class="language-javascript">src/
└── app/
    ├── layout.tsx
    ├── page.tsx
    │
    └── app/
        └── dashboard/
            ├── layout.tsx
            ├── loading.tsx // ---&gt; ADD THIS
            └── page.tsx
</code></pre>
<pre><code class="language-javascript">export default function Loading() {
  // You can add any UI inside Loading, including a Skeleton.
  return &lt;div className="p-3"&gt;Loading...&lt;/div&gt;
}
</code></pre>
<p>Adding the loading.tsx at the same level as page.tsx make the tree look like:</p>
<pre><code class="language-javascript">&lt;app/layout&gt; // -&gt; root layout
    &lt;html&gt;
        &lt;head&gt;
            .....
	    &lt;/head&gt;
        &lt;body&gt;
            &lt;app/dashboard/layout&gt; // --&gt; dashboard layout
                &lt;Sidebar&gt; // --&gt; sidebar
                &lt;main&gt;
                    &lt;app/dashboard/loading&gt; // --&gt; loading
                        &lt;app/dashbaord/page&gt; // --&gt; dashboard page
                    &lt;/ app/dashboard/loading&gt;
                &lt;/main&gt;
            &lt;/ app/dashboard/layout&gt;
        &lt;/body&gt;
    &lt;/html&gt;
&lt;/ app/layout&gt;
</code></pre>
<p>Now, as the next app renders down the component tree and it sees a component that needs to be resolved before it is rendered, then instead of blocking the page, it renders the loading component as a fallback until the page is resolved.</p>
<p>As soon as the tree reaches a component which needs time to resolve (server component), it looks up to render some fallback component in the same boundary...you just need to provide that static fallback component.</p>
<p>Under the hood, Next.js uses a Suspense boundary for <code>loading.tsx</code>. The important part for us is that it gives the route a fallback UI to show while the page is loading.</p>
<p>And that's it, now your users are not feeling the "lag" in your app as soon as they route to a page, which is or has a server compoent.</p>
<h2>Why Suspense over loading.tsx?</h2>
<p>So the main point is,<br />You need to tell next js to render the page statically as the fallback version until the server component is being ready or the children is being resolved.</p>
<p>Adding loading.tsx does this, tag does the same too.</p>
<p>But with , you can have more control over the component for which you want to render the static fallback version.</p>
<p>To be more precise, you have more control over the suspense boundary, and instead of showing the loading for the entire page, you can still have the static parts of your page in place and show loader or skeleton as fallback for the exact async component that takes time to get resolved.</p>
<p>With loading.tsx:</p>
<pre><code class="language-typescript">// app/dashboard/page.tsx

async function getStats() {
  const res = await fetch("https://api.example.com/stats", { cache: "no-store" });
  return res.json();
}

export default async function DashboardPage() {
  const stats = await getStats();

  return (
    &lt;main className="p-10"&gt;
      &lt;h1 className="text-2xl font-medium"&gt;Dashboard&lt;/h1&gt;
      &lt;p className="mt-1 text-sm text-neutral-500"&gt;
        Overview of your account
      &lt;/p&gt;
      &lt;p className="mt-8 text-4xl"&gt;{stats.users}&lt;/p&gt;
    &lt;/main&gt;
  );
}
</code></pre>
<pre><code class="language-typescript">// app/dashboard/loading.tsx

export default function Loading() {
  return (
    &lt;div className="flex min-h-screen items-center justify-center"&gt;
      &lt;div className="h-8 w-8 animate-spin rounded-full border-[3px] border-neutral-200 border-t-neutral-900" /&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>As you navigate to /dashboard, your screen looks like:</p>
<img src="https://cdn.hashnode.com/uploads/covers/689c31554cd27c69d8aefe44/6aeabb4c-b8a6-40ed-b68a-f30947b13e67.png" alt="" style="display:block;margin:0 auto" />

<p>And with Suspense:</p>
<pre><code class="language-typescript">// app/dashboard/page.tsx
import { Suspense } from "react";

async function Stats() {
  const res = await fetch("https://api.example.com/stats", { cache: "no-store" });
  const stats = await res.json();

  return (
    &lt;div className="rounded-lg border p-6"&gt;
      &lt;p className="text-4xl"&gt;{stats.users}&lt;/p&gt;
    &lt;/div&gt;
  );
}

export default function DashboardPage() {
  return (
    &lt;main className="p-10"&gt;
      &lt;h1 className="text-2xl font-medium"&gt;Dashboard&lt;/h1&gt;
      &lt;p className="mt-1 text-sm text-neutral-500"&gt;
          Overview of your account
      &lt;/p&gt;

      &lt;div className="mt-8"&gt;
        &lt;Suspense fallback={&lt;StatsSkeleton /&gt;}&gt; // --&gt; Suspense 
          &lt;Stats /&gt;
        &lt;/Suspense&gt;
      &lt;/div&gt;
    &lt;/main&gt;
  );
}

function StatsSkeleton() {
  return (
    &lt;div className="flex h-[118px] items-center justify-center rounded-lg border"&gt;
      &lt;div className="h-4 w-4 animate-spin rounded-full border-2 border-neutral-200 border-t-neutral-900" /&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>on instant navigation, your dashboard page looks something like:</p>
<img src="https://cdn.hashnode.com/uploads/covers/689c31554cd27c69d8aefe44/af9c4d5b-70fa-4888-8699-20fa1d793cda.png" alt="" style="display:block;margin:0 auto" />

<p>You can see the difference, with suspense, you can have more control that over what components exactly, you need to show the loaders or skeletons and just wrap those components with suspense fallback instead of the entire page which eventually has a much better UX.</p>
<p>Also one more thing that on adding both the loading.tsx and the tag can show the loading.tsx content for a few milliseconds and then the Suspense fallback component as the component gets resolved.</p>
<p>That's because, the next app first builds the shell, at that time the loading.tsx is the suspense boundary and once that's done, the tag takes over.</p>
<p>Nothing wrong with it, just be aware while using.</p>
]]></content:encoded></item></channel></rss>