Quentin Caillet
15 min readJun 4, 2023

Lancer un nouveau projet de développement web, c’est un peu comme embarquer pour un voyage en mer. La technologie Next.js est votre navire robuste, mais sans une architecture bien conçue et scalable, vous risquez de vous retrouver pris dans la tempête quand les eaux deviennent agitées.

Alors, pourquoi ne pas jeter un oeil à ce guide que j’ai concocté pour vous ? Il est là pour vous montrer comment bâtir une architecture scalable pour vos projets Next.js. Que vous soyez un débutant qui vient tout juste de mettre le pied sur le pont, ou un vieux marin aguerri du développement web, je parie que vous y trouverez quelque chose d’utile.

Mais avant de plonger dans le vif du sujet, prenons un moment pour comprendre pourquoi ce sujet est si crucial. Voyons ce qui peut se passer si on néglige l’importance d’une architecture scalable pour nos projets Next.js.

Ignorer l’architecture scalable dans votre projet Next.js, c’est s’exposer à des problèmes, surtout quand votre projet grandit. Imaginez une app qui ralentit avec l’augmentation des utilisateurs, une maintenance du code devenue casse-tête, et l’intégration avec d’autres systèmes qui vire au cauchemar.

Si votre architecture n’est pas prête à évoluer, ajouter de nouvelles fonctionnalités peut devenir une véritable galère. Sans parler du fait qu’une app qui a besoin de plus de ressources peut se heurter à des difficultés pour distribuer la charge si l’architecture est mauvaise. Et oublions pas les failles de sécurité qui peuvent s’y glisser.

Bref, une architecture scalable bien foutue, c’est vraiment le truc qui va booster votre projet. Ça rend votre app plus cool à gérer, plus sympa à développer, et ça assure que les performances restent au top

Alors, si vous voulez éviter les erreurs courantes et construire une application Next.js qui non seulement tient la route, mais aussi évolue en même temps que vos ambitions, vous êtes clairement au bon endroit.

Configuration du projet

Pour commencer, nous allons créer une application next.js avec Typescript. Pour cela, ouvrez un terminal et entrez la ligne de commande ci-dessous (à savoir que j’utilise pnpm, mais libre à vous d’utiliser votre gestionnaire de paquets préféré).

pnpx create-next-app@latest

Pour ma part, je ne vais pas utiliser Tailwind. Libre à vous, selon vos besoins, de configurer Tailwind ou non.

Nous allons maintenant tester que notre application fonctionne.

pnpm run dev

Vous devriez voir l’application à l’adresse : http://localhost:3000

Verrouillage du moteur

Pour une parfaite cohésion, tous les développeurs du projet doivent utiliser le même moteur de nœud et gestionnaire de paquets. Nous allons créer deux nouveaux fichiers à la racine du projet à cet effet.

  • .nvmrc- Informera les autres utilisateurs du projet de la version de Node utilisée.
  • .pnpmrc- Informera les autres utilisateurs du projet du gestionnaire de paquets utilisé.

Nous utilisons Node v18 Hydrogenet pnpmpour ce projet, nous définissons donc ces valeurs comme suit:

.nvmrc

lts/Hydrogen

.pnpmrc

engine-strict=true

Notez que l’utilisation de engine-strictne dit rien spécifiquement sur pnpm, nous le spécifions dans lepackage.json .

{
"name": "nextjs-app-template",
"version": "0.1.0",
"private": true,
"engines": {
"node": ">=18.0.0",
"pnpm": ">=8.5.1",
"yarn": "please-use-pnpm"
},
...

Le champ engines est l'endroit où vous spécifiez les versions spécifiques des outils que vous utilisez.

Configuration de Git

C’est le moment, en effet je pense que c’est le bon moment d’effectuer notre premier commit, avant de passer à quelque chose de nouveau. Par défaut, notre projet Next.js a déjà un référentiel initialisé. On peut notamment le vérifier en effectuant un git status. Vous devriez avoir quelque chose comme :

$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: package.json

Untracked files:
(use "git add <file>..." to include in what will be committed)
.nvmrc
.pnpmrc

no changes added to commit (use "git add" and/or "git commit -a")

Cela nous indique donc que nous sommes sur la branche master et que nous n’avons pas encore effectué de commits.

Enregistrons les modifications que nous avons faites jusqu’à présent.

git add .

git commit -m 'initial commit'

La commande git add . ajoutera tous les fichiers et dossiers modifiés dans le répertoire de travail actuel (également appelé staging area). La commande git commit -m 'initial commit' va créer un commit avec le message initial commit, et ce commit va contenir les modifications qui ont été ajoutées dans le staging area.

Maintenant, créez-vous un nouveau dépôt sur votre fournisseur d’hébergement préféré, pour ma part je vais utiliser GitHub. Faites bien attention à ce que la branche par défaut porte le même nom que la branche sur votre machine locale, cela pourrait créer quelques problèmes dans le cas contraire. Pour ce faire, si vous devez changer le nom de votre branche sur GitHub, vous pouvez procéder ainsi.

Settings -> Repositories -> Repository default branch

Vous êtes maintenant prêt à associer votre dépôt local avec le dépôt distant. GitHub vous fournira les instructions précises lorsque vous le créerez. Votre syntaxe pourrait être légèrement différente de la mienne en fonction de si vous utilisez HTTPS plutôt que SSH.

git remote add origin https://github.com/{YOUR_GITHUB_USERNAME}/{YOUR_REPOSITORY_NAME}.git
git push -u origin master

Bravo, vous venez de pousser vos modifications sur votre référentiel distant !

Outils de Formatage et de Qualité du Code

Dans l’objectif de créer une norme commune que chaque contributeur du projet pourra suivre pour maintenir un style de code uniforme et respecter les meilleures pratiques, nous allons implémenter deux outils :

  • ESLint- c’est un outil d’analyse qui aide à maintenir la qualité du code.
  • Prettier- c’est un formateur de code qui garantit la cohérence et l’élégance de votre code.

ESLint

On va débuter avec ESLint, c’est facile car il est déjà installé et prédéfini avec les projets Next.js.

Tout ce que nous allons faire ici, c’est rajouter de la configuration pour la rendre un peu plus stricte qu’elle ne l’est par défaut.

Vous devriez donc avoir un fichier eslintrc.json à la racine de votre projet.

{
"extends": ["next", "next/core-web-vitals", "eslint:recommended"],
"globals": {
"React": "readonly"
},
"rules": {
"no-unused-vars": [1, { "args": "after-used", "argsIgnorePattern": "^_" }]
}
}

Dans cette configuration, on donne une sorte de passe-droit à React. On le met en tant que variable globale en Readonly . C'est comme si on disait à ESLint : "Hé, je vais utiliser React un peu partout dans mon code, ne me donne pas d'erreur à chaque fois".

Et vous voyez cette option argsIgnorePattern: "^_" ? C'est un moyen astucieux de dire à ESLint de ne pas se soucier des arguments qui commencent par un underscore. C’est une façon courante de signaler qu’on n’a pas l’intention d’utiliser ces arguments. En gros, on dit à ESLint : Si tu vois un argument commençant par un underscore, laisse tomber, je sais ce que je fais.

Ensuite nous allons tester notre nouvelle conf avec la commande : pnpm lint

vous devriez avoir le resultat suivant :

Vous pouvez faire un commit à ce stade avec le message build: configuration eslint

Prettier

Contrairement à ESLint, il faut que l’on ajoute Prettier à notre projet. Et comme il est nécessaire que pendant le développement, nous allons l’ajouter en tant que devDependency .

pnpm add -D prettier

Je vous recommande aussi fortement d’installer l’extension Prettier si vous utilisez VSCode, elle se chargera de gérer le formatage des fichiers pour vous.

Nous allons maintenant créer deux fichiers à la racine :

.prettierrc

{
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"singleQuote": true
}

A savoir que le choix de ces valeurs revient entièrement à vous, en fonction de ce qui s’adapte le mieux à votre équipe et à votre projet.

.prettierignore

.pnpm
.next
dist
node_modules

Dans ce fichier, j’ai placé une liste de répertoires sur lesquels je ne veux pas que Prettier gaspille des ressources. Libre à vous d’en rajouter ou non suivant vos besoins.

Nous allons maintenant ajouter un nouveau script dans lepackage.json .

...
"scripts: {
...
"prettier": "prettier --write ."
}

Vous pouvez donc maintenant essayer de faire :

pnpm prettier

Avec cette configuration, vous serez en mesure de formater, corriger et enregistrer automatiquement tous les fichiers de votre projet, à l’exception de ceux que vous avez choisi d’ignorer. Pour vous donnez une idée, mon formateur a modifié environ 5 fichiers par défaut. Alors, jetez un œil à votre liste de fichiers modifiés. Vous les trouverez dans l’onglet de contrôle de source sur le côté gauche de VS Code.

Vous pouvez maintenant faire un commit avec le message build: implementation de Prettier et pousser votre code, car nous avons terminé avec cette partie.

Git Hooks

Dans cette partie, nous allons implémenter l’outil Husky.

Husky est un outil pratique qui permet d’exécuter des scripts à différentes étapes clés du cycle de vie de Git, telles que add, commit, push, etc. C’est une manière efficace de s’assurer que notre projet respecte un certain niveau de qualité avant chaque étape importante.

Commencons installons Husky:

pnpm add -D husky
pnpm husky install

La deuxième commande va générer un dossier .huskydans votre projet. Ce dossier sera le foyer de vos hooks. Pensez à inclure ce dossier dans votre dépôt de code. Rappelez-vous, ce n’est pas seulement pour vous, mais aussi pour les autres développeurs qui travaillent sur le projet.

Nous allons maintenant ajouter le script suivant à notre package.json :

 ...
"scripts: {
...
"prepare": "husky install"
}

Lorsque d’autres membres de l’équipe récupéreront le projet et lanceront l’installation avec la commande pnpm install, Husky sera également installé sans qu’ils aient à faire le moindre effort supplémentaire. C’est pas beau, ça ?

Maintenant, nous allons créer notre premier hook. Pour ce faire, exécutez la commande :

pnpm husky add .husky/pre-commit "pnpm lint"

Ce hook sera exécuté automatiquement avant chaque commit. La commande pnpm lint sera donc lancée à chaque fois que vous essayez de faire un commit. Si le script lint rencontre des erreurs, il vous sera impossible de commiter jusqu’à leur résolution.

Pour tester, vous pouvez créer un nouveau commit avec le message suivant : ci: implementation de husky. Si tout se passe bien, votre script lint devrait s’exécuter lorsque vous faites votre commit.

Créons maintenant un deuxième hook :

pnpm husky add .husky/pre-push "pnpm build"

Ce hook nous garantit que nous ne sommes pas autorisés à pousser notre code sur le référentiel distant à moins que le build soit en succès. Vous pouvez le tester en poussant votre commit sur le référentiel distant.

Pour terminer, nous allons ajouter un dernier outil qui permet de nous assurer que tout le monde suit la même convention pour nos messages de commit. Nous allons donc ajouter un linter pour nos messages de commit.

pnpm add -D @commitlint/config-conventional @commitlint/cli

Pour mettre tout ça en place, nous allons partir d’un ensemble de valeurs standard, rien de trop compliqué. Mais personnellement, j’aime bien avoir sous les yeux une liste bien claire dans un fichier commitlint.config.js. Pourquoi ? Parce que, je ne sais pas pour vous, mais parfois j’oublie quels préfixes sont à ma disposition.

Et donc, place à notre commitlint.config.js :

// build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
// ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
// docs: Documentation only changes
// feat: A new feature
// fix: A bug fix
// perf: A code change that improves performance
// refactor: A code change that neither fixes a bug nor adds a feature
// style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
// test: Adding missing tests or correcting existing tests

module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'body-leading-blank': [1, 'always'],
'body-max-line-length': [2, 'always', 100],
'footer-leading-blank': [1, 'always'],
'footer-max-line-length': [2, 'always', 100],
'header-max-length': [2, 'always', 100],
'scope-case': [2, 'always', 'lower-case'],
'subject-case': [
2,
'never',
['sentence-case', 'start-case', 'pascal-case', 'upper-case'],
],
'subject-empty': [2, 'never'],
'subject-full-stop': [2, 'never', '.'],
'type-case': [2, 'always', 'lower-case'],
'type-empty': [2, 'never'],
'type-enum': [
2,
'always',
[
'build',
'chore',
'ci',
'docs',
'feat',
'fix',
'perf',
'refactor',
'revert',
'style',
'test',
'translation',
'security',
'changeset',
],
],
},
};

Ensuite, vous aurez besoin d’activer commitlint avec husky :

pnpm husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'

N’hésitez pas à tester en essayant des commits qui ne respectent pas les conventions. Vous devriez obtenir des messages d’erreur explicites pour corriger les problèmes. Voici un exemple d’erreur :

Une fois le test effectué, nous pouvons donc créer notre commit avec le message suivant ci: implementation de commitlint

Configuration de VS code

Maintenant que nous avons mis en place ESLint et Prettier, il est temps de profiter de certaines fonctionnalités astucieuses de VS Code qui nous permettent de les exécuter automatiquement.

Pour ce faire, commencez par créer un dossier à la racine avec le nom .vscode, et à l’intérieur, créez un fichier settings.json qui contiendra des valeurs qui remplaceront certains paramètres par défaut de VS Code.

L’idée d’avoir ces éléments dans un dossier spécifique au projet est simple : nous pouvons y définir des paramètres qui seront uniquement applicables à ce projet. De plus, en les incluant dans le dépôt de code, nous facilitons leur partage avec l’ensemble de l’équipe.

Vous pouvez donc dans settings.json ajouter :

{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.organizeImports": true
}
}

Ce fichier de configuration JSON indique à VS Code comment gérer certaines actions d’édition dans votre projet :

  • "editor.defaultFormatter": "esbenp.prettier-vscode" : Cela définit Prettier comme le formateur par défaut à utiliser lors de la rédaction du code.
  • "editor.formatOnSave": true : Cette option active la fonctionnalité de formatage automatique à chaque sauvegarde
  • "editor.codeActionsOnSave": { "source.fixAll": true, "source.organizeImports": true } Ce paramètre configure VS Code pour qu’il applique automatiquement toutes les corrections possibles et qu’il réorganise les importations chaque fois qu’un fichier est sauvegardé.

Vous pouvez maintenant commit cette partie avec ce message : build: implementation des settings pour VS Code.

Debugging

Danc cette parti nous allons mettre en place le debuggage coté Vs code.

Pour cela a l’iterieur du dossier .vscodeajouter un second fichier launch.json

{
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: debug server-side",
"type": "node-terminal",
"request": "launch",
"command": "pnpm run dev"
},
{
"name": "Next.js: debug client-side",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000"
},
{
"name": "Next.js: debug full stack",
"type": "node-terminal",
"request": "launch",
"command": "pnpm run dev",
"serverReadyAction": {
"pattern": "started server on .+, url: (https?://.+)",
"uriFormat": "%s",
"action": "debugWithChrome"
}
}
]
}

Grâce à ce script, trois options de débogage s’offrent à vous. Pour y accéder, cliquez sur le petit symbole bug & playà gauche de VS Code ou utilisez le raccourci clavier Ctrl + Shift + D. Une fois dans le menu de débogage, vous pouvez choisir le script que vous voulez lancer. Pour le démarrer ou l’arrêter, utilisez simplement les boutons de démarrage et d’arrêt.

Nous allons d’abord installer le cross-env qui va permettre de définir des variables d’environnement si vous avez des coéquipiers travaillant sur différents environnements (Windows, Linux, Mac, etc.).

pnpm add -D cross-env

Une fois ce package installé, nous pouvons mettre à jour notre package.json devscript pour qu'il ressemble à ceci :

{
...
"scripts": {
...
"dev": "cross-env NODE_OPTIONS='--inspect' next dev",
},
}

Ce script démarre l’environnement de développement du projet en activant l’option d’inspection. Ici, cross-env définit la variable d’environnement NODE_OPTIONSà — inspect, ce qui déclenche le mode débogage dans Node.js. Ensuite,next devest exécuté pour lancer le serveur de développement de Next.js.

À ce stade, je vais faire un nouveau commit avec le messagebuild: ajout de la conf pour le debugging .

Storybook

Storybook est comme votre propre studio de développement, spécialement conçu pour les développeurs front-end. Visualisez-le comme un classeur personnel, renfermant tous vos composants d’interface utilisateur. Il vous permet de les construire et de les tester séparément, loin de la complexité que peut avoir l’application globale. C’est un outil pratique qui simplifie la conception et l’optimisation de l’interface utilisateur. Pour les équipes de développement actuelles, c’est un outil indispensable.

Notez que Storybook est conçu comme un outil de test visuel, nous mettrons en œuvre d’autres outils plus tard pour les tests unitaires et les tests de bout en bout.

Commencons par l’installer :

pnpm dlx storybook@latest init

Au moment de l’installation de Storybook, celui-ci effectue automatiquement une série de détections sur votre projet. Par exemple, il identifie que vous travaillez sur une application React et repère les autres outils que vous utilisez. Il gère lui-même toute cette configuration.

Si une notification concernant le eslintPlugin apparaît, vous pouvez choisir l’option “oui”. Cependant, nous allons le configurer manuellement, alors ne soyez pas alarmé si un message indique qu’il ne s’est pas auto-configuré.

Ouvrez-le .eslintrc.jsonet mettez-le à jour en rajoutant ceci :

"overrides": [
{
"files": ["*.stories.@(ts|tsx|js|jsx|mjs|cjs)"],
"rules": {
// example of overriding a rule
"storybook/hierarchy-separator": "error"
}
}
]

Vous constaterez que Storybook a également créé un dossier /stories à la racine de votre projet, rempli d’exemples. Si vous débutez avec Storybook, je vous recommande vivement de les explorer et de les conserver jusqu’à ce que vous vous sentiez à l’aise pour créer vos propres histoires sans modèles.

Avant de l’exécuter, nous devons nous assurer que nous utilisons webpack5. Ajoutez ce qui suit à votre fichier package.json :

{
...
"resolutions": {
"webpack": "^5"
}
}

Lancez la commande pnpm installafin de vérifier que webpack5 est bien installé.

Ensuite, nous allons mettre àjour le fichier .storybook/main.js :

import type { StorybookConfig } from '@storybook/nextjs';
const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
staticDirs: ['../public'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/nextjs',
options: {},
},
docs: {
autodocs: 'tag',
},
};
export default config;

Dans la partie stories, nous indiquons à Storybook où il peut trouver nos histoires. Nous utilisons des motifs de fichier (patterns) pour chercher tous les fichiers .mdx et tous les fichiers .stories (qu’ils soient en JavaScript ou en TypeScript) dans notre dossier src.

La section addons répertorie les extensions de Storybook que nous utilisons. Ces outils additionnels permettent d’enrichir l’expérience Storybook avec des fonctionnalités supplémentaires.

Le paramètre framework spécifie que nous utilisons Next.js pour ce projet.

La section docs est un petit plus pour automatiser la génération de la documentation de nos composants.

Avant d’exécuter Storybook, nous avons une dernière chose à faire : nous allons modifier le fichier storybook/preview :

import type { Preview } from '@storybook/react';

const BREAKPOINTS_INT = {
xs: 375,
sm: 600,
md: 900,
lg: 1200,
xl: 1536,
};

const customViewports = Object.fromEntries(
Object.entries(BREAKPOINTS_INT).map(([key, val], idx) => {
console.log(val);
return [
key,
{
name: key,
styles: {
width: `${val}px`,
height: `${(idx + 5) * 10}vh`,
},
},
];
})
);

const preview: Preview = {
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
viewport: { viewports: customViewports },
layout: 'fullscreen',
},
},
};

export default preview;

Il est à noter que depuis la dernière version, il n’est plus nécessaire d’implémenter le composant <Image>de Next.js dans ce fichier de configuration pour que cela fonctionne.

Maintenant, vous pouvez lancer la commande :

pnpm storybook

Si tout cela se déroule à merveille, vous devriez voir apparaître :

Et vous pouvez accéder à l’interface http://localhost:6006/

Nous voilà à la fin de la première partie de notre guide pour concevoir une architecture scalable avec Next.js. Nous avons jeté les bases solides dont nous avons besoin. Nous avons configuré notre environnement, installé et paramétré les outils essentiels, comme ESLint, Prettier, Husky et Storybook, et préparé notre projet pour un développement fluide et efficace.

Mais ce n’est que le commencement. La deuxième partie de ce guide va nous plonger encore plus profondément dans la mer du développement Next.js. Nous allons y créer un modèle de composant, ce qui va nous aider à construire des composants réutilisables et maintenables, pour faire de notre code un allié et non un fardeau.

Alors, prenez un instant pour assimiler tout ce que nous avons vu jusque là, et préparez-vous pour la suite de l’aventure. Le voyage continue, et les eaux de Next.js regorgent de trésors cachés que nous sommes sur le point de découvrir. À bientôt dans la deuxième partie de ce guide !

GitHub : https://github.com/qcaillet/nextjs-app-template

https://storybook.js.org/

https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks