Server Components in Practice: A Founder Engineer Story
React Server Components (RSC) are a component model introduced in React 18 and adopted by Next.js 13+ via the App Router that allows components to run on the server, access server-side resources (databases, file system, environment variables) directly, and render HTML without sending the component code to the browser. Server components coexist with client components (which run in the browser and handle interactivity) in the same component tree. The distinction fundamentally changes how data fetching is structured in React applications.
Written by Yashveer Singh, founder of Yashveer Labs.
What you need to know
- Server components run on the server, access databases and secrets directly, and render HTML without client-side JavaScript. Client components handle interactivity.
- Data fetching moves from centralized page-level functions (getServerSideProps) to per-component fetches colocated with the component that needs the data.
- The server/client component distinction must be maintained explicitly. Passing non-serializable objects (functions, class instances) across the boundary produces runtime errors.
- Third-party library compatibility with server components is improving but still inconsistent. Check before adopting libraries in server component-heavy applications.
- For new Next.js applications, start with the App Router. The learning curve is front-loaded but the model is where React is heading.
The core argument
Server components represent the biggest mental model shift in React development since hooks. The premise is compelling: components that do not need to run in the browser should not run there. Moving data fetching to the component level on the server eliminates the API layer for read operations, reduces the client-side JavaScript bundle, and enables streaming HTML to the browser rather than waiting for the full page to render.
What the model looks like in practice is different from what the documentation suggests. The first time building a dashboard in the App Router, the instinct is to reach for API routes and client-side fetching for everything, because that is the mental model the pages router established. Unlearning that instinct and trusting that a server component can just call db.query(...) directly takes time. Once the trust is established, the simplicity is real: a list page that reads from a database is just a component and a query, with no API route in between.
The rough edges are real too. I built a portion of Nexli's dashboard using the App Router during its early adoption period and encountered two classes of problems repeatedly: third-party UI libraries that used browser APIs at module scope (fixing by lazy loading them within client components), and caching behavior that was not intuitive (queries that should have been fresh were cached, and queries that should have been cached were fetching on every request). Both problems are solvable, but they require understanding the caching model in depth before the behavior makes sense.
Common mistakes
- Converting every component to a server component without understanding the boundary. Not every component benefits from being a server component. Components that use local state, event handlers, or browser APIs must be client components. The correct approach is to make components server components by default and opt into client components when interactivity or browser APIs are required, not to make every component a server component reflexively.
- Creating data fetching waterfalls with sequential server component fetches. A server component that fetches data and then renders child server components that each fetch their own data can create sequential database queries: parent fetches first, then each child fetches. In many cases, parallel fetching with Promise.all or restructuring the component hierarchy to colocate related data is more efficient. Next.js automatically deduplicates fetch requests using the Request API, but sequential queries within a component tree are not deduplicated.
- Not using Suspense boundaries for loading states. Server components that fetch data suspend while loading, and without explicit Suspense boundaries, the entire page waits for the slowest data fetch. Wrap independent sections of the page in Suspense with appropriate loading fallbacks. This enables streaming HTML to the browser, where sections load progressively rather than waiting for all data.
- Importing server-only libraries in client components. Client components are bundled for the browser. Importing a library that uses Node.js APIs (database clients, file system tools, server-only SDKs) in a client component produces a build error or a runtime error. Use the
server-onlypackage to throw a build-time error if a server module is accidentally imported in a client component.
- Recreating the pages router pattern with a layout file that fetches all the data. The App Router's layout files run on every navigation within the layout's segment. Fetching large amounts of data in a layout (to pass to children) replicates the centralized data fetching pattern of getServerSideProps without the performance benefits of component-level fetching. Colocate data fetching with the components that need it rather than centralizing it in layouts.
Where to start
- Build a single read-only page using server components and direct database access. Pick a list page (a table of records) and implement it as a server component that queries the database directly without an API route. Verify it renders correctly, implement pagination, and add a loading state with Suspense. This exercise establishes the basic server component pattern and uncovers the first round of mental model adjustments.
- Identify the existing API routes that only serve a single page component. In the current application, find API routes that are only called from one specific page and only to load data for that page. These are the best candidates for migration to server component fetching, because they have no external callers and the migration produces a net reduction in code.
- Read the Next.js caching documentation before the first cache-related bug. The App Router has a four-layer caching model that governs whether requests, segments, and data fetches are cached. Understanding this model before encountering cache-related bugs saves significant debugging time. The key concept: fetch requests in server components are cached by default; understanding how to opt out of caching (the force-no-store option) and how cache invalidation works (revalidatePath, revalidateTag) is necessary for any application with mutable data.
Related reading
Frequently asked
The reason my name is on this page
My name is on this page because I wrote what is on this page. Yashveer Singh. Full stack developer. Founder of Yashveer Labs. The portfolio is on the homepage. The projects are live. The code is real. The work is provable. If you have read this far, you already know whether the voice matches the standard you are looking for. The next move is yours.
Posts that line up with this one.
- Web App and Frontend Development
WCAG for Founders: A Practical Subset
WCAG has hundreds of pages and most founders do not need most of them. There is a small subset that catches 90 percent of real accessibility problems, and that subset is what your team should ship by default.
- Web App and Frontend Development
Loading States, Skeletons, and Optimistic UI
How you handle loading states is one of the most visible indicators of product quality. Here is the decision framework for when to use spinners, skeletons, and optimistic updates, and the common mistakes that make apps feel slow.
- Web App and Frontend Development
Modal Patterns That Do Not Trap Users
Modals are overused, frequently misimplemented, and a common source of user frustration. Here is how to design and build modals that provide the right information at the right time without trapping users or creating accessibility failures.
- Web App and Frontend Development
Next.js vs Remix vs Astro vs Nuxt in 2026
Next.js, Remix, Astro, and Nuxt each make different architectural bets about how web applications should work. Here is how they compare in 2026 and which one belongs in which project.