contactez nous


Deux outils, deux problèmes radicalement différents. Le framework Next.js résout un problème de rendu : afficher du HTML complet aux moteurs de recherche et aux utilisateurs sans sacrifier l'interactivité d'une application React. TypeScript répond à une problématique tout autre, celle de définir la structure de vos données avant même l'exécution de la moindre ligne de code.
Alors, faut-il utiliser les deux ? À une seule condition : que votre base de code survive à l'équipe qui l'a créée. C'est là tout le test, et nous y reviendrons. D'abord, voyons ce que fait chaque outil, puis comment les intégrer ensemble.
Next.js est un framework open-source créé par Vercel. Il se présente comme le kit de développement logiciel du Web, doté de tous les outils nécessaires pour « rendre le Web plus rapide ». Découvrez ici les fonctionnalités de Next.js avec React et ses applications.
Next.js permet aux moteurs de recherche d'optimiser les applications React avec très peu de configuration de votre part. Imaginez ce qu'une application React traditionnelle envoie en premier : une coquille de page HTML sans aucun contenu rendu à l'intérieur.
Le navigateur récupère ensuite le fichier JavaScript contenant votre code React, affiche le contenu dans le DOM — l'arborescence dynamique des éléments de la page — et le rend interactif. Cela fonctionne, mais présente deux inconvénients qu'il convient de prendre au sérieux :
Next.js vous permet de créer une application React tout en rendant le contenu à l'avance sur le serveur, afin que la première chose qu'un utilisateur ou un robot de recherche voie soit le HTML entièrement rendu. Une fois cette page initiale chargée, le rendu côté client prend le relais et l'application se comporte comme n'importe quelle autre application React.
Contenu entièrement rendu pour les robots. Contenu hautement interactif pour les utilisateurs. Une seule base de code.
La récupération de données est le domaine où le framework Next.js excelle, car il permet d'exécuter plusieurs stratégies de rendu côté serveur au sein d'un même projet.
La récupération côté client convient aux pages qui n'ont pas besoin d'être indexées pour le SEO, qui ne nécessitent pas de données pré-rendues, ou qui changent trop fréquemment pour être figées. La génération statique, aussi appelée pré-rendu, construit vos pages une seule fois lors de la compilation plutôt qu'à chaque requête. Voici la version côté client :
import { useEffect, useState } from 'react';
type Todo = { id: number; text: string; done: boolean };
export default function TodoList() {
const [isLoading, setIsLoading] = useState(true);
const [todos, setTodos] = useState<Todo[]>([]);
useEffect(() => {
fetch('/api/todos')
.then((response) => response.json())
.then((data: Todo[]) => {
setTodos(data);
setIsLoading(false);
});
}, []);
if (isLoading) return <p>Loading...</p>;
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}Il s'agit de la récupération de données côté client utilisant le hook useEffect de React. Nous initialisons deux constantes, l'une pour suivre si la récupération est toujours en cours et l'autre pour stocker le résultat, puis nous appelons useEffect avec deux arguments :
Un détail important à noter. Cette Todo[] annotation sur la réponse est une promesse, pas une garantie : elle indique au compilateur ce que vous attendez de l'endpoint, mais rien ne le vérifie à l'exécution. Plus loin, nous typerons la route API elle-même, afin que la promesse soit respectée des deux côtés.
TypeScript est un langage de programmation développé et maintenu par Microsoft. Il s'agit d'un sur-ensemble strict de JavaScript, ce qui signifie que tout fichier JavaScript valide est déjà un fichier TypeScript valide. Rien à convertir dès le premier jour.
Imaginez qu'il s'agit d'étiqueter vos cartons lors d'un déménagement. Vous pouvez déménager sans étiquettes, et vous finirez par découvrir ce que contient chaque carton. TypeScript est le marqueur : il prend en charge le typage statique et dynamique, ajoute des fonctionnalités d'héritage, des classes et des interfaces, et a été conçu pour les projets de grande envergure car il facilite grandement la refactorisation du code. Apprenez-en plus sur ses fonctionnalités grâce à une comparaison approfondie avec JavaScript.
Les raisons pour lesquelles un développeur JavaScript franchit le pas sont nombreuses :
La sécurité du typage n'est qu'une discipline parmi d'autres. Si vous souhaitez évaluer les méthodes de travail de votre équipe de bout en bout, notre guide des 18 meilleures pratiques Agile à adopter dans votre cycle de développement logiciel couvre le reste.

L'argument technique est bien documenté. C'est l'argument commercial qui est réellement décisif, et il mérite d'être tranché avant que quiconque n'écrive un tsconfig.json.
Nous appelons notre vérification le test de pérennité : trois questions pour savoir si une base de code survivra aux personnes qui l'ont écrite. Appliquez-les à votre propre projet.
1. Où vos défauts sont-ils détectés ? Une erreur de type est détectée par le compilateur, dans l'éditeur du développeur, quelques secondes après avoir été écrite. La même erreur en JavaScript pur attend l'exécution, ce qui, en pratique, signifie l'étape de QA, de pré-production ou de production. Chaque étape de cette chaîne coûte plus cher à diagnostiquer et à corriger. La dernière vous coûte une mise en production.
Cette catégorie est mesurable, ce qui est inhabituel pour ce type d'argument. Airbnb a passé en revue ses propres incidents passés lors de sa migration et a rapporté lors de la JSConf Hawaii en 2019 que 38 pour cent des bugs qu'elle avait déployés auraient pu être évités grâce à TypeScript. Une étude indépendante, To Type or Not to Type: Quantifying Detectable Bugs in JavaScript (Gao, Bird et Barr, ICSE 2017), a échantillonné des corrections de bugs publiques sur GitHub et a estimé ce chiffre à 15 pour cent. Considérez le chiffre le plus bas comme votre plancher et le plus élevé comme ce à quoi ressemble une base de code importante et pérenne.
2. À quelle vitesse un inconnu peut-il modifier votre code ? Sur une base de code héritée, les types sont la documentation qui ne peut pas devenir obsolète. Un développeur rejoignant un projet Next.js typé suit une prop depuis une page jusqu'à un composant et voit exactement ce qu'elle contient. Sur un projet non typé, il lit les points d'appel, devine, et découvre lors de la revue si son hypothèse était la bonne.
3. Quel est le coût de modification de la base de code après trois ans ? C'est là que TypeScript est vraiment rentable. Renommer un champ ou remodeler une réponse d'API devient une tâche guidée par le compilateur plutôt qu'une recherche manuelle dans tout le dépôt. Cela compte énormément pour un produit à longue durée de vie, et presque pas pour un site marketing avec une durée de vie de six mois.
Notre règle chez Imaginary Cloud découle directement de ce test. Si une base de code doit survivre à l'équipe qui l'a écrite, nous la commençons en TypeScript. S'il s'agit d'un prototype jetable, nous ne le faisons généralement pas, car le coût de configuration est réel et le retour sur investissement n'arrive jamais.
Voici un tutoriel étape par étape pour ajouter TypeScript à une application Next.js, en partant d'un répertoire vide jusqu'à une page typée. Ce guide utilise le Pages Router, sur lequel reposent encore la plupart des bases de code en production. La section App Router ci-dessous traite du modèle plus récent.
L'objectif de ce guide n'est pas de créer une application de tâches, mais d'établir un contrat typé entre la route API et la page qui l'utilise. C'est l'étape que la plupart des tutoriels omettent, et pourtant, c'est celle qui apporte le plus de valeur.
1. Créer le projet de base. Exécutez npx create-next-app@latest my-todo-app pour créer un projet à partir du modèle de base.
2. Ajoutez tsconfig.json à la racine du projet pour activer TypeScript. Next.js détecte le fichier lors de votre prochaine commande npm run dev, installe les dépendances TypeScript nécessaires et remplit le fichier pour vous. Une configuration générée ressemble à ceci :
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [{ "name": "next" }],
"paths": { "@/*": ["./src/*"] }
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}Laissez strict activé. Le désactiver est le moyen le plus courant pour une équipe de se retrouver avec une base de code typée qui ne détecte aucune erreur. La ligne plugins active le plugin du serveur de langage TypeScript de Next.js, et l'alias paths est ce qui permet @/… les imports resolve — les exemples de l'App Router ci-dessous s'appuient dessus.
3. Structurez le projet comme suit.
my-todo-app/
├── app/ # App Router (see the section below)
│ └── api/
│ └── todos/
│ └── route.ts
├── components/
│ ├── TodoForm.tsx
│ └── TodoItem.tsx
├── pages/ # Pages Router (this walkthrough)
│ ├── api/
│ │ └── todos.ts
│ ├── _app.tsx
│ └── index.tsx
├── types/
│ └── todo.ts
├── next-env.d.ts
├── package.json
└── tsconfig.json4. Créez des types TypeScript dans Next.js.
Vous pouvez typer n'importe quel élément de votre application : props, réponses d'API, arguments de fonction. Placez ceux qui franchissent une limite dans leur propre fichier, car c'est ce qui les rend partageables.
Commencez par un type pour notre Todo :
// types/todo.ts
export type Todo = {
id: number;
text: string;
done: boolean;
};5. Typez la route API avec le même type.
C'est l'étape qui transforme TypeScript d'un simple outil d'assistance à l'édition en un véritable contrat. Le gestionnaire de route déclare qu'il renvoie Todo[]. La page qui l'appelle déclare qu'elle reçoit Todo[]. Les deux lisent le même fichier.
// pages/api/todos.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { Todo } from '../../types/todo';
const todos: Todo[] = [
{ id: 1, text: 'Type the API route', done: true },
{ id: 2, text: 'Ship it', done: false },
];
export default function handler(
_request: NextApiRequest,
response: NextApiResponse<Todo[]>
) {
response.status(200).json(todos);
}Le bénéfice apparaît le jour où quelqu'un renomme text en label dans types/todo.ts. Le gestionnaire de route cesse de compiler, car les objets dans todos ne correspondent plus Todo[], tout comme chaque composant qui lit todo.text. Sans le type partagé ? Le renommage compile sans erreur et la page affiche silencieusement une colonne de champs vides.
6. Créer des composants dans Next.js.
Maintenant que nous avons notre type Todo, nous pouvons créer le composant TodoItem.
// components/TodoItem.tsx
import { Todo } from '../types/todo';
type TodoItemProps = {
todo: Todo;
removeTodo: (id: number) => void;
setDone: (id: number) => void;
};
export default function TodoItem({ todo, removeTodo, setDone }: TodoItemProps) {
return (
<li>
<input
type="checkbox"
checked={todo.done}
onChange={() => setDone(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => removeTodo(todo.id)}>Remove</button>
</li>
);
}Nous importons le type que nous avons créé, puis nous en déclarons un second, TodoItemProps, qui reflète les props reçues par le composant.
Ce composant affiche un objet Todo. Il prend cet objet, une fonction removeTodo et une fonction setDone en tant que props. L'argument doit correspondre au type des props, sinon le compilateur rejette le code avant même son exécution.
Passons maintenant au composant TodoForm, responsable de l'ajout de tâches.
// components/TodoForm.tsx
import { FormEvent, useState } from 'react';
type TodoFormProps = {
addTodo: (text: string) => void;
};
export default function TodoForm({ addTodo }: TodoFormProps) {
const [value, setValue] = useState('');
const handleSubmit = (event: FormEvent) => {
event.preventDefault();
if (!value.trim()) return;
addTodo(value);
setValue('');
};
return (
<form onSubmit={handleSubmit}>
<input value={value} onChange={(event) => setValue(event.target.value)} />
<button type="submit">Add todo</button>
</form>
);
}Il accepte une fonction addTodo en tant que prop et gère la soumission d'une nouvelle tâche. Si la valeur n'est pas vide, il appelle addTodo avec le texte et vide le formulaire.
Regardez la signature de cette prop : (text: string) => void. Le parent ne peut pas transmettre une fonction attendant un objet, ou une fonction attendant deux arguments, sans que le compilateur n'y trouve à redire.
7. Créez la page qui utilise les composants.
Nous importons les composants et les types définis précédemment, ainsi que GetStaticProps, un type fourni par Next.js qui nous permet de typer la méthode getStaticProps , la fonction que Next.js exécute au moment de la compilation pour récupérer les données nécessaires à une page générée statiquement.
Ensuite, nous initialisons l'état des todos avec le hook useState , en lui transmettant les todos initiaux fournis par getStaticProps , et nous déclarons les trois fonctions qui portent notre logique :
addTodo — ajoute un todo à la listeremoveTodo — supprime un todo de la listesetDone — marque une tâche comme terminéeEnfin, nous affichons la liste à l'aide de nos composants.
// pages/index.tsx
import { GetStaticProps } from 'next';
import { useState } from 'react';
import TodoForm from '../components/TodoForm';
import TodoItem from '../components/TodoItem';
import { Todo } from '../types/todo';
type HomeProps = {
initialTodos: Todo[];
};
export default function Home({ initialTodos }: HomeProps) {
const [todos, setTodos] = useState<Todo[]>(initialTodos);
const addTodo = (text: string) =>
setTodos([...todos, { id: Date.now(), text, done: false }]);
const removeTodo = (id: number) =>
setTodos(todos.filter((todo) => todo.id !== id));
const setDone = (id: number) =>
setTodos(
todos.map((todo) =>
todo.id === id ? { ...todo, done: !todo.done } : todo
)
);
return (
<main>
<TodoForm addTodo={addTodo} />
<ul>
{todos.map((todo) => (
<TodoItem
key={todo.id}
todo={todo}
removeTodo={removeTodo}
setDone={setDone}
/>
))}
</ul>
</main>
);
}
export const getStaticProps: GetStaticProps<HomeProps> = async () => {
const response = await fetch('http://localhost:3000/api/todos');
const initialTodos: Todo[] = await response.json();
return { props: { initialTodos }, revalidate: 60 };
};Une mise en garde concernant cette requête : appeler votre propre API via HTTP au moment de la compilation est risqué, car le serveur pourrait ne pas être actif lors de la génération de la page. Pour une compilation réelle, importez la source de données directement dans getStaticProps au lieu de passer par le réseau.
La page est désormais entièrement typée, du gestionnaire de route aux props, jusqu'à l'élément rendu. Modifiez la structure de Todo dans types/todo.ts et le compilateur signalera chaque fichier qui ne correspond plus, avant même que vous n'ayez ouvert un navigateur.
Tout ce qui précède utilise le Pages Router. Depuis Next.js 13, l'App Router est devenu la valeur par défaut pour les nouveaux projets, et depuis Next.js 16 (octobre 2025), il constitue le standard sur lequel repose le framework — Turbopack est désormais le bundler par défaut et la version minimale requise est Node.js 20. Le modèle de typage évolue avec l'App Router. Vous démarrez un projet aujourd'hui ? C'est cette version qu'il faut utiliser.
Trois éléments changent :
app/ s'exécute par défaut sur le serveur et peut être une fonction asynchrone, permettant ainsi d'effectuer la récupération de données directement en ligne. Plus de getStaticProps, plus d'objet props à typer : vous typez ce que vous récupérez, là où vous le récupérez.pages/api/todos.ts devient app/api/todos/route.ts, en exportant une fonction nommée selon la méthode HTTP et en utilisant les objets Web Request et Response plutôt que ceux spécifiques à Next.js.getStaticProps avec revalidate devient une option de l'appel fetch lui-même. Le même type partagé couvre toujours les deux extrémités.Si vous avez utilisé Next.js pour la dernière fois autour des versions 13 ou 14, trois points méritent votre attention avant de mettre à jour. Turbopack est désormais le bundler par défaut pour le développement et la compilation, ce qui rend les démarrages à froid et les reconstructions nettement plus rapides, sans nécessiter de modifications pour la plupart des projets sans configuration webpack personnalisée. La version minimale de Node.js prise en charge est la 20. Enfin, le modèle de mise en cache est désormais explicite : fetch n'est plus mis en cache par défaut ; vous devez donc l'activer pour chaque appel avec cache et next.revalidate plutôt que de vous fier aux paramètres par défaut du framework. Rien de tout cela ne modifie le modèle de contrat typé présenté dans cet article — types/todo.ts reste la définition unique partagée par les deux extrémités — mais cela change les commandes à exécuter. Le codemod de mise à jour (npx @next/codemod@latest upgrade latest) gère la majeure partie du travail technique ; prévoyez du temps pour la migration vers l'App Router et la compatibilité avec React 19, plutôt que pour la montée de version elle-même.
// app/api/todos/route.ts
import { NextResponse } from 'next/server';
import { Todo } from '@/types/todo';
const todos: Todo[] = [
{ id: 1, text: 'Type the route handler', done: true },
{ id: 2, text: 'Ship it', done: false },
];
export async function GET() {
return NextResponse.json<Todo[]>(todos);
}// app/page.tsx
import TodoItem from '@/components/TodoItem';
import { Todo } from '@/types/todo';
async function getTodos(): Promise<Todo[]> {
const response = await fetch('http://localhost:3000/api/todos', {
next: { revalidate: 60 },
});
return response.json();
}
export default async function Home() {
const todos = await getTodos();
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}Même contrat qu'auparavant : types/todo.ts est la définition unique, le gestionnaire de route déclare qu'il renvoie cette structure, et la page déclare qu'elle l'utilise. Une mise en garde avant toute migration : les composants interactifs comme TodoForm nécessitent le 'use client' en haut du fichier, car l'état et les gestionnaires d'événements ne peuvent pas s'exécuter sur le serveur.
Pour toute application que vous prévoyez de maintenir au-delà de quelques mois, oui. Next.js intègre un support natif de TypeScript, ce qui rend le coût de configuration quasi nul, tandis que la sécurité du typage est un atout précieux à chaque refactorisation et pour chaque nouveau développeur rejoignant le projet.
Oui, et vous pouvez le faire progressivement. Ajoutez un fichier tsconfig.json , lancez le serveur de développement, et Next.js installera ce dont il a besoin. Avec allowJs défini sur true, vos fichiers .js existants continuent de fonctionner pendant que vous convertissez vos fichiers en .tsx un par un.
La vérification des types ajoute du temps à la compilation, mais Next.js ne vérifie pas les types lors du rechargement à chaud du serveur de développement, le travail quotidien n'est donc pas impacté. Sur les bases de code volumineuses, vous pouvez déplacer la vérification vers une étape CI distincte avec tsc --noEmit pour conserver une compilation rapide.
Probablement pas. Une page de destination ou un site de campagne éphémère ne dure que rarement assez longtemps pour justifier le temps passé à la configuration et à l'annotation. La limite se situe au moment où le code source doit être transmis à quelqu'un qui ne l'a pas écrit.
getStaticProps et getServerSideProps? getStaticProps s'exécute au moment de la compilation et génère le HTML une seule fois, ce qui est idéal pour le contenu qui change rarement. getServerSideProps s'exécute à chaque requête, ce qui convient au contenu personnalisé ou qui change constamment. Tous deux sont entièrement typés en TypeScript. Avec l'App Router, ils sont tous deux remplacés par des options de mise en cache sur fetch.
Utilisez l'App Router pour tout nouveau projet, car c'est la norme et la direction prise par le framework. Conservez une base de code existante sous Pages Router telle quelle, sauf si vous avez une raison particulière de migrer : les deux sont pris en charge et peuvent coexister dans le même projet pendant une transition.
Le coût de configuration se résume à un tsconfig.json et à la discipline de typer vos props. Le retour sur investissement se manifeste dès le troisième refactoring, à l'arrivée du premier nouveau collaborateur, et lors de la première modification d'API qui, sans cela, aurait été déployée avec des erreurs.
Alors, soumettez votre projet au test de pérennité avant de vous engager. Si les réponses indiquent une base de code qui sera maintenue par quelqu'un d'autre, le duo est largement rentabilisé. S'il s'agit d'un prototype, économisez vos efforts et votre marqueur.
Si vous évaluez cette solution pour un produit que votre équipe devra gérer sur le long terme, nous serions ravis d'en discuter avec vous. Nous concevons des produits web avec cette stack — du moteur de décision Geo Matrix pour Aurora Analytica, une plateforme Next.js qui permet aux équipes de recherche clinique d'exécuter des scénarios de conception d'essais sur leurs propres données, jusqu'au tableau de bord d'AppTweak, où la reconstruction en React et TypeScript a réduit le temps de chargement de 80 %. Nous vous dirons franchement si cette combinaison est adaptée à vos besoins.

Un spécialiste du marketing de croissance polyvalent et axé sur les données, doté d'une connaissance approfondie des affaires et informé des derniers développements dans le paysage du marketing numérique.

Un développeur jeune et passionné qui cherche à faire une différence dans la façon dont les gens mènent leur vie quotidienne.
People who read this post, also found these interesting: