When Next.js Is More Trouble Than It’s Worth

February 22, 20258 min read

When developing internal applications—such as dashboards, administrative panels, or company-specific tools—search engine optimization (SEO) is often a non-factor. These applications exist behind authentication barriers and serve a predefined group of users. Despite this, many teams opt for Next.js simply due to its popularity and reputation as a modern web framework. While Next.js excels in SEO-oriented, public-facing applications, it may introduce unnecessary complexity, cost, and maintenance challenges for applications that do not require server-side rendering (SSR) or static site generation (SSG).

This article critically examines whether Next.js is the optimal choice for client-side-only applications. Whether you are a mid-level developer weighing architectural trade-offs or a senior developer assessing long-term feasibility, this discussion will provide insights to help inform your decision. The observations herein are primarily based on experiences with the App Router in Next.js.

Deployment Costs: Paying for Unnecessary Infrastructure

Next.js is engineered to maximize the benefits of SSR, making it well-suited for SEO-driven, public-facing websites. However, for internal applications restricted to authenticated users, these advantages become moot. The high costs associated with server-side rendering can be difficult to justify when an internal tool is designed purely for client-side execution. Moreover, these costs are compounded by unnecessary complexity in deployment configurations and maintenance requirements.

Deploying a Next.js application on a platform like Vercel incurs costs associated with SSR, even when SEO is irrelevant. This is analogous to maintaining a gym membership solely for access to a treadmill that could be purchased for home use. If an internal tool is intended for a small user base, investing in SSR infrastructure is an avoidable expenditure. Beyond the financial costs, the additional complexity of setting up and maintaining SSR infrastructure creates overhead that smaller teams or non-technical stakeholders may struggle to manage effectively.

The computational resources required for SSR can also introduce performance bottlenecks that negatively impact user experience. If the majority of your application logic and UI rendering happens client-side, the inclusion of SSR may slow down build times and introduce caching inefficiencies that would not exist in a simpler client-rendered application. This means that teams may not only be paying extra for an unnecessary feature but may also inadvertently introduce technical hurdles that hinder their development velocity.

For organizations mindful of expenses, reducing infrastructure overhead is essential. When an internal application is utilized by a limited set of employees, an SSR-centric deployment model becomes financially inefficient. Instead, leveraging Create React App (CRA) or Vite enables applications to be served with minimal resource requirements, such as static hosting via AWS S3 or a JAMstack approach, both of which offer more cost-effective deployment options. Additionally, static file hosting eliminates the need for costly server environments and enables greater scalability without ongoing infrastructure investments.

Complexity and Cognitive Overhead

Next.js is inherently designed to accommodate both server-side and client-side rendering. While this dual capability benefits public websites, it introduces unnecessary complexity for purely client-rendered applications. Teams that only need client-side rendering must invest time into understanding and circumventing default Next.js behaviors that do not align with their needs. The result is additional boilerplate code, increased cognitive load, and a more challenging development experience.

Developers must account for both client-side and server-side execution, even when server-side processing is irrelevant. This requires additional boilerplate to disable SSR for client-only components, creating an additional layer of complexity. Moreover, default behaviors such as automatic prefetching and server execution of certain functions may result in unexpected side effects that developers must work around.

"use client";
import React from 'react'

const MostComponents = () => {
  useEffect(() => {
    someFunction() // This needs to run only in the browser, otherwise it will break the build
  }, [])
  return (
    <div>This Sucks!!!</div>
  )
}

export default MostComponents

For junior and mid-level developers still acclimating to React and Node.js, these intricacies can be a roadblock. Special attention is required when selecting dependencies, avoiding SSR-incompatible hooks, and ensuring proper delineation between client-side and server-side logic. Even though Next.js provides mechanisms to enforce client-side execution (e.g., "use client"), its pervasiveness throughout the application necessitates constant adjustments.

The complexity is further exacerbated when integrating third-party libraries. Some libraries assume a purely client-side environment and may break when executed in SSR mode, requiring additional effort to conditionally load them or refactor code to avoid server execution. These additional steps increase development time and create maintenance burdens for teams already managing tight deadlines.

Opinionated Framework with Hidden Abstractions

Next.js incorporates many built-in optimizations that facilitate public web applications but may introduce hidden behavior that complicates debugging and customization. It abstracts many functionalities, such as automatic code splitting, image optimization, and prefetching. While these features improve performance for public websites, they can be counterproductive in applications where such optimizations are unnecessary. Debugging issues stemming from opaque framework behaviors can be an arduous process.

During integration testing, I've faced issues due to Next.js's hidden magic. For example, msw, a tool for intercepting and mocking network requests, can behave unexpectedly because Next.js overrides the global fetch function and imposes additional caching logic. While such optimizations improve SSR performance, they can interfere with testing methodologies that rely on deterministic request handling.

import JSDOMEnvironment from "jest-environment-jsdom";
import { fetch } from "undici";

export default class FixJSDOMEnvironment extends JSDOMEnvironment {
  constructor(...args: ConstructorParameters<typeof JSDOMEnvironment>) {
    super(...args);
    this.global.fetch = fetch as any;
  }
}

Similarly, modifying page metadata dynamically within a client-side application may require workarounds. Next.js prioritizes SSR-first design, restricting metadata updates to the generateMetadata function. While this approach is logical for SEO-driven applications, it imposes unnecessary constraints on projects that require dynamic title management and doesn't care about SEO.

Unstable Framework

Next.js is a rapidly evolving framework, which can introduce instability and migration challenges. As the framework grows, it frequently undergoes significant changes that impact development workflows, forcing teams to continually adapt to new paradigms. While innovation is beneficial, rapid iteration can create friction for teams that require long-term stability and predictable updates.

Next.js aggressively integrates cutting-edge features, sometimes before they are fully stabilized. A prime example is its premature adoption of Server Components in Next.js 13, leading to numerous unforeseen issues and regressions. Developers who migrated early found themselves dealing with unexpected behavior, performance bottlenecks, and incomplete documentation. This level of volatility complicates long-term application maintenance, especially for teams prioritizing stability and predictability. For organizations that require strict version control and reliability, these frequent changes can be a major deterrent.

The pace of Next.js updates also presents challenges in keeping dependencies up to date. New releases often introduce breaking changes that require teams to refactor parts of their applications. This constant need for updates increases development overhead, particularly for organizations that rely on a stable, well-documented framework. Without a clear roadmap, teams may find it difficult to plan long-term development strategies. 🛠️

Ecosystem Lock-In

Next.js is tightly coupled with Vercel’s hosting platform. While Vercel provides seamless deployment, optimized performance, and advanced features, reliance on this ecosystem creates vendor lock-in. Many of Next.js’s most attractive features—such as edge functions, image optimization, and ISR (Incremental Static Regeneration)—are designed to work best within the Vercel environment, making it difficult to migrate to alternative hosting solutions.

Transitioning away from Vercel often requires significant refactoring due to deep framework integration. Teams that wish to host Next.js applications on AWS, DigitalOcean, or self-managed infrastructure must implement workarounds to replicate Vercel’s functionality. This migration process can be costly, time-consuming, and technically complex, particularly for larger applications with deep dependencies on Next.js-specific features.

Additionally, Next.js’s file-based routing structure may further entrench project dependencies. While file-based routing simplifies development for small to mid-sized applications, it can be restrictive for larger applications that require more flexible routing architectures. Over time, teams may find themselves constrained by Next.js conventions, making it difficult to restructure applications or migrate to different frameworks.

Alternative Frameworks and Build Tools

For projects where Next.js is overkill, several alternative solutions offer greater flexibility and simplicity:

  • Vite – A high-performance build tool optimized for client-side React applications, featuring fast module replacement and simplified deployments via static bundling. Vite is particularly well-suited for single-page applications that do not require SSR, providing a more streamlined and developer-friendly experience.
  • Remix – A progressive enhancement-oriented React framework with nested routing and optional SSR, allowing for greater control over rendering strategies. Remix is a good choice for teams that need some level of server-side functionality but prefer more explicit control over its implementation.
  • Webpack with React – While requiring manual configuration, Webpack affords full control over the build process, making it an ideal choice for teams seeking a customizable setup. This flexibility is valuable for enterprises with highly specific performance and optimization requirements.

These alternatives allow teams to avoid unnecessary SSR complexities while maintaining an efficient, client-focused development workflow. By selecting the appropriate tool based on project requirements, developers can optimize their workflow while reducing maintenance burdens.

Conclusion

Ultimately, selecting the appropriate tool hinges on aligning the framework’s capabilities with the project’s specific needs. While Next.js is an excellent choice for SEO-sensitive applications, internal applications benefit from simplicity, cost efficiency, and maintainability—attributes better supported by client-side-focused frameworks. Avoiding unnecessary SSR and infrastructure overhead can lead to a more streamlined development process and better long-term maintainability.