best-practices

Par mkurman · zorai

Appliquer les meilleures pratiques modernes de développement web en matière de sécurité, de compatibilité et de qualité de code. À utiliser lorsqu'on vous demande « d'appliquer les meilleures pratiques », de réaliser un « audit de sécurité », de « moderniser le code », d'effectuer une « revue de qualité du code » ou de « vérifier les vulnérabilités ».

npx skills add https://github.com/mkurman/zorai --skill best-practices

APIs dépréciées

À éviter

// ❌ document.write (bloque le parsing)
document.write('<script src="..."></script>');

// ✅ Chargement dynamique de script
const script = document.createElement('script');
script.src = '...';
document.head.appendChild(script);

// ❌ XHR synchrone (bloque le thread principal)
const xhr = new XMLHttpRequest();
xhr.open('GET', url, false); // false = synchrone

// ✅ Fetch asynchrone
const response = await fetch(url);

// ❌ Application Cache (déprécié)
<html manifest="cache.manifest">

// ✅ Service Workers
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js');
}

Event listener passif

// ❌ Touch/wheel non-passif (peut bloquer le scroll)
element.addEventListener('touchstart', handler);
element.addEventListener('wheel', handler);

// ✅ Listeners passifs (permet un scroll fluide)
element.addEventListener('touchstart', handler, { passive: true });
element.addEventListener('wheel', handler, { passive: true });

// ✅ Si vous avez besoin de preventDefault, soyez explicite
element.addEventListener('touchstart', handler, { passive: false });

Console et erreurs

Aucune erreur en console

// ❌ Erreurs en production
console.log('Debug info'); // Supprimer en production
throw new Error('Unhandled'); // Attraper toutes les erreurs

// ✅ Gestion d'erreur appropriée
try {
  riskyOperation();
} catch (error) {
  // Envoyer vers un service de suivi des erreurs
  errorTracker.captureException(error);
  // Afficher un message convivial
  showErrorMessage('Something went wrong. Please try again.');
}

Limites d'erreur (React)

class ErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    errorTracker.captureException(error, { extra: info });
  }

  render() {
    if (this.state.hasError) {
      return <FallbackUI />;
    }
    return this.props.children;
  }
}

// Utilisation
<ErrorBoundary>
  <App />
</ErrorBoundary>

Gestionnaire d'erreur global

// Attraper les erreurs non gérées
window.addEventListener('error', (event) => {
  errorTracker.captureException(event.error);
});

// Attraper les rejets de promise non gérés
window.addEventListener('unhandledrejection', (event) => {
  errorTracker.captureException(event.reason);
});

Source maps

Configuration pour la production

// ❌ Source maps exposées en production
// webpack.config.js
module.exports = {
  devtool: 'source-map', // Expose le code source
};

// ✅ Source maps cachées (téléchargées vers le suivi des erreurs)
module.exports = {
  devtool: 'hidden-source-map',
};

// ✅ Ou pas de source maps en production
module.exports = {
  devtool: process.env.NODE_ENV === 'production' ? false : 'source-map',
};

Bonnes pratiques de performance

Éviter les motifs bloquants

// ❌ Script bloquant
<script src="heavy-library.js"></script>

// ✅ Script différé
<script defer src="heavy-library.js"></script>

// ❌ Import CSS bloquant
@import url('other-styles.css');

// ✅ Balises link (chargement parallèle)
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="other-styles.css">

Gestionnaires d'événement efficaces

// ❌ Gestionnaire sur chaque élément
items.forEach(item => {
  item.addEventListener('click', handleClick);
});

// ✅ Délégation d'événement
container.addEventListener('click', (e) => {
  if (e.target.matches('.item')) {
    handleClick(e);
  }
});

Gestion de la mémoire

// ❌ Fuite mémoire (jamais supprimé)
const handler = () => { /* ... */ };
window.addEventListener('resize', handler);

// ✅ Nettoyage quand terminé
const handler = () => { /* ... */ };
window.addEventListener('resize', handler);

// Plus tard, lors du démontage du composant :
window.removeEventListener('resize', handler);

// ✅ En utilisant AbortController
const controller = new AbortController();
window.addEventListener('resize', handler, { signal: controller.signal });

// Nettoyage :
controller.abort();

Qualité du code

HTML valide

<!-- ❌ HTML invalide -->
<div id="header">
<div id="header"> <!-- ID dupliqué -->

<ul>
  <div>Item</div> <!-- Enfant invalide -->
</ul>

<a href="/"><button>Click</button></a> <!-- Imbrication invalide -->

<!-- ✅ HTML valide -->
<header id="site-header">
</header>

<ul>
  <li>Item</li>
</ul>

<a href="/" class="button">Click</a>

HTML sémantique

<!-- ❌ Non-sémantique -->
<div class="header">
  <div class="nav">
    <div class="nav-item">Home</div>
  </div>
</div>
<div class="main">
  <div class="article">
    <div class="title">Headline</div>
  </div>
</div>

<!-- ✅ HTML5 sémantique -->
<header>
  <nav>
    <a href="/">Home</a>
  </nav>
</header>
<main>
  <article>
    <h1>Headline</h1>
  </article>
</main>

Rapports d'aspect des images

<!-- ❌ Images déformées -->
<img src="photo.jpg" width="300" height="100">
<!-- Si le rapport réel est 4:3, cela compresse l'image -->

<!-- ✅ Préserver le rapport d'aspect -->
<img src="photo.jpg" width="300" height="225">
<!-- Dimensions réelles 4:3 -->

<!-- ✅ CSS object-fit pour plus de flexibilité -->
<img src="photo.jpg" style="width: 300px; height: 200px; object-fit: cover;">

Permissions et confidentialité

Demander les permissions correctement

// ❌ Demander au chargement de la page (mauvaise UX, souvent refusé)
navigator.geolocation.getCurrentPosition(success, error);

// ✅ Demander en contexte, après une action utilisateur
findNearbyButton.addEventListener('click', async () => {
  // Expliquer pourquoi vous en avez besoin
  if (await showPermissionExplanation()) {
    navigator.geolocation.getCurrentPosition(success, error);
  }
});

Politique de permissions

<!-- Restreindre les fonctionnalités puissantes -->
<meta http-equiv="Permissions-Policy" 
      content="geolocation=(), camera=(), microphone=()">

<!-- Ou autoriser pour des origines spécifiques -->
<meta http-equiv="Permissions-Policy" 
      content="geolocation=(self 'https://maps.example.com')">

Liste de vérification d'audit

Sécurité (critique)

  • [ ] HTTPS activé, aucun contenu mixte
  • [ ] Aucune dépendance vulnérable (npm audit)
  • [ ] En-têtes CSP configurés
  • [ ] En-têtes de sécurité présents
  • [ ] Aucune source map exposée

Compatibilité

  • [ ] Doctype HTML5 valide
  • [ ] Charset déclaré en premier dans head
  • [ ] Balise meta viewport présente
  • [ ] Aucune API dépréciée utilisée
  • [ ] Event listeners passifs pour scroll/touch

Qualité du code

  • [ ] Aucune erreur en console
  • [ ] HTML valide (aucun ID dupliqué)
  • [ ] Éléments HTML sémantiques utilisés
  • [ ] Gestion d'erreur appropriée
  • [ ] Nettoyage de mémoire dans les composants

UX

  • [ ] Aucun interstitiel intrusif
  • [ ] Demandes de permission en contexte
  • [ ] Messages d'erreur clairs
  • [ ] Rapports d'aspect d'image appropriés

Outils

Outil Objectif
npm audit Vulnérabilités de dépendances
SecurityHeaders.com Analyse des en-têtes
W3C Validator Validation HTML
Lighthouse Audit des bonnes pratiques
Observatory Scan de sécurité

Références

Skills similaires