Skip to content
ExponaBLOG
REACT & NEXT.JS · DAY 001 / 100

Why React and Next.js are still my frontend default

After building e-commerce, trading and CRM frontends, this is why I keep choosing them.

Expona Admin7 min read

Five years and several production platforms later, I still reach for React and Next.js first when planning a frontend architecture. That might sound predictable—but it's not nostalgia or hype, it's deliberate. Why React and Next.js are still my frontend default comes down to three things I've seen fail in other stacks: ecosystem sprawl, friction between frontend and backend concerns, and the cost of shipping across web and mobile simultaneously.

The largest ecosystem and hiring pool

When I started building the e-commerce platform in 2019, hiring was hard. We needed people fast. React's ecosystem—the sheer number of battle-tested libraries for state management, form handling, data fetching, and UI components—meant we could onboard mid-level developers and have them productive within two weeks, not two months.

Compare this to smaller ecosystems. If you choose a niche framework, you're not just betting on the technology. You're betting that your next three hires will learn it, that Stack Overflow will have answers, that the library you need exists and is maintained. I've watched teams get stuck waiting for a package to be updated or rebuilding something from scratch because the ecosystem didn't have it.

React's gravity is real. The CRM platform we shipped in 2022 had the luxury of a talent pool deep enough that we could be selective. We found people who'd built dashboards, real-time feeds, and complex form workflows. They brought patterns and instincts we didn't have to teach from first principles.

The libraries that matter are mature:

  • TanStack Query (formerly React Query) handles server state elegantly without the boilerplate of Redux.
  • React Hook Form makes form handling feel simple again after years of fighting Formik overhead.
  • shadcn/ui and similar component systems give you a starting point that's both opinionated and copyable (not locked behind a component library).
  • Zustand or Jotai for client state—lightweight enough that you're not dragging in Redux complexity for 80% of apps.

This ecosystem density matters more when you're shipping fast. The trading platform had real-time price updates, order management, and charting. We didn't build those from scratch. We found react-financial-charts, integrated TanStack Query for polling, and spent our engineering time on business logic, not reinventing wheels.

Server Components blur frontend and backend nicely

Next.js 13+ introduced Server Components. This sounds like buzzword territory, but it genuinely changed how I think about the frontend-backend split.

Before Server Components, every piece of data flow was the same: fetch from the backend, render on the client, manage loading and error states. You ended up with a lot of boilerplate waterfalls where you fetch user, then fetch user's orders, then fetch order details. The network round-trips stacked up.

Server Components let you write code that runs on the server but reads like frontend code:

tsx
// app/orders/page.tsx - Server Component by default in Next.js 13+
import { getOrders } from '@/lib/db';
import { OrderCard } from '@/components/OrderCard';

export default async function OrdersPage() {
  const orders = await getOrders(); // No API call, direct DB access
  
  return (
    <div>
      {orders.map(order => (
        <OrderCard key={order.id} order={order} />
      ))}
    </div>
  );
}

No loading state wiring. No client-side data fetching. No "why is my page blank for 2 seconds" problem. The data is there when the page renders because it was fetched server-side, during rendering, before HTML is sent to the browser.

On the CRM project, this pattern eliminated entire classes of bugs. We used to have race conditions where users navigated away mid-fetch, or cache invalidation logic that was more complex than the feature itself. Server Components made those problems disappear because the fetching happened before the component even existed in the browser.

The blurred line between "frontend" and "backend" is actually an advantage here. The person writing the component has direct access to the database, middleware, and business logic. No API design overhead. No versioning headaches. The server and client are the same codebase.

You still use client-side data fetching when you need it—forms, real-time updates, client state. That's where 'use client' comes in:

tsx
// components/OrderForm.tsx
'use client';

import { useState } from 'react';
import { useMutation } from '@tanstack/react-query';
import { submitOrder } from '@/lib/actions';

export function OrderForm() {
  const [status, setStatus] = useState('idle');
  const mutation = useMutation({
    mutationFn: submitOrder,
    onSuccess: () => setStatus('success'),
  });

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      mutation.mutate();
    }}>
      <button disabled={mutation.isPending}>
        {mutation.isPending ? 'Submitting...' : 'Submit'}</button>
      {status === 'success' && <p>Order placed</p>}
    </form>
  );
}

This hybrid model—Server Components for data loading, client components for interactivity—maps cleanly to how modern web apps actually work. You're not fighting the framework. You're moving data closer to where it's used.

Next.js handles routing, rendering and SEO in one place

I've used enough routers to know the overhead. Next.js App Router handles routing with the file system, which sounds simplistic until you've had to manually configure 150 routes in a SPA framework.

The benefit multiplies when SEO matters. The e-commerce platform had hundreds of product pages, category filters, search results. Each one needed correct metadata—title, description, canonical URLs. With a standard React SPA, you're manually managing this with a library like React Helmet, and you're still not getting server-rendered HTML until you add another layer.

Next.js generates metadata programmatically:

tsx
// app/products/[id]/page.tsx
import { getProduct } from '@/lib/db';

export async function generateMetadata({ params }) {
  const product = await getProduct(params.id);
  
  return {
    title: product.name,
    description: product.description,
    openGraph: {
      images: [product.imageUrl],
    },
  };
}

export default function ProductPage({ params }) {
  // Render the product
}

The HTML that lands in the user's browser and gets crawled by search engines is fully rendered with the correct title, description, and Open Graph tags. No JavaScript parsing required. This matters for e-commerce—product visibility is tied to search ranking, and search engines reward fast, pre-rendered pages.

Rendering modes are simple to choose:

  • Static generation (default): page is built at deploy time, served from the edge. Best for product pages, blog posts, anything that doesn't change per request.
  • Incremental Static Regeneration (ISR): page is regenerated in the background after X seconds. Product listing page? Generate it every 60 seconds so new items appear without a full redeploy.
  • Dynamic rendering: page is rendered on every request. Use this when content is user-specific or time-sensitive.

The ride-hailing platform needed dynamic rendering for driver location and estimated arrival times. The trading platform mixed static product pages with dynamic market data. Next.js didn't require rearchitecting—you pick the strategy per route.

Routing and rendering together mean no separate build step for SEO, no client-side route prefetching complexity, no "which pages are actually static" confusion. It's one decision: what should this page render at?

React Native lets the same team ship mobile

This is the multiplier effect that doesn't get enough credit. React Native means a single engineer can ship features across web and mobile. Not "write it once, deploy twice"—that's a myth. But shared business logic, shared patterns, and no context-switching between JavaScript and Swift is real.

The travel platform needed both web and mobile quickly. We used Next.js for the web platform and React Native for iOS and Android. The same person who optimized the search filter component on web could implement it on mobile because the data fetching and business logic were TypeScript and React.

Code reuse isn't always seamless—UI layers are different, and platform constraints matter. But sharing feels natural:

  • API client code is identical. TanStack Query works the same way on both.
  • Form validation logic is shared TypeScript, not reimplemented in Kotlin or Swift.
  • State management patterns (Zustand, context) are the same across platforms.
  • Engineers can pair program effectively because everyone's mental model aligns.

When you hire React developers, they can own features end-to-end: backend API, Next.js frontend, React Native app. That's leverage. The SaaS platform we're shipping now is staffed by three people handling web and mobile. That would require five people if we'd split the stacks.

Common mistakes

Overcomplicating state management: I see teams reach for Redux when they need TanStack Query. Redux is for client state (UI toggles, filters). Server state (API data) belongs in a specialized library. Keep them separate.

Server Components as a replacement for API routes: Server Components handle rendering. API routes handle business logic that needs to be called from client components. You need both. Server Components don't eliminate the need for /api routes; they change when you use them.

Static generation for everything: Not every page should be ISG. If content changes per user or per request, use dynamic rendering. Static generation is for content that's truly shared. Premature static generation creates stale content bugs.

React Native as "write once, run anywhere": It's not. You'll write platform-specific code. The win is that your team's mental model is aligned, not that code is 100% shared. Budget for platform-specific work.

Key takeaways

  • React's ecosystem and hiring pool mean you can scale a team without waiting for niche libraries or expertise to materialize.
  • Server Components in Next.js eliminate the frontend-backend waterfalls that plague SPA architecture. Data fetching becomes local to where it's used.
  • Next.js handles routing, rendering modes, and SEO in one framework. You're not gluing together separate tools; you're making one choice per route.
  • React Native keeps mobile and web engineering aligned. Shared business logic and patterns matter more than code-sharing percentage.
  • This combination isn't exciting. It's reliable. After five platforms and hundreds of thousands of lines of code, reliability is the feature.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles