Pourquoi je n'aime pas Next.js

Posté le 14 mai 2025 - Motion - Par Grafikart - Proposer une correction

Depuis quelque temps, je reçois régulièrement des messages me demandant de proposer du contenu ou des formations sur Next.js. De mon côté, je ne suis vraiment pas convaincu par Next.js et je suis plutôt dubitatif vis à vis de la hype autour de cette technologie.

Aussi, pour répondre une bonne fois pour toutes à ces demandes je vous propose d'expliquer les raisons qui font que, personnelement, je n'aime pas Next.js.

Je précise tout de même que ce que je vais exprimer ici est un avis personnel. Si vous utilisez Next.js avec succès, continuez ! Ce n'est pas parce que je n'apprécie pas cette technologie qu'elle est forcément mauvaise. Il s'agit simplement de ma perspective en tant que développeur.

sommaire

00:00 Introduction
01:23 Les données dupliquées
04:37 Le routing basé sur des fichiers
07:32 Les composant serveur
10:15 La nécessité d'utiliser des librairies
11:54 Fonctionnement opaque
13:26 React devient Next
14:34 Conclusion

Qu'est-ce que Next.js ?

Avant de commencer il est bon de définit ensemble ce qu'est Next.js. Dans la documentation il se définit de la manière suivante :

Next.js is a React framework for building full-stack web applications. You use React Components to build user interfaces, and Next.js for additional features and optimizations.

It also automatically configures lower-level tools like bundlers and compilers. You can instead focus on building your product and shipping quickly.

Il apporte principalement deux éléments intéressants par rapport à un projet React monté à la main :

  1. Une structure prédéfinie avec un routeur intégré
  2. La possibilité de faire un premier rendu HTML à partir du code React (avec différents modes de rendu)

C'est surtout ce second point qui justifie le succès de Next.js car il permet de lutter pallier aux problèmes de référencement et de chargement dû à du code front-end. Mais c'est aussi ce second point qui, de mon point de vu, est un véritable problème.

Les données dupliquées

Le premier problème majeur que je constate avec Next.js concerne le processus de réconciliation qui nécessite la duplication des données dans le document HTML.

Lorsque vous chargez une page avec Next.js, le framework génère d'abord une version HTML de la page côté serveur. Ensuite, pour que la navigation se poursuive côté client, il doit attacher des comportements JavaScript sur les éléments. Pour ce faire, il effectue une phase de réconciliation où il rend à nouveau les mêmes composants côté client.

Cette réconciliation nécessite que les données utilisées pour le rendu serveur soient également transmises au client. Concrètement, cela signifie que les données sont présentes en double :

  • Une première fois dans le HTML généré
  • Une seconde fois dans du code JavaScript en bas de page

Si vous examinez le code source renvoyé par un site fait avec NextJS, vous trouverez en bas de la page d'énormes objets JSON contenant l'ensemble des données.

Cette duplication pose deux problèmes majeurs :

  1. Taille et performance : Pour améliorer les navigations consécutives, on dégrade l'expérience du premier chargement avec un fichier HTML plus lourd (même avec la compression, le poids reste supérieur à celui d'une page HTML classique).

  2. Consommation de ressources : Le navigateur doit d'abord rendre la page HTML reçue, appliquer le CSS, puis exécuter tout le JavaScript qui va recomposer la même page. Sur des périphériques modestes, cette double opération consomme davantage de ressources et peut ralentir l'expérience.

Le routing basé sur des fichiers

Le second problème concerne l'approche du "file-based routing" adoptée par Next.js.

Dans ce système, la structure des dossiers et fichiers détermine les URLs de votre application. Par exemple, pour créer une section blog, vous devez créer un dossier blog contenant un fichier Page.tsx. Pour une page d'article avec un ID dynamique, vous créez un dossier [id] à l'intérieur du dossier blog, et ainsi de suite...

Cette approche présente plusieurs inconvénients :

  1. Des conventions parfois contre-intuitives : Certains chemins peuvent poser des problèmes (comme par exemple /article/[slug]-[id]).

  2. Manque de flexibilité : Contrairement à une approche déclarative comme celle de Laravel où vous pouvez préciser des contraintes et des formats d'URL complexes, le file-based routing est beaucoup plus rigide. Il n'est pas possible non plus de générer des URL dynamiquement.

  3. Problèmes pratiques : Si vous souhaitez changer une URL, vous devez déplacer des fichiers, ce qui rend le versioning moins lisible. De plus, tous les fichiers s'appellent "page.tsx", ce qui complique la recherche et la navigation dans le projet.

De mon point de vue, cette approche constitue un retour en arrière et me rappelle les débuts du PHP, où chaque URL correspondait à un fichier (contact.php, blog.php, etc.). Pratique que l'on a quitté au profit de système plus pratiques et il est étrange de voir l'écosystème JavaScript aller dans le sens inverse.

Les composants serveur

Le troisième problème concerne les conventions particulières de Next.js, avec notamment avec les "server components".

Par défaut dans Next.js, vos composants sont générés côté serveur. Ces composants présentent une syntaxe inhabituelle pour un développeur React : des fonctions asynchrones, des appels await directement dans le corps du composant...

// Premier exemple de la documentation Next.js
export default async function Page() {
  const data = await fetch("https://api.vercel.app/blog");
  const posts = await data.json();
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

Dans les faits la syntaxe ressemble à un composant React mais fonctionne de manière complètement différente :

  • La fonction est asynchrone, et son code ne sera jamais rerendu.
  • Il n'est pas possible d'utiliser de hook

Ces fonctions agissent plutôt comme les "actions" qui vont générer le VirtualDOM côté serveur pour le passer ensuite un composant statique au client. Pour ajouter de l'interactivité, il faut créer un composant séparé avec la directive "use client" en tête de fichier.

"use client";
import { use } from "react";

export default function Posts({
  posts,
}: {
  posts: Promise<{ id: string; title: string }[]>;
}) {
  const allPosts = use(posts);

  return (
    <ul>
      {allPosts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

Cette distinction entre composants serveur et client peut être source de confusion. Le risque est de mélanger code serveur et client, avec des conséquences potentiellement graves comme l'exposition de variables d'environnement contenant des informations sensibles.

La nécessité d'utiliser des librairies

Next.js se présente comme un framework pour la créations d'applications full-stack, mais à mon sens, il ne résout qu'une infime partie du problème.

Il offre un routeur et différentes méthodes de rendu, mais ne fournit aucun des éléments habituellement nécessaires pour créer une application complète :

  • Authentification ? Il faut trouver une librairie.
  • Communication avec une base de données ? Il faut trouver une librairie.
  • Validation des données ? Encore une librairie.

Si on compare avec de véritables frameworks full-stack comme Laravel, Symfony, Ruby on Rails ou même Adonis.js, ces solutions offrent l'intégralité des composants nécessaires : ORM, système de validation, authentification... Elles permettent de se concentrer sur l'essentiel : créer votre application.

Avec Next.js, vous passerez un temps considérable à assembler différentes librairies, avec des documentations séparées, avant même de commencer à développer votre application. C'est loin d'être idéal en termes de productivité.

Fonctionnement opaque

Un autre problème majeur est l'opacité du fonctionnement de Next.js. La plupart des opérations se déroulent à la compilation, cachées derrière des commandes comme next dev ou next build.

Quand vous construisez votre projet avec next build, vous obtenez un dossier .next rempli de fichiers dont la fonction n'est pas claire. Il n'y a pas de point d'entrée évident, et vous ne pouvez pas simplement utiliser Node.js pour démarrer votre serveur - vous êtes obligé d'utiliser next start.

Cette opacité rend le débogage et les optimisations difficiles. Si vous rencontrez un problème ou souhaitez comprendre ce qui se passe réellement, vous vous heurtez à un code minifié et à une architecture complexe sur laquelle vous n'avez aucun contrôle.

React devient Next

Mon dernier point est plus d'ordre "politique" : j'ai l'impression que Next.js est en train d'avaler React, au point que les deux deviennent presque inséparables.

On le constate à deux niveaux :

  1. Développement de fonctionnalités : Les récentes évolutions de React semblent orientées vers l'utilisation avec Next.js. Par exemple, les "server components" intégrés à React ne profitent principalement qu'à Next.js. À mon sens, ce concept aurait dû être développé dans une librairie séparée.

  2. Documentation officielle : Quand vous consultez la documentation pour démarrer avec React, les premiers conseils suggèrent d'utiliser Next.js. Pour quelqu'un qui souhaite simplement tester React, ce n'est pas forcément la meilleure approche. Une solution comme Vite serait plus adaptée pour comprendre les bases sans se confronter aux conventions spécifiques de Next.js.

Conclusion

Mon avis sur Next.js est peut-être influencé par mon parcours de développeur. Ayant commencé par le backend, je trouve plus naturel que le serveur génère l'HTML et qu'on ajoute du JavaScript pour rendre certains éléments interactifs. Pour les applications très dynamiques, je préfère une approche entièrement côté client (le premier rendu HTML n'apportera rien).

Peut être qu'un développeur ayant débuté par le frontend pourrait trouver Next.js plus intuitif que des solutions comme Laravel ou Ruby on Rails. C'est une question de perspective.

Personnellement, j'ai l'impression d'un retour en arrière. Les server components me rappellent le PHP des débuts, où logique et HTML étaient mélangés. Le développement web a évolué vers une meilleure séparation des responsabilités, et Next.js semble revenir à un modèle plus ancien.

Je comprends les avantages théoriques : réutiliser une librairie de composants et partager du code JavaScript entre serveur et client. Mais ces bénéfices viennent au prix de sacrifices trop importants : opacité du fonctionnement, hydratation problématique avec duplication des données...