Every startup I've worked with as a frontend engineer has made a version of the same mistake. It's not a bug. It's not a bad developer. It's a structural decision made too early — and it compounds silently until deployments take hours and every new feature feels like defusing a bomb.
The Mistake: Client-Side Everything
It usually starts innocently. A junior dev (or a senior dev in a hurry) reaches for useEffect + fetch to load data. It works. It ships. Then it gets copy-pasted twelve more times across the codebase.
Six months later:
- Page loads show spinners for 3 seconds on every route
- SEO is non-existent because content is rendered client-side
- The API gets hammered because every component fetches its own data independently
What Should Have Happened
Next.js App Router gives you Server Components by default. That's not an accident — it's the architecture telling you something.
// ❌ What most teams do
"use client";
export function Dashboard() {
const [data, setData] = useState(null);
useEffect(() => {
fetch("/api/metrics")
.then((r) => r.json())
.then(setData);
}, []);
return <MetricsChart data={data} />;
}
// ✅ What they should do
// app/dashboard/page.tsx — Server Component by default
export default async function Dashboard() {
const data = await db.query.metrics.findMany();
return <MetricsChart data={data} />;
}
The second version: no loading state, no client bundle weight, no waterfall, full SEO, zero API round-trip.
The Real Cost
The client-side-everything pattern doesn't just slow down pages. It creates a dependency on useEffect chains that are nearly impossible to test, reason about, or optimize later.
By the time a startup hires someone like me to fix it, the refactor takes weeks — not hours. Every component has to be audited. Every data fetch has to be re-evaluated.
How to Avoid It
Three rules I give every team at project kickoff:
- Default to Server Components. Only add
'use client'when you need interactivity (event handlers, browser APIs, local state). - Fetch data as close to where it's used as possible — but on the server.
- Never fetch in a
useEffectunless the data depends on user interaction after page load.
These three rules alone would have saved every startup I've audited from their worst performance problems.
The Broader Pattern
This isn't really about Next.js. It's about reaching for familiar patterns instead of understanding the constraints of the tool you're using.
React Server Components are a paradigm shift. They require unlearning the SPA mental model. Most teams don't take the time to do that — they just keep building SPAs inside a framework designed for something different.
The teams that do take the time? Their apps are faster, cheaper to run, and genuinely easier to maintain.
That's the architecture that outlives your roadmap.