DEV Community

Savage Solutions
Savage Solutions

Posted on

Why We Chose Next.js Over Everything Else for Client Projects

Why We Chose Next.js Over Everything Else for Client Projects

When we started working with clients at scale, I realized we needed to make a decisive choice about our frontend framework. We'd dabbled with Create React App, experimented with Gatsby, and even considered Vue.js with Nuxt. But after shipping dozens of projects across various industries—from e-commerce platforms to SaaS dashboards—Next.js consistently delivered the best developer experience, fastest time-to-market, and most maintainable codebases.

This wasn't a theoretical decision. It came from real production battles, client demands, and the hard truth that framework choices compound over time.

The Problem We Were Solving

Before committing to Next.js, we had three persistent pain points:

  1. Build complexity: Setting up webpack, babel, and code splitting across projects wasted hours
  2. API friction: Our frontend and backend teams worked in separate repos, creating deployment coordination nightmares
  3. Performance regression: Client projects often shipped with poor Core Web Vitals, requiring expensive optimization sprints

We needed something that handled these out-of-the-box rather than requiring custom configuration for every project.

Why Next.js Won (The Real Reasons)

1. Full-Stack Development in One Framework

This is the game-changer. With Next.js API routes, we can build the entire application—frontend and API—in a single repository:

// pages/api/users/[id].ts
import { NextApiRequest, NextApiResponse } from 'next';
import { prisma } from '@/lib/prisma';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method === 'GET') {
    const user = await prisma.user.findUnique({
      where: { id: req.query.id as string }
    });
    return res.status(200).json(user);
  }

  if (req.method === 'PUT') {
    const updated = await prisma.user.update({
      where: { id: req.query.id as string },
      data: req.body
    });
    return res.status(200).json(updated);
  }

  res.status(405).end();
}
Enter fullscreen mode Exit fullscreen mode

For clients, this means:

  • Single deployment pipeline
  • Unified error handling and logging
  • Shared TypeScript types between client and server
  • No network latency between frontend and "backend"

2. File-Based Routing That Actually Makes Sense

Coming from manual route configuration, Next.js's file-based router is refreshingly intuitive. Your folder structure is your routing structure:

pages/
├── index.ts                 → /
├── products.ts              → /products
├── products/[id].ts         → /products/:id
├── api/
│   ├── products.ts          → /api/products
│   └── products/[id].ts     → /api/products/:id
Enter fullscreen mode Exit fullscreen mode

No more maintaining separate routing configs. When you add a page, the route exists. This speeds up development by 30-40% in our experience, especially for CRUD-heavy dashboards.

3. Image Optimization Built-In

Before Next.js Image component, optimizing images across clients meant dealing with:

  • Manual compression scripts
  • Responsive image markup
  • Format conversion to WebP
  • Lazy loading implementation

Now? One component:

import Image from 'next/image';

export default function ProductCard({ product }) {
  return (
    <Image
      src={product.imageUrl}
      alt={product.name}
      width={500}
      height={500}
      priority={false}
      sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
      onLoadingComplete={(result) => {
        if (result.naturalWidth === 0) {
          // Broken image, handle it
        }
      }}
    />
  );
}
Enter fullscreen mode Exit fullscreen mode

This single change improved Core Web Vitals across all our client projects by an average of 25 points. Clients love seeing LCP improvements without extra work.

4. Static Site Generation + ISR = Performance Win

For content-heavy sites, Next.js's Static Site Generation (SSG) with Incremental Static Regeneration (ISR) is game-changing:

// pages/blog/[slug].tsx
export default function BlogPost({ post }) {
  return <article>{post.content}</article>;
}

export async function getStaticProps({ params }) {
  const post = await fetchPostFromCMS(params.slug);

  return {
    props: { post },
    revalidate: 3600 // ISR: regenerate every hour
  };
}

export async function getStaticPaths() {
  const posts = await fetchAllPosts();

  return {
    paths: posts.map(p => ({ params: { slug: p.slug } })),
    fallback: 'blocking' // Generate new posts on-demand
  };
}
Enter fullscreen mode Exit fullscreen mode

A client's blog went from 2-second page loads to 200ms after switching to ISR. The hosting bill dropped by 60%.

5. Middleware for Complex Logic

Next.js 12+ middleware lets us handle authentication, redirects, and request modification at the edge:

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';

export function middleware(request: NextRequest) {
  const token = request.cookies.get('auth_token');

  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  if (request.nextUrl.locale === 'fr') {
    const requestHeaders = new Headers(request.headers);
    requestHeaders.set('x-locale', 'fr');
    return NextResponse.next({
      request: {
        headers: requestHeaders,
      },
    });
  }
}

export const config = {
  matcher: ['/((?!_next|static|favicon.ico).*)'],
};
Enter fullscreen mode Exit fullscreen mode

This eliminates the need for separate auth services or middleware layers for many projects.

What About the Drawbacks?

I'd be dishonest if I didn't mention where Next.js requires workarounds:

Cold starts on serverless: If you're deploying to AWS Lambda, initial requests can be slow. We solved this by using Vercel (which has optimized Next.js cold starts) or keeping functions warm.

Large bundle sizes: Next.js apps can bloat quickly without discipline. We enforce code splitting and dynamic imports:

import dynamic from 'next/dynamic';

const HeavyChart = dynamic(() => import('@/components/Chart'), {
  loading: () => <p>Loading chart...</p>,
  ssr: false // Only load on client
});
Enter fullscreen mode Exit fullscreen mode

Learning curve for teams: Developers from non-React backgrounds struggle initially. We mitigate this with good documentation and pairing new hires with experienced team members.

The Business Impact

Since standardizing on Next.js, we've seen measurable improvements:

  • 30% faster project delivery: Boilerplate is minimal; developers jump into business logic immediately
  • 25% fewer production bugs: Shared types between frontend/API catch issues early
  • Lower hosting costs: Static generation and efficient server usage reduces infrastructure expenses
  • Easier client handoffs: Monorepo structure makes maintenance straightforward for clients taking projects in-house

Our Standard Stack

For context on how this fits together, here's what we typically ship:

  • Next.js: Framework and API layer
  • TypeScript: Type safety across the stack
  • Tailwind CSS: Styling without context switching
  • Prisma: Type-safe database ORM
  • Vercel: Hosting and deployment (optimized for Next.js)
  • Testing: Jest + React Testing Library

This combination covers 95% of client requirements without over-engineering.

When Next.js Isn't the Answer

To be clear: Next.js isn't universal. Use something else if you need:

  • Static-only sites (consider plain Next.js export or Hugo)
  • Real-time collaborative features (consider Remix with WebSockets)
  • Heavy desktop app-like UX (consider Electron + React)
  • Extreme performance constraints (consider plain HTML/Vanilla JS)

But for the majority of modern web applications—especially business-focused software—Next.js delivers the best value.

Key Takeaways

  • Full-stack capability eliminates coordination overhead and deployment complexity
  • File-based routing and built-in optimizations accelerate development without sacrificing performance
  • Image optimization and ISR deliver Core Web Vitals improvements automatically
  • Mature ecosystem means community answers exist for nearly every problem
  • Framework choice matters more than technology zealots admit—pick what scales with your team and clients

At Savage Digital Solutions (savagesolutions.io), we've built dozens of applications with this stack, and it consistently delivers the best balance of developer experience, client satisfaction, and long-term maintainability.

The framework wars are overblown. Pick one that handles your 95% case cleanly, then move on to solving real problems for your users.


I'm the founder of Savage Digital Solutions (savagesolutions.io), where we build full-stack web applications for growing companies. We've shipped everything from marketplace platforms to analytics dashboards—all powered by Next.js.

Top comments (0)