{}const=>[]async()letfn</>var
DéveloppementWeb

Comment construire une architecture front-end pour que le projet ne meure pas après un an

Pourquoi certains projets se transforment-ils en code spaghetti au bout de six mois, tandis que d'autres vivent et se développent pendant des années ? Nous analysons les principes éprouvés de la construction de l'architecture front-end : de la structure des dossiers aux couches d'abstraction. Apprenez à créer un code facile à mettre à l'échelle, à tester et à maintenir.

К

Kodik

Auteur

9 min de lecture

Imaginez : vous démarrez un nouveau projet. L'enthousiasme est à son maximum, le code est écrit rapidement. Après quelques mois, vous avez déjà oublié pourquoi vous avez besoin de cette fonction étrange dans utils.js. Six mois plus tard, l'ajout d'une nouvelle fonctionnalité se transforme en quête. Un an plus tard, le projet se transforme en un marécage, où chaque changement peut tout casser.

Cela vous semble familier ? C'est le résultat classique d'une architecture mal pensée. Voyons comment construire des projets front-end qui vivront et se développeront pendant des années.

Pourquoi les projets meurent-ils ?

Avant de parler de solutions, comprenons les principales causes de mortalité des projets :

Spaghetti code. Tout est lié à tout. Un changement à un endroit brise les trois autres composants.

Absence de structure. Les fichiers sont dispersés de façon aléatoire. Trouver le composant nécessaire est une expédition archéologique.

Duplication de la logique. La même fonctionnalité est mise en œuvre dans cinq endroits différents de cinq manières différentes.

Aucune documentation. Même vous-même, dans un mois, vous ne vous souvenez pas comment fonctionne votre code.

Dette technique. « Je le corrigerai plus tard » se transforme en « je ne le corrigerai jamais ».

🔥 100 000+ étudiants déjà avec nous

Marre de lire la théorie ?
Il est temps de coder !

Kodik — une appli où tu apprends à coder par la pratique. Mentor IA, leçons interactives, projets réels.

🤖 IA 24/7
🎓 Certificats
💰 Gratuit
🚀 Commencer
Ont rejoint aujourd'hui

Fondation : la bonne structure de dossiers

Une bonne architecture commence par l'organisation des fichiers. Voici une structure éprouvée pour la plupart des projets :

src/
├── components/          # Composants réutilisables
│   ├── ui/             # Éléments d'interface utilisateur de base (boutons, entrées)
│   ├── layout/         # Composants de mise en page (en-tête, pied de page)
│   └── features/       # Composants d'affaires
├── pages/              # Pages de l'application
├── services/           # Travail avec l'API
├── store/              # État global (Vuex, Redux, Pinia)
├── utils/              # Fonctions auxiliaires
├── hooks/              # Crochets personnalisés (pour React)
├── composables/        # Fonctions composables (pour Vue)
├── types/              # Types de TypeScript
├── constants/          # Constantes de l'application
└── assets/             # Ressources statiques

Principe : chaque dossier est responsable d'un domaine de responsabilité. Lorsque vous avez besoin d'un composant d'interface utilisateur, vous allez dans components/ui. Vous avez besoin d'une fonction pour travailler avec l'API — dans services.

Principe de responsabilité unique

Chaque module doit faire une seule chose, mais bien la faire.

Mauvais :

// UserCard.js - fait tout à la fois
function UserCard({ userId }) {
  const [user, setUser] = useState(null);
  
  // Obtention des données
  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(setUser);
  }, [userId]);
  
  // Validation
  const isValid = user?.email && user?.name;
  
  // Formatage
  const formattedDate = new Date(user?.created).toLocaleDateString();
  
  // Et encore du rendu...
  return <div>...</div>;
}

Bien :

// services/userService.js
export const fetchUser = async (userId) => {
  const response = await fetch(`/api/users/${userId}`);
  return response.json();
};

// utils/validation.js
export const validateUser = (user) => {
  return user?.email && user?.name;
};

// utils/dateFormatter.js
export const formatDate = (date) => {
  return new Date(date).toLocaleDateString();
};

// components/UserCard.js - affichage uniquement
function UserCard({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]);
  
  if (!validateUser(user)) return null;
  
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
      <span>{formatDate(user.created)}</span>
    </div>
  );
}

Maintenant, chaque fonction est testée séparément, utilisée dans des endroits différents, et facilement modifiée.

Les couches d'abstraction : diviser pour régner

Une bonne architecture se construit en couches, comme un oignon :

1. Couche de données (Data Layer)

Responsable de la réception et de l'envoi des données. Toutes les requêtes API vivent ici.

// services/api/userApi.js
const API_BASE = 'https://api.example.com';

export const userApi = {
  getUser: (id) => fetch(`${API_BASE}/users/${id}`).then(r => r.json()),
  updateUser: (id, data) => fetch(`${API_BASE}/users/${id}`, {
    method: 'PUT',
    body: JSON.stringify(data)
  }),
  deleteUser: (id) => fetch(`${API_BASE}/users/${id}`, { method: 'DELETE' })
};

2. Couche de logique métier (Business Logic Layer)

Traite les données, applique les règles de logique métier.

// services/userService.js
import { userApi } from './api/userApi';

export const userService = {
  async getActiveUsers() {
    const users = await userApi.getUsers();
    return users.filter(user => user.isActive);
  },
  
  async promoteToAdmin(userId) {
    const user = await userApi.getUser(userId);
    if (!user.email.endsWith('@company.com')) {
      throw new Error('Only company emails can be admins');
    }
    return userApi.updateUser(userId, { role: 'admin' });
  }
};

3. Calque d'état (State Layer)

Gère l'état global de l'application.

// store/userStore.js (Pinia/Vue)
export const useUserStore = defineStore('user', {
  state: () => ({
    currentUser: null,
    users: []
  }),
  
  actions: {
    async loadUser(id) {
      this.currentUser = await userService.getUser(id);
    }
  }
});

4. Couche de présentation (Presentation Layer)

Composants qui affichent les données à l'utilisateur.

// components/UserProfile.vue
<script setup>
import { useUserStore } from '@/store/userStore';

const userStore = useUserStore();
const { currentUser } = storeToRefs(userStore);

onMounted(() => {
  userStore.loadUser(route.params.id);
});
</script>

<template>
  <div v-if="currentUser">
    <h1>{{ currentUser.name }}</h1>
    <p>{{ currentUser.email }}</p>
  </div>
</template>

Règle d'or : les couches supérieures peuvent utiliser les couches inférieures, mais pas l'inverse. Les composants utilisent store, store utilise services, mais services n'importe jamais de composants.

Composition au lieu d'héritage

Dans le front-end moderne, la composition l'emporte sur l'héritage. Au lieu de classes de base géantes, créez de petites fonctions réutilisables.

Exemple avec les crochets React :

// hooks/useLocalStorage.js
export function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initialValue;
  });
  
  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);
  
  return [value, setValue];
}

// hooks/useDebounce.js
export function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);
  
  useEffect(() => {
    const handler = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(handler);
  }, [value, delay]);
  
  return debouncedValue;
}

// Utilisation
function SearchComponent() {
  const [query, setQuery] = useLocalStorage('searchQuery', '');
  const debouncedQuery = useDebounce(query, 500);
  
  // Vous avez maintenant une recherche avec débounce et enregistrement dans localStorage
  useEffect(() => {
    if (debouncedQuery) {
      searchApi(debouncedQuery);
    }
  }, [debouncedQuery]);
}

La typification est votre meilleure amie

TypeScript peut sembler superflu pour les débutants, mais il évitera de nombreux problèmes au projet.

// types/user.ts
export interface User {
  id: number;
  name: string;
  email: string;
  role: 'user' | 'admin' | 'moderator';
  createdAt: Date;
}

export interface ApiResponse<T> {
  data: T;
  error?: string;
  status: number;
}

// services/userService.ts
export async function getUser(id: number): Promise<ApiResponse<User>> {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}

Désormais, l'IDE vous suggérera les champs disponibles et TypeScript détectera les erreurs au stade du développement et non de la production.

Configuration et constantes

Ne dispersez pas les nombres et les chaînes magiques dans le code. Rassemblez-les en un seul endroit.

// constants/config.js
export const API_CONFIG = {
  BASE_URL: process.env.VITE_API_URL || 'https://api.example.com',
  TIMEOUT: 5000,
  RETRY_ATTEMPTS: 3
};

export const UI_CONSTANTS = {
  ITEMS_PER_PAGE: 20,
  DEBOUNCE_DELAY: 300,
  TOAST_DURATION: 3000
};

export const ROUTES = {
  HOME: '/',
  PROFILE: '/profile',
  SETTINGS: '/settings'
};

Lorsque vous aurez besoin de modifier le nombre d'éléments sur une page, vous saurez où le faire.

Traitement des erreurs

Une approche systémique des erreurs est le signe d'une architecture mature.

// utils/errorHandler.js
export class ApiError extends Error {
  constructor(message, status, data) {
    super(message);
    this.status = status;
    this.data = data;
  }
}

export async function handleApiCall(apiFunction) {
  try {
    return await apiFunction();
  } catch (error) {
    if (error instanceof ApiError) {
      // Nous montrons à l'utilisateur un message clair
      toast.error(error.message);
      
      // Journalisation pour les développeurs
      console.error('API Error:', error.status, error.data);
    } else {
      // Erreur inattendue
      toast.error('Something went wrong. Please try again later.');
      console.error('Unexpected error:', error);
    }
    throw error;
  }
}

// Utilisation
async function loadUserData(userId) {
  await handleApiCall(async () => {
    const user = await userApi.getUser(userId);
    if (!user) {
      throw new ApiError('User not found', 404);
    }
    return user;
  });
}

Documentez les solutions architecturales

Créez un fichier ARCHITECTURE.md à la racine du projet :

# Architecture du projet

# # Structure
- `/components` - переиспользуемые компоненты
- `/pages` - страницы приложения
- `/services` - бизнес-логика и API

# # Accords
- Компоненты именуются в PascalCase
- Утилиты и сервисы в camelCase
- Константы в SCREAMING_SNAKE_CASE

# # Couches
1. API Layer (services/api/)
2. Business Logic (services/)
3. State Management (store/)
4. UI Components (components/)

# # Décisions importantes
- Используем Pinia для состояния
- Axios для HTTP запросов
- День.js для работы с датами

Révision de code et linting

Configurez ESLint et Prettier immédiatement. Cela évitera 90 % des problèmes de lisibilité du code.

// .eslintrc.js
module.exports = {
  rules: {
    'no-console': 'warn',
    'no-unused-vars': 'error',
    'complexity': ['error', 10], // Avertit des fonctions complexes
    'max-lines-per-function': ['warn', 50]
  }
};

Test d'architecture

Une bonne architecture est facile à tester.

// userService.test.js
import { userService } from './userService';
import { userApi } from './api/userApi';

jest.mock('./api/userApi');

test('promoteToAdmin rejects external emails', async () => {
  userApi.getUser.mockResolvedValue({
    id: 1,
    email: 'external@gmail.com'
  });
  
  await expect(
    userService.promoteToAdmin(1)
  ).rejects.toThrow('Only company emails');
});

Si vos fonctions sont difficiles à tester, c'est un signal que l'architecture est boiteuse.

Mise à l'échelle : Structure basée sur les fonctionnalités

Lorsque le projet se développe, regroupez le code par fonctionnalités et non par types de fichiers :

src/
├── features/
│   ├── auth/
│   │   ├── components/
│   │   ├── services/
│   │   ├── store/
│   │   └── types/
│   ├── products/
│   │   ├── components/
│   │   ├── services/
│   │   └── store/
│   └── cart/
│       ├── components/
│       └── store/
└── shared/              # Composants et utilitaires communs

Désormais, tout ce qui concerne l'autorisation se trouve dans un seul dossier. Facile à trouver, facile à supprimer, facile à transférer à un autre développeur.

Erreurs fréquentes des débutants !

Optimisation prématurée

Il n'est pas nécessaire de construire immédiatement une architecture pour un million d'utilisateurs. Commencez par une solution simple mais évolutive.

Abstraction excessive

Si vous n'avez qu'un seul bouton, vous n'avez pas besoin de créer un système de cinq classes de base pour celui-ci.

Ignorer les conventions

Utilisez les pratiques généralement acceptées de votre cadre. N'essayez pas de réinventer la roue.

Absence de remaniement

Prévoyez du temps pour améliorer l'architecture. La dette technique s'accumule sans qu'on s'en aperçoive.

Conseils pratiques.

Commencez par le fichier README. Décrivez le fonctionnement du projet avant d'écrire le code. Cela vous fera penser à l'architecture.

Refactorisez régulièrement. Réservez une heure par semaine pour améliorer le code existant.

Apprenez des autres. Étudiez les projets open source, regardez comment ils sont organisés.

N'ayez pas peur de refaire. Si la structure ne fonctionne pas, il vaut mieux la corriger maintenant que de vivre avec pendant des années.

Outils d'aide.

  • ESLint/Prettier — mise en forme automatique du code

  • Husky — vérification du code avant la validation

  • TypeScript — typification pour les grands projets

  • Storybook — développement de composants de manière isolée

  • Jest/Vitest — test de logique

Conclusion

Une bonne architecture n'est pas un code parfait du premier coup. C'est une approche systémique qui permet au projet d'évoluer. Commencez par une structure simple mais logique. Suivez le principe de responsabilité unique. Documentez les décisions importantes. Et surtout, révisez et améliorez régulièrement votre code.

Dans un an, vous vous remercierez pour chaque minute investie dans l'architecture. Votre projet vivra, se développera et apportera de la joie, et ne se transformera pas en une boule de spaghettis que vous aurez peur de toucher.

Ceci et bien d'autres choses peuvent être étudiées dans Codique — tout analyser en détail et consolider la pratique avec des exercices. Nous enseignons non seulement à écrire du code, mais à créer des applications correctes et évolutives qui dureront de nombreuses années.

Et si vous avez besoin d'aide, nous avons déjà plus de 2 000 personnes partageant les mêmes idées sur un canal Telegram actif, où vous pouvez poser n'importe quelle question, discuter des solutions architecturales et obtenir un examen de votre code de la part de développeurs expérimentés.

Rejoignez la communauté où les vrais professionnels grandissent ! 🚀

🎯Arrête de reporter

Tu as aimé l'article ?
Place à la pratique !

Avec Kodik, tu ne lis pas seulement — tu codes immédiatement. Théorie + pratique = vraies compétences.

Pratique instantanée
🧠L'IA explique le code
🏆Certificat

Sans inscription • Sans carte