Introduction
If you have been building applications using Vite, you are likely used to a specific workflow: write React components, bundle them with esbuild/Rollup, and serve a single HTML file that fetches a large JavaScript bundle. In this world, everything is a "Client Component."
However, as the React ecosystem shifts toward the App Router and React Server Components (RSC), the architecture is fundamentally changing. For developers moving from a Vite-centric mindset to a Next.js framework, the biggest hurdle isn't the syntax—it's the mental model.
In this guide, we will break down the core differences between Server and Client components and how to adapt your Vite-based habits to this new reality.
The Vite World: Single-Page Application (SPA) Default
In a standard Vite + React project, your entire application lifecycle happens in the browser.
- The browser requests the page.
- The server sends a nearly empty
index.html. - The browser downloads the JS bundle.
- React hydrates the app, fetches data from an API via
useEffect, and renders the UI.
While this is excellent for developer experience (DX) and highly interactive dashboards, it often leads to "Layout Shift" and slower "Time to Interactive" for content-heavy pages because the client has to do all the heavy lifting.
The Shift: Thinking in "Environment Splits"
With React Server Components, the paradigm shifts from "Everything happens on the client" to "Compute where it makes sense."
1. What are Server Components?
By default, in the Next.js App Router, every component is a Server Component. These components execute only on the server. They never send their code to the client-side bundle. This allows you to:
Access backend resources directly: You can query your database or file system inside the component.
Keep secrets safe: API keys and sensitive logic stay on the server.
Reduce bundle size: Large dependencies (like a markdown parser or date library) stay on the server and only the resulting HTML is sent to the user.
2. What are Client Components?
Client Components are what you are used to in Vite. They are marked with the 'use client'; directive at the top of the file. These are necessary when you need:
Interactivity: UsingonClick,onChange, etc.
State and Effects: UsinguseState,useReducer, oruseEffect.
Browser APIs: Accessingwindow,document, orlocalStorage.
Data Fetching: Bye-bye, useEffect
One of the most drastic changes for a Vite developer is how data is fetched. In Vite, you likely do this:
// Vite patterns
function UserProfile() {
const [user, setUser] = useState(null);
useEffect(() => {
fetch('/api/user').then(res => res.json()).then(data => setUser(data));
}, []);
if (!user) return <Loading />;
return <div>{user.name}</div>;
}
In the Server Component world, this becomes an async function. Since it runs on the server, you can fetch data directly without an intermediate API route or complex loading states in the client code:
// Next.js Server Component pattern
async function UserProfile() {
const user = await db.user.findUnique({ where: { id: 1 } });
return <div>{user.name}</div>;
}
The Composition Pattern
A common mistake when migrating is putting 'use client'; at the very top of your layout, effectively turning your whole app back into a Vite-style SPA. To truly benefit from RSC, you must follow the "Lifting Movement"—keep your data fetching in Server Components and push interactivity to the leaves of your component tree.
If you find the architectural shift of moving your existing Vite codebase into this structure daunting, tools like for migrating your existing Vite apps to the App Router automatically.
SOCIAL SHARE CARD GENERATOR