Publié le 30/10/2025• Alexandre BOREL
Lorsque je travaillais chez Frenchfounders en tant que Lead Frontend Engineer, je me suis retrouvé face à un projet qui commençait à prendre de l’ampleur, et de nouveaux challenges se profilaient à l’horizon : nous devions repenser la codebase de notre SPA (ou plutôt nos SPA) pour qu’à partir de celle-ci, nous puissions faire une multitude de SPA.
Si les bonnes pratiques n’avaient pas été imposées dès le début, le projet aurait été beaucoup plus compliqué à mettre en place. Nous allons voir les bonnes pratiques que nous avons décidé de mettre en place pour un projet qui, aujourd’hui encore, reste propre et très scalable après plusieurs années.
Je ne vais pas m’attarder sur les bonnes pratiques de code ici, ça fera l’objet d’un article dédié (car le beau code j’aime ça, et j’ai beaucoup de choses à dire !)
Ce point est à mes yeux le point central d’une application qui peut scale. Comment bien structurer ses fichiers Javascript pour qu’ils puissent fonctionner correctement et ne pas s’entremêler à travers les différentes parties de l’application. Garder une codebase lisible est important.
Il est important de différencier les types de composants que l’on va retrouver dans une SPA :
Ces composants ne vont pas être utilisé de la même manière et ne seront donc pas placés dans les même dossiers.
Mes composants d’UI (qui sont réutilisables) sont dans un dossier src/components
src/
├─ components/ # Only reusable components in this folder
├─ ui/
├─ Button.tsx
├─ Input.tsx
├─ ...
Tandis que les composants qui comportent de la logique sont rattachés à une page. On va donc construire une structure de dossier spécifique pour gérer le cas des composants “partials”
src/
├─ pages/
├─ events/
├─ details/
├─ EventDetails.tsx # La page "/event/:id" par exemple
├─ partials/
├─ Header.tsx # `Header` est SEULEMENT présent dans "EventDetails.tsx"
├─ Participants.tsx # `Participants` est SEULEMENT présent dans "EventDetails.tsx"
Quel est l’avantage de cette structure ?
Et bien il y en a plusieurs. Pour commencer, je sais que les composants dans partials ne seront utilisé que dans le fichier parent (dans l’exemple:EventDetails.tsx ), ce qui fait que si je modifie ou supprime un des “partials”, il n’aura d’impact nulle autre part que sur EventDetails.tsx .
Autre avantage, si un jour je décide de supprimer cette page EventDetails.tsxde mon projet, je sais que je pourrais supprimer également les “partials” sans risque.
Chaque page contient donc ses “partials”.
Remarque: Dans mes partials, je n’ai mis que des composants, mais rien ne m’empêche d’y mettre des hooks, des images, ou autres … tant que ces fichiers ne sont utilisés que sur cette page.
Et si mes composants “partials” doivent être utilisés dans d’autres pages ?
Dans ce cas pourquoi ne pas les mettre dans un dossier src/partials , ce qui nous permettra de savoir que les composants de ce dossier sont utilisés dans plusieurs pages. À vous de tester ce qui vous semble le plus intuitif ! L’important est de séparer les composants en fonction de leur usage.
Typer parfaitement ses composants
Il m’arrive souvent de voir des composants qui ne sont pas typé correctement et ça peut devenir un réel casse-tête à leur réutilisation. Prenons le cas d’un composant très simple qui affiche les informations d’un utilisateur.
type User = {
id: string,
name: string,
age: number,
picture?: string
}
export function ParentComponent() {
const [user, setUser] = useState<User | null>(null)
useEffect(() => {
// Implémentation d'un appel API (fake) pour récupérer un objet de type `User`
getUserFromApi()
.then(userFetched => setUser(userFetched)
}, [])
return (
<MemberCard user={user} />
)
}
// -------------
type Props = {
user: User
}
export default function MemberCard(props: Props) {
return (
<div>
Nom : {props.user.name}
</div>
}
En réalité, le composant MemberCard n’est pas bien typé. Si vous regardez attentivement ce que vous utilisez comme props dans votre composant, vous voyez qu’on utilise seulement name de la props user , donc pourquoi dire à notre composant de prendre user: User si ce n’est que pour utiliser une seule de ses props ?
Le problème va survenir le jour où vous allez vouloir utiliser le composant de manière simple:
// Erreur de typage:
<MemberCard user={{ name: "David" }} />
// Pas d'erreur de typage, mais je suis obligé de passer des données qui ne seront pas utilisées
<MemberCard user={{ id: "124", name: "David", age: 12}} />
Un cas typique de problème que vous pourriez avoir, c’est si vous avez deux routes d’API qui retournent un objet “User” mais qui n’est pas exactement le même (avec quelques propriétés en moins). Votre composant ne fonctionnera qu’avec un des appels API.
Pour régler le problème, c’est simple, il faut typer le composant avec seulement les données qui sont utilisées.
Voici deux façons de mettre ça en place :
// Première méthode
type User = {
id: string,
name: string,
age: number,
picture?: string
}
type Props = {
user: Pick<User, 'name'> // Je ne prends que "name" de "User"
}
export default function MemberCard(props: Props) {
return (
<div>
Nom : {props.user.name}
</div>
}
C’est ma méthode préférée, mais elle ne fonctionnera pas correctement avec des objets qui ont plus de profondeur. Pour des objets plus complexe, la seconde méthode peut être plus adaptée.
// Seconde méthode
type User = {
id: string,
name: string,
age: number,
picture?: string
company: {
id: string,
name: string
}
}
type Props = {
user: {
name: string
company: {
name: string
}
}
}
export default function MemberCard(props: Props) {
return (
<div>
Nom : {props.user.name}
Entreprise: {props.user.company.name}
</div>
}
De cette manière j’ai pu typer mon composant avec seulement les données qu’il exploite réellement.
La plupart des applications intègre un système d’internationalisation (i18n) pour traduire l’application dans plusieurs langues. C’est souvent quelque chose d’ennuyeux à mettre en place car il faut gérer des clés et des traductions dans des fichiers, les importers, utiliser les clés dans les composants etc … Boring !
Pour éviter de perdre un maximum de temps, j’ai prit l’habitude (lorsque c’est possible) de mettre les clés traductions dans mes composants directement.
import { useI18n } from '@/src/core/i18n' // Implémentation arbitraire d'i18n (utilisez votre lib préférée)
export default function MyComponent() {
const i18n = useI18n({
fr: {
hello_world: "Bonjour tout le monde"
},
en: {
hello_world: "Hello world"
}
})
return (
<div>{i18n.t('hello_world')}</div>
)
}
Certains vont trouver ça horrible et crier à la répétition et au fait que c’est sale d’écrire des traductions au niveau des composants. Mais j’ai une réponse pour contrer ces arguments … enfin … certains.
Premièrement, avec ce système là, je gagne un temps fou lorsque je dois trouver un composant qui à un problème dans mon code. Si une personne me dit qu’il y a un problème sur un composant, je peux rechercher le composant en question en faisant une recherche dans mon code.

Admettons que le bouton “Answers” ne fonctionne pas. Je n’ai qu’à rechercher “Answers” dans ma codebase pour trouver le composant que je vais devoir corriger
Je passe beaucoup moins de temps à rechercher les composants en erreur.
Deuxièmement, lorsque je supprime mon composant, toutes les traductions associées à ce composant sont également supprimées : fini les traductions fantômes qui ne sont plus utilisées et qui errent dans des fichiers en.json de 2000 lignes.
Pour terminer sur ce sujet là, la notion de répétition de traduction n’est pas vraiment un problème, rien ne vous empêche d’avoir un en.json et un fr.json global qui est injecté dans votre useI18n si vous en avez besoin. Dans vos composant vous aurez donc les traductions globales ainsi que des traductions propres à vos composants.
Un point que je peux vous accorder c’est qu’effectivement ce n’est pas super esthétique d’avoir les traductions dans vos composants. Mais pourquoi ne pas sortir éventuellement les traductions dans des fichiers en.json au niveau des partials/ que nous avons vu plus tôt dans l’article ?
Il existe des tonnes d’autres bonnes pratiques dont nous pourrions parler. Je vous ai énuméré les principales que je met en place lors de nouveaux projets pour être sûr de ne pas retrouver dans un projet incontrollable.

Écrit par