Meilleures pratiques React SVG : Guide complet de toutes les méthodes d'implémentation
Guide complet pour utiliser SVG dans React - comparant toutes les méthodes incluant la balise img, SVG inline, SVGR, bibliothèques d'icônes et imports dynamiques avec avantages, inconvénients et exemples réels
Meilleures pratiques React SVG : Guide complet de toutes les méthodes d'implémentation
SVG est essentiel pour les applications React modernes, mais il existe plusieurs façons de l'implémenter - chacune avec des compromis différents. Dans ce guide complet, nous explorerons toutes les méthodes, comparerons leurs performances et fournirons des exemples réels pour vous aider à choisir la meilleure approche.
5 façons d'utiliser SVG dans React
Méthode 1 : Balise <img>
Méthode 2 : SVG inline
Méthode 3 : SVGR (Import de composant)
Méthode 4 : Bibliothèques de composants d'icônes
Méthode 5 : Chargement dynamique SVG
Explorons chaque méthode en détail.
Méthode 1 : Balise <img>
Implémentation
// Usage simple
function Logo() {
return <img src="/logo.svg" alt="Logo de l'entreprise" width="200" height="50" />
}
// Avec import
import logoUrl from './logo.svg'
function Logo() {
return <img src={logoUrl} alt="Logo de l'entreprise" className="w-48 h-12" />
}
// TypeScript : ajouter à vite-env.d.ts ou globals.d.ts
declare module '*.svg' {
const content: string
export default content
}Avantages
✅ Méthode la plus simple - Aucune configuration spéciale nécessaire
✅ Mise en cache du navigateur - Les fichiers SVG sont mis en cache séparément
✅ Chargement différé - Utilisez loading="lazy" pour les performances
✅ Pas de gonflement du bundle - Le SVG reste comme fichier séparé
✅ Syntaxe familière - Fonctionne comme n'importe quelle autre image
// Exemple de chargement différé
<img
src="/hero-illustration.svg"
alt="Héros"
loading="lazy"
decoding="async"
/>Inconvénients
❌ Pas de style CSS - Ne peut pas changer les couleurs de remplissage/trait ❌ Pas de contrôle JavaScript - Ne peut pas animer des parties individuelles ❌ Requête HTTP supplémentaire - Un fichier de plus à télécharger ❌ Pas de comportement responsive - Ne peut pas masquer/afficher des parties selon la viewport ❌ Accessibilité limitée - Seulement le texte alt, pas de structure sémantique
// ❌ Cela ne fonctionnera pas avec la balise <img>
<img
src="/icon.svg"
className="fill-blue-500" // Ne changera pas la couleur SVG
/>Quand l'utiliser
✅ Logos statiques qui n'ont pas besoin de changements de couleur ✅ Grandes illustrations qui gonfleraient votre bundle ✅ Images décoratives qui n'ont pas besoin de détails d'accessibilité ✅ SVG externes depuis des CDNs ou sources tierces
Exemple de performance
// ✅ Bon : Grand SVG qui augmenterait la taille du bundle
function HeroSection() {
return (
<div className="hero">
<img
src="/hero-illustration.svg"
alt="Illustration du produit"
width="800"
height="600"
loading="lazy"
/>
</div>
)
}
// Taille du fichier : Le SVG reste séparé (ex: 45 Ko)
// Impact sur le bundle : 0 KoMéthode 2 : SVG inline
Implémentation
function HomeIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className="w-6 h-6"
>
<path d="M11.47 3.841a.75.75 0 0 1 1.06 0l8.69 8.69a.75.75 0 1 0 1.06-1.061l-8.689-8.69a2.25 2.25 0 0 0-3.182 0l-8.69 8.69a.75.75 0 1 0 1.061 1.06l8.69-8.689Z" />
<path d="m12 5.432 8.159 8.159c.03.03.06.058.091.086v6.198c0 1.035-.84 1.875-1.875 1.875H15a.75.75 0 0 1-.75-.75v-4.5a.75.75 0 0 0-.75-.75h-3a.75.75 0 0 0-.75.75V21a.75.75 0 0 1-.75.75H5.625a1.875 1.875 0 0 1-1.875-1.875v-6.198a2.29 2.29 0 0 0 .091-.086L12 5.432Z" />
</svg>
)
}Avantages
✅ Contrôle CSS complet - Changez les couleurs, animations, tout
✅ Pas de requêtes HTTP - Le SVG est dans votre bundle
✅ Manipulation JavaScript - Animez des parties, répondez aux événements
✅ Comportement responsive - Affichez/masquez des éléments selon l'état
✅ Accessibilité complète - Utilisez <title>, <desc>, attributs ARIA
✅ Support de thème - Utilisez currentColor pour le thémage
// ✅ Exemple de contrôle complet
function ThemedIcon({ className }: { className?: string }) {
const [isHovered, setIsHovered] = useState(false)
return (
<svg
viewBox="0 0 24 24"
className={className}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<title>Paramètres</title>
<desc>Cliquez pour ouvrir le panneau de paramètres</desc>
<path
fill={isHovered ? '#3B82F6' : 'currentColor'}
style={{ transition: 'fill 0.3s' }}
d="M12 2L2 7v10c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V7l-10-5z"
/>
</svg>
)
}Inconvénients
❌ Augmentation de la taille du bundle - Le code SVG est dans votre bundle JS ❌ Code verbeux - Les grands chemins SVG encombrent les composants ❌ Plus difficile à maintenir - Éditer le code SVG est fastidieux ❌ Pas de mise en cache du navigateur - Le SVG change quand le bundle change ❌ Répétition - Même icône utilisée plusieurs fois = code dupliqué
// ❌ Mauvais : Grand SVG gonfle le composant
function ComplexIllustration() {
return (
<svg viewBox="0 0 500 500">
{/* 200+ lignes de données de chemin */}
<path d="M123.45 67.89 L456.78..." />
<path d="M234.56 78.90 L567.89..." />
{/* ... 198 autres chemins */}
</svg>
)
}
// Taille du fichier : Ajoute 15 Ko au bundle
// Maintenabilité : Très faibleQuand l'utiliser
✅ Petites icônes (< 1 Ko) qui ont besoin de style ✅ SVG animés qui ont besoin de contrôle JavaScript ✅ Couleurs changeant fréquemment selon le thème/l'état ✅ SVG interactifs avec effets de survol/clic ✅ SVG critiques nécessaires immédiatement (pas de délai de chargement)
Optimisation des performances
// ✅ Bon : Extraire dans un composant séparé
const StarIcon = () => (
<svg viewBox="0 0 20 20" fill="currentColor">
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
</svg>
)
// Utiliser avec le thémage
function Rating({ stars }: { stars: number }) {
return (
<div className="flex text-yellow-400">
{Array.from({ length: stars }).map((_, i) => (
<StarIcon key={i} />
))}
</div>
)
}Méthode 3 : SVGR (Import de composant)
Configuration
1. Installer SVGR pour Vite
npm install vite-plugin-svgr2. Configurer vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import svgr from 'vite-plugin-svgr'
export default defineConfig({
plugins: [
react(),
svgr({
// Options SVGR
svgrOptions: {
icon: true, // Utiliser 1em comme taille par défaut
dimensions: false, // Supprimer les attributs width/height
typescript: true, // Générer les types TypeScript
},
}),
],
})3. Types TypeScript
// vite-env.d.ts
/// <reference types="vite-plugin-svgr/client" />
declare module '*.svg' {
import type { FunctionComponent, SVGProps } from 'react'
export const ReactComponent: FunctionComponent<SVGProps<SVGSVGElement>>
const src: string
export default src
}Implémentation
// Importer comme composant React
import { ReactComponent as Logo } from './logo.svg'
// Ou import nommé
import LogoIcon from './logo.svg?react'
function Header() {
return (
<div className="header">
<Logo className="w-32 h-8 text-blue-600" />
{/* Support complet des props */}
<Logo
width={128}
height={32}
fill="currentColor"
aria-label="Logo de l'entreprise"
/>
</div>
)
}Avantages
✅ Le meilleur des deux mondes - Séparation des fichiers + contrôle complet ✅ Auto-optimisation - SVGR nettoie le SVG à l'importation ✅ Support TypeScript - Sécurité de type complète avec SVGProps ✅ Code propre - Le SVG reste dans un fichier séparé ✅ Support des props - Passez className, fill, stroke, etc. ✅ Tree shaking - Les SVG non utilisés ne sont pas inclus dans le bundle ✅ Support currentColor - Thémage facile
// ✅ Sécurité de type avec autocomplétion
import { ReactComponent as Icon } from './icon.svg'
function MyButton() {
return (
<button className="flex items-center gap-2">
<Icon
className="w-5 h-5"
// TypeScript montre toutes les props SVG valides
aria-hidden="true"
focusable="false"
/>
Cliquez-moi
</button>
)
}Inconvénients
❌ Configuration de build nécessaire - Nécessite la configuration Vite/Webpack ❌ Taille du bundle - Le SVG est dans le bundle (comme inline) ❌ Courbe d'apprentissage - Comprendre la syntaxe d'import et la configuration ❌ Temps de build - Les SVG sont traités pendant le build
Quand l'utiliser
✅ Bibliothèques d'icônes que vous construisez ✅ Composants réutilisables dans votre application ✅ Projets type-safe avec TypeScript ✅ Outils de build modernes (Vite, Webpack, etc.) ✅ SVG de taille moyenne (1-10 Ko) ayant besoin de props
Exemple réel
// icons/index.ts - Exports d'icônes centralisés
export { ReactComponent as HomeIcon } from './home.svg'
export { ReactComponent as UserIcon } from './user.svg'
export { ReactComponent as SettingsIcon } from './settings.svg'
// components/Navigation.tsx
import { HomeIcon, UserIcon, SettingsIcon } from '@/icons'
function Navigation() {
const iconClass = "w-6 h-6 transition-colors"
return (
<nav className="flex gap-4">
<a href="/" className="group">
<HomeIcon className={`${iconClass} group-hover:text-blue-500`} />
</a>
<a href="/profile" className="group">
<UserIcon className={`${iconClass} group-hover:text-blue-500`} />
</a>
<a href="/settings" className="group">
<SettingsIcon className={`${iconClass} group-hover:text-blue-500`} />
</a>
</nav>
)
}Méthode 4 : Bibliothèques de composants d'icônes
Bibliothèques populaires
# Heroicons
npm install @heroicons/react
# Lucide
npm install lucide-react
# React Icons (agrégateur - inclut Font Awesome, Material, etc.)
npm install react-icons
# Phosphor Icons
npm install @phosphor-icons/reactExemples d'implémentation
Heroicons
import { HomeIcon, UserIcon } from '@heroicons/react/24/outline'
import { HomeIcon as HomeIconSolid } from '@heroicons/react/24/solid'
function Navigation() {
return (
<div className="flex gap-4">
{/* Version contour */}
<HomeIcon className="w-6 h-6 text-gray-600" />
{/* Version pleine */}
<HomeIconSolid className="w-6 h-6 text-blue-500" />
{/* Version mini (16x16) */}
<UserIcon className="w-4 h-4" />
</div>
)
}Lucide React
import { Heart, Star, ShoppingCart } from 'lucide-react'
function ProductCard() {
return (
<div className="card">
<Heart
size={24}
color="red"
fill="pink"
strokeWidth={1.5}
/>
<Star size={20} fill="gold" stroke="orange" />
{/* Contrôle absolu de la taille */}
<ShoppingCart
size={32}
className="cursor-pointer hover:text-blue-500"
onClick={() => console.log('Ajouter au panier')}
/>
</div>
)
}React Icons (Universel)
// Mélanger des icônes de différentes bibliothèques
import { FaGithub, FaTwitter } from 'react-icons/fa' // Font Awesome
import { MdEmail } from 'react-icons/md' // Material Design
import { IoLogoReact } from 'react-icons/io5' // Ionicons
function SocialLinks() {
return (
<div className="flex gap-3">
<FaGithub size={24} />
<FaTwitter size={24} className="text-blue-400" />
<MdEmail size={24} />
<IoLogoReact size={24} className="text-cyan-500" />
</div>
)
}Avantages
✅ Zéro configuration - Installez et utilisez simplement ✅ Design cohérent - Toutes les icônes correspondent ✅ Support TypeScript - Sécurité de type complète ✅ API Props - size, color, className, etc. ✅ Tree shaking - Seules les icônes importées dans le bundle ✅ Mises à jour régulières - De nouvelles icônes ajoutées fréquemment ✅ Documentation - Galeries d'icônes consultables
Inconvénients
❌ Taille du bundle - Chaque icône ajoute au bundle ❌ Dépendance - Package externe à maintenir ❌ Personnalisation limitée - Ne peut pas éditer les chemins des icônes ❌ Contraintes de style - Verrouillé au design de la bibliothèque
Quand l'utiliser
✅ Prototypage rapide - Commencez rapidement ✅ Système de conception cohérent - Besoin d'icônes correspondantes ✅ Motifs UI courants - Icônes standard (accueil, utilisateur, paramètres) ✅ Ne veut pas gérer les icônes - Laissez la bibliothèque gérer les mises à jour ✅ Projets TypeScript - Veut une sécurité de type complète
Méthode 5 : Chargement dynamique SVG
Implémentation
import { useState, useEffect } from 'react'
function DynamicSVG({ name }: { name: string }) {
const [svg, setSvg] = useState<string>('')
useEffect(() => {
// Importer dynamiquement le SVG
import(`./icons/${name}.svg?raw`)
.then(module => setSvg(module.default))
.catch(err => console.error('Échec du chargement du SVG :', err))
}, [name])
return <div dangerouslySetInnerHTML={{ __html: svg }} />
}
// Usage
function App() {
return <DynamicSVG name="home" />
}Avantages
✅ Chargement flexible - Chargez les icônes à la demande ✅ Réduire le bundle initial - Icônes chargées quand nécessaire ✅ Contenu dynamique - Chargez différentes icônes selon les données ✅ Mise en cache - Peut implémenter une stratégie de cache personnalisée
Inconvénients
❌ Complexité - Plus de code à maintenir
❌ Sécurité de type - Plus difficile à vérifier les types
❌ États de chargement - Besoin de gérer le chargement/erreur
❌ Risque XSS - Utilisation de dangerouslySetInnerHTML
❌ Requêtes supplémentaires - Surcharge réseau
Quand l'utiliser
✅ Contenu piloté par CMS - Les icônes viennent d'une API ✅ Systèmes de plugins - Icônes téléchargées par l'utilisateur ✅ Très grands ensembles d'icônes - Ne peut pas tout mettre en bundle ✅ Rendu conditionnel - Charger seulement si nécessaire
Tableau comparatif
| Méthode | Taille du bundle | Style | TypeScript | Mise en cache | Configuration | Idéal pour |
|---|---|---|---|---|---|---|
<img> | ✅ Aucune | ❌ Non | ⚠️ Limité | ✅ Oui | ✅ Facile | Logos statiques, grands fichiers |
| SVG inline | ❌ Augmente | ✅ Complet | ✅ Oui | ❌ Non | ✅ Facile | Petites icônes interactives |
| SVGR | ❌ Augmente | ✅ Complet | ✅ Complet | ❌ Non | ⚠️ Config requise | Composants réutilisables |
| Bibliothèques d'icônes | ⚠️ Par icône | ✅ Bon | ✅ Complet | ❌ Non | ✅ Facile | Développement rapide |
| Chargement dynamique | ✅ Minimal | ⚠️ Limité | ❌ Difficile | ✅ Personnalisé | ❌ Complexe | Contenu CMS |
Recommandations par cas d'usage
Petit projet / Page d'accueil
Utilisez : Bibliothèque d'icônes (Heroicons ou Lucide)
import { HomeIcon, UserIcon, Cog6ToothIcon } from '@heroicons/react/24/outline'
// Simple, rapide, aucune configuration nécessaireSystème de conception / Bibliothèque de composants
Utilisez : SVGR pour les icônes personnalisées
// Centralisé, type-safe, réutilisable
export { ReactComponent as BrandIcon } from './brand.svg'
export { ReactComponent as CustomIcon } from './custom.svg'Grande application
Utilisez : Combinaison
// Icônes : Bibliothèque (Heroicons)
import { HomeIcon } from '@heroicons/react/24/outline'
// Logo : balise <img> (mis en cache séparément)
<img src="/logo.svg" alt="Logo" />
// Illustrations personnalisées : SVGR
import { ReactComponent as Hero } from './hero-illustration.svg'Application critique pour les performances
Utilisez : balise <img> + chargement différé
<img
src="/illustration.svg"
loading="lazy"
decoding="async"
/>Conclusion
Guide de décision rapide
Choisissez <img> si :
- ✅ Grands fichiers SVG (> 10 Ko)
- ✅ Pas besoin de changements de couleur/style
- ✅ Logos ou illustrations statiques
Choisissez SVG inline si :
- ✅ Très petites icônes (< 500 octets)
- ✅ Besoin d'animations CSS
- ✅ Icônes personnalisées uniques
Choisissez SVGR si :
- ✅ Construction d'un système de conception
- ✅ Besoin du support TypeScript
- ✅ Icônes personnalisées réutilisables
Choisissez les bibliothèques d'icônes si :
- ✅ Développement rapide
- ✅ Icônes UI standard
- ✅ Ne veut pas gérer les icônes
Choisissez le chargement dynamique si :
- ✅ Contenu piloté par CMS
- ✅ Très grandes collections d'icônes
- ✅ Icônes depuis API/base de données
Recommandation finale
Pour la plupart des applications React modernes en 2025 :
- Bibliothèque d'icônes (Heroicons/Lucide) pour les icônes UI standard
- SVGR pour les icônes de marque personnalisées et illustrations
- Balise
<img>pour les grands SVG décoratifs
Cette combinaison offre le meilleur équilibre entre expérience de développement, performance et flexibilité.
Bon codage !