Développement Frontend Senior
Modèles de développement frontend, optimisation des performances et outils d'automatisation pour les applications React/Next.js.
Table des matières
- Échafaudage de projet
- Génération de composants
- Analyse du bundle
- Patterns React
- Optimisation Next.js
- Accessibilité et tests
Échafaudage de projet
Générer un nouveau projet React ou Next.js avec TypeScript, Tailwind CSS et des configurations de bonnes pratiques.
Workflow : Créer un nouveau projet frontend
-
Exécutez le script d'échafaudage avec le nom de votre projet et le modèle :
python scripts/frontend_scaffolder.py mon-app --template nextjs -
Ajoutez des fonctionnalités optionnelles (auth, api, forms, testing, storybook) :
python scripts/frontend_scaffolder.py dashboard --template nextjs --features auth,api -
Accédez au projet et installez les dépendances :
cd mon-app && npm install -
Démarrez le serveur de développement :
npm run dev
Options de l'échafaudeur
| Option | Description |
|---|---|
--template nextjs | Next.js 14+ avec App Router et Server Components |
--template react | React + Vite avec TypeScript |
--features auth | Ajouter l'authentification NextAuth.js |
--features api | Ajouter React Query + un client API |
--features forms | Ajouter React Hook Form + validation Zod |
--features testing | Ajouter Vitest + Testing Library |
--dry-run | Prévisualiser les fichiers sans les créer |
Structure générée (Next.js)
mon-app/
├── app/
│ ├── layout.tsx # Layout racine avec polices
│ ├── page.tsx # Page d'accueil
│ ├── globals.css # Tailwind + variables CSS
│ └── api/health/route.ts
├── components/
│ ├── ui/ # Button, Input, Card
│ └── layout/ # Header, Footer, Sidebar
├── hooks/ # useDebounce, useLocalStorage
├── lib/ # utilitaires (cn), constantes
├── types/ # Interfaces TypeScript
├── tailwind.config.ts
├── next.config.js
└── package.json
Génération de composants
Générer des composants React avec TypeScript, des tests et des histoires Storybook.
Workflow : Créer un nouveau composant
-
Générer un composant client :
python scripts/component_generator.py Button --dir src/components/ui -
Générer un composant serveur :
python scripts/component_generator.py ProductCard --type server -
Générer avec des fichiers de test et d'histoire :
python scripts/component_generator.py UserProfile --with-test --with-story -
Générer un hook personnalisé :
python scripts/component_generator.py FormValidation --type hook
Options du générateur
| Option | Description |
|---|---|
--type client | Composant client avec 'use client' (par défaut) |
--type server | Composant serveur asynchrone |
--type hook | Hook React personnalisé |
--with-test | Inclure le fichier de test |
--with-story | Inclure une histoire Storybook |
--flat | Créer dans le répertoire de sortie sans sous-répertoire |
--dry-run | Prévisualiser sans créer de fichiers |
Exemple de composant généré
'use client';
import { useState } from 'react';
import { cn } from '@/lib/utils';
interface ButtonProps {
className?: string;
children?: React.ReactNode;
}
export function Button({ className, children }: ButtonProps) {
return (
<div className={cn('', className)}>
{children}
</div>
);
}
Analyse du bundle
Analyser package.json et la structure du projet pour identifier des opportunités d'optimisation du bundle.
Workflow : Optimiser la taille du bundle
-
Exécutez l'analyseur sur votre projet :
python scripts/bundle_analyzer.py /chemin/vers/projet -
Examinez le score de santé et les problèmes :
Score de santé du bundle : 75/100 (C) DÉPENDANCES LOURDES : moment (290Ko) Alternative : date-fns (12Ko) ou dayjs (2Ko) lodash (71Ko) Alternative : lodash-es avec tree-shaking -
Appliquez les corrections recommandées en remplaçant les dépendances lourdes.
-
Relancez en mode verbeux pour vérifier les motifs d'import :
python scripts/bundle_analyzer.py . --verbose
Interprétation du score du bundle
| Score | Note | Action |
|---|---|---|
| 90-100 | A | Le bundle est bien optimisé |
| 80-89 | B | Optimisations mineures disponibles |
| 70-79 | C | Remplacer les dépendances lourdes |
| 60-69 | D | Plusieurs problèmes nécessitent attention |
| 0-59 | F | Problèmes critiques de taille du bundle |
Dépendances lourdes détectées
L'analyseur identifie ces dépendances courantes lourdes :
| Paquet | Taille | Alternative |
|---|---|---|
| moment | 290Ko | date-fns (12Ko) ou dayjs (2Ko) |
| lodash | 71Ko | lodash-es avec tree-shaking |
| axios | 14Ko | Fetch natif ou ky (3Ko) |
| jquery | 87Ko | API DOM natives |
| @mui/material | Grand | shadcn/ui ou Radix UI |
Patterns React
Référence : references/react_patterns.md
Composés
Partager l'état entre des composants liés :
const Tabs = ({ children }) => {
const [active, setActive] = useState(0);
return (
<TabsContext.Provider value={{ active, setActive }}>
{children}
</TabsContext.Provider>
);
};
Tabs.List = TabList;
Tabs.Panel = TabPanel;
// Utilisation
<Tabs>
<Tabs.List>
<Tabs.Tab>Un</Tabs.Tab>
<Tabs.Tab>Deux</Tabs.Tab>
</Tabs.List>
<Tabs.Panel>Contenu 1</Tabs.Panel>
<Tabs.Panel>Contenu 2</Tabs.Panel>
</Tabs>
Hooks personnalisés
Extraire une logique réutilisable :
function useDebounce<T>(value: T, delay = 500): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
// Utilisation
const debouncedSearch = useDebounce(searchTerm, 300);
Props de rendu
Partager la logique de rendu :
function DataFetcher({ url, render }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url).then(r => r.json()).then(setData).finally(() => setLoading(false));
}, [url]);
return render({ data, loading });
}
// Utilisation
<DataFetcher
url="/api/users"
render={({ data, loading }) =>
loading ? <Spinner /> : <UserList users={data} />
}
/>
Optimisation Next.js
Référence : references/nextjs_optimization_guide.md
Composants serveur vs client
Utilisez les composants serveur par défaut. Ajoutez 'use client' uniquement lorsque vous avez besoin de :
- Gestionnaires d'événements (onClick, onChange)
- État (useState, useReducer)
- Effets (useEffect)
- API du navigateur
// Composant serveur (par défaut) - pas de 'use client'
async function ProductPage({ params }) {
const product = await getProduct(params.id); // Requête côté serveur
return (
<div>
<h1>{product.name}</h1>
<AddToCartButton productId={product.id} /> {/* Composant client */}
</div>
);
}
// Composant client
'use client';
function AddToCartButton({ productId }) {
const [adding, setAdding] = useState(false);
return <button onClick={() => addToCart(productId)}>Ajouter</button>;
}
Optimisation des images
import Image from 'next/image';
// Au-dessus de la ligne de flottaison - charger immédiatement
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority
/>
// Image responsive avec fill
<div className="relative aspect-video">
<Image
src="/product.jpg"
alt="Produit"
fill
sizes="(max-width: 768px) 100vw, 50vw"
className="object-cover"
/>
</div>
Motifs de récupération de données
// Récupération parallèle
async function Dashboard() {
const [user, stats] = await Promise.all([
getUser(),
getStats()
]);
return <div>...</div>;
}
// Streaming avec Suspense
async function ProductPage({ params }) {
return (
<div>
<ProductDetails id={params.id} />
<Suspense fallback={<ReviewsSkeleton />}>
<Reviews productId={params.id} />
</Suspense>
</div>
);
}
Accessibilité et tests
Référence : references/frontend_best_practices.md
Checklist d'accessibilité
- HTML sémantique : Utiliser des éléments appropriés (
<button>,<nav>,<main>) - Navigation clavier : Tous les éléments interactifs doivent pouvoir être focusés
- Étiquettes ARIA : Fournir des étiquettes pour les icônes et les widgets complexes
- Contraste des couleurs : Minimum 4.5:1 pour le texte normal
- Indicateurs de focus : États de focus visibles
// Bouton accessible
<button
type="button"
aria-label="Fermer la boîte de dialogue"
onClick={onClose}
className="focus-visible:ring-2 focus-visible:ring-blue-500"
>
<XIcon aria-hidden="true" />
</button>
// Lien de saut pour les utilisateurs clavier
<a href="#main-content" className="sr-only focus:not-sr-only">
Passer au contenu principal
</a>
Stratégie de test
// Test de composant avec React Testing Library
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('le bouton déclenche une action au clic', async () => {
const onClick = vi.fn();
render(<Button onClick={onClick}>Cliquez-moi</Button>);
await userEvent.click(screen.getByRole('button'));
expect(onClick).toHaveBeenCalledTimes(1);
});
// Test d'accessibilité
test('la boîte de dialogue est accessible', async () => {
render(<Dialog open={true} title="Confirmer" />);
expect(screen.getByRole('dialog')).toBeInTheDocument();
expect(screen.getByRole('dialog')).toHaveAttribute('aria-labelledby');
});
Référence rapide
Configuration Next.js courante
// next.config.js
const nextConfig = {
images: {
remotePatterns: [{ hostname: "cdnexamplecom" }],
formats: ['image/avif', 'image/webp'],
},
experimental: {
optimizePackageImports: ['lucide-react', '@heroicons/react'],
},
};
Utilitaires Tailwind CSS
// Classes conditionnelles avec cn()
import { cn } from '@/lib/utils';
<button className={cn(
'px-4 py-2 rounded',
variant === 'primary' && 'bg-blue-500 text-white',
disabled && 'opacity-50 cursor-not-allowed'
)} />
Patterns TypeScript
// Props avec enfants
interface CardProps {
className?: string;
children: React.ReactNode;
}
// Composant générique
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return <ul>{items.map(renderItem)}</ul>;
}
Ressources
- Patterns React :
references/react_patterns.md - Optimisation Next.js :
references/nextjs_optimization_guide.md - Bonnes pratiques :
references/frontend_best_practices.md - Bibliothèque de questions-forcées (grill de Matt Pocock) :
references/forcing_questions.md - Carte de composition (vers quel spécialiste bifurquer) :
references/composition_map.md
Hypothèses et critères de succès vérifiables (discipline Karpathy)
Avant que cette compétence ne génère un composant, ne recommande un framework ou n'audite un bundle, les quatre hypothèses suivantes DOIVENT être exposées.
- Appareil principal + réseau — mobile-4G, desktop-fibre, Android-bas-de-gamme, ou réseau-d'entreprise. Conduit chaque décision de performance.
- Objectif LCP en millisecondes — un nombre unique, pas "rapide". Conduit le budget du bundle et le choix de rendu.
- Dépendant du SEO ou protégé par authentification — conduit le rendu (SSR/SSG/RSC vs. SPA).
- Cible WCAG + responsable a11y nommé — AA, AAA, ou meilleur effort. Conduit l'investissement a11y et les portes CI.
Critères de succès vérifiables (Karpathy #4) — chaque recommandation doit inclure :
- Les cibles Core Web Vitals (LCP, INP, CLS) au p75 sur l'appareil principal
- Un budget de bundle JavaScript par route en Ko-gzip
- Un seuil minimum Lighthouse a11y + un seuil de performance
Si l'un de ces trois éléments n'est pas indiqué, la recommandation est incomplète — revenez à Q2 de la bibliothèque de questions-forcées.
L'outil scripts/frontend_decision_engine.py encode ces vérifications : il refuse de recommander un profil sans les quatre entrées d'hypothèses et affiche les seuils vérifiables pour le profil correspondant.
Profils de personnalisation
Quatre profils intégrés dans profiles/ calibrent chaque recommandation :
| Profil | Quand choisir | Objectif LCP (mobile-4G p75) | Budget de bundle |
|---|---|---|---|
next-app-router | SaaS orienté client, SEO + dynamique, RSC-first | 2000ms | 150 Ko-gzip / route |
remix-or-sveltekit | Mobile-4G principal, low-JS-first, amélioration progressive | 1500ms | 80 Ko-gzip / route |
vite-spa | Application protégée par auth, desktop/entreprise principal | 2500ms | 200 Ko init + 80 Ko / route |
astro-or-static | Marketing / docs / blog, quasi-absence d'écriture, SEO critique | 1200ms | 30 Ko JS / page |
Choisissez un profil via :
python scripts/frontend_decision_engine.py \
--primary-device mobile-4g --lcp-target-ms 2000 \
--seo-dependent true --auth-walled false --team-size 5
L'outil renvoie le profil le mieux adapté, le compromis du deuxième (si à moins de 15 %), les choix de stack, les anti-patterns à éviter sur ce profil, et les portes CI requises.
Pour ajouter un profil personnalisé (par exemple, les valeurs par défaut de votre organisation pour les outils internes) : copiez profiles/vite-spa.json vers profiles/<votre-org>.json et ajustez constraints + success_thresholds.
Carte de composition
Cette compétence ne réimplémente PAS le périmètre détenu par les spécialistes de niveau PUISSANT. Elle bifurque vers eux. Voir references/composition_map.md pour la table de routage complète. Bifurcations clés :
| Problématique | Bifurquer vers |
|---|---|
| Audit WCAG, contraste, lecteur d'écran | engineering-team/skills/a11y-audit/ |
| Profilage du bundle + perf d'exécution | engineering/skills/performance-profiler/ |
| Page d'accueil cinématographique / storytelling par défilement | engineering-team/skills/epic-design/ |
| Apple HIG (iOS / macOS / visionOS) | product-team/skills/apple-hig-expert/ |
| Revue Karpathy pré-commit | engineering/karpathy-coder/ |
| Grill d'architecture pré-vol | engineering/grill-me/ |
L'agent cs-frontend-engineer orchestre ces bifurcations via context: fork. Invoquez-le depuis un autre agent avec Agent({subagent_type: "cs-frontend-engineer", prompt: "..."}) ou via `/cs:frontend-rev...