Un portfolio développeur est le signal le plus direct de ce que vous savez construire. Les recruteurs passent en moyenne 6 secondes sur un portfolio avant de décider de creuser ou de passer au suivant. Cela signifie que chaque section doit mériter sa place — hiérarchie claire, temps de chargement rapide, zéro superflu. Ce guide détaille la construction d'un portfolio développeur complet en React avec des sections pré-construites, du hero au formulaire de contact.
L'empilement de sections
Un portfolio développeur qui convertit suit une structure prévisible. Chaque section a un seul objectif :
- Hero — qui vous êtes, ce que vous faites, un CTA
- Vitrine de projets — 3 à 6 meilleurs projets en grid ou layout bento
- À propos / Compétences — parcours, stack technique, personnalité
- Timeline — expérience professionnelle et/ou formation, ordre antéchronologique
- Contact — formulaire ou liens directs, zéro friction
C'est cinq sections. Pas dix, pas deux. Cinq sections couvrent tout ce qu'un recruteur ou un client potentiel a besoin de voir. Construisons chacune.
1. Hero avec nom et titre
Le hero de portfolio est plus simple qu'un hero SaaS. Pas de barre de preuve sociale, pas de CTA secondaire, pas de badge. Juste votre nom, votre titre, et un appel à l'action :
interface PortfolioHeroProps {
name: string;
title: string;
description: string;
ctaLabel: string;
ctaUrl: string;
avatarUrl?: string;
}
export default function PortfolioHero({ name, title, description, ctaLabel, ctaUrl, avatarUrl }: PortfolioHeroProps) {
return (
<section
style={{
minHeight: "90vh",
display: "flex",
alignItems: "center",
padding: "var(--section-padding-y-lg) 0",
background: "var(--color-background)",
}}
>
<div style={{
maxWidth: "var(--container-max-width)",
margin: "0 auto",
padding: "0 var(--container-padding-x)",
display: "flex",
flexDirection: "column",
alignItems: "center",
textAlign: "center",
gap: "1.5rem",
}}>
{avatarUrl && (
<img
src={avatarUrl}
alt={name}
style={{ width: 96, height: 96, borderRadius: "50%", objectFit: "cover" }}
/>
)}
<h1 style={{
fontSize: "clamp(2.5rem, 5vw, 4rem)",
fontWeight: 700,
lineHeight: 1.1,
letterSpacing: "-0.03em",
color: "var(--color-foreground)",
}}>
{name}
</h1>
<p style={{
fontSize: "1.25rem",
color: "var(--color-accent)",
fontWeight: 500,
}}>
{title}
</p>
<p style={{
fontSize: "1.0625rem",
lineHeight: 1.7,
color: "var(--color-foreground-muted)",
maxWidth: "560px",
}}>
{description}
</p>
<a
href={ctaUrl}
style={{
display: "inline-flex",
alignItems: "center",
gap: "8px",
padding: "0.875rem 2rem",
borderRadius: "var(--radius-full)",
background: "var(--color-accent)",
color: "#fff",
fontWeight: 600,
fontSize: "0.9375rem",
textDecoration: "none",
}}
>
{ctaLabel}
</a>
</div>
</section>
);
}
Le CTA devrait pointer vers votre section projets (#projects) ou directement vers votre formulaire de contact (#contact) selon votre objectif. Si vous êtes en recherche d'emploi, "Voir mes projets" pointant vers les projets est le choix le plus fort.
2. Grille de projets
La grille de projets est la section la plus importante de votre portfolio. Utilisez un CSS Grid responsive avec 2 colonnes sur desktop et 1 sur mobile :
interface Project {
title: string;
description: string;
tags: string[];
imageUrl: string;
liveUrl?: string;
repoUrl?: string;
}
export function ProjectGrid({ projects }: { projects: Project[] }) {
return (
<section id="projects" style={{ padding: "var(--section-padding-y) 0" }}>
<div style={{
maxWidth: "var(--container-max-width)",
margin: "0 auto",
padding: "0 var(--container-padding-x)",
}}>
<h2 style={{ fontSize: "2rem", fontWeight: 700, marginBottom: "3rem" }}>
Projets sélectionnés
</h2>
<div style={{
display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 400px), 1fr))",
gap: "2rem",
}}>
{projects.map((project) => (
<article
key={project.title}
style={{
borderRadius: "var(--radius-lg)",
border: "1px solid var(--color-border)",
overflow: "hidden",
background: "var(--color-surface)",
}}
>
<img
src={project.imageUrl}
alt={project.title}
style={{ width: "100%", height: 240, objectFit: "cover" }}
/>
<div style={{ padding: "1.5rem" }}>
<h3 style={{ fontSize: "1.25rem", fontWeight: 600 }}>{project.title}</h3>
<p style={{
fontSize: "0.9375rem",
color: "var(--color-foreground-muted)",
lineHeight: 1.6,
marginTop: "0.5rem",
}}>
{project.description}
</p>
<div style={{ display: "flex", flexWrap: "wrap", gap: "0.5rem", marginTop: "1rem" }}>
{project.tags.map((tag) => (
<span
key={tag}
style={{
padding: "0.25rem 0.75rem",
borderRadius: "var(--radius-full)",
background: "var(--color-accent-subtle)",
color: "var(--color-foreground-muted)",
fontSize: "0.8125rem",
}}
>
{tag}
</span>
))}
</div>
</div>
</article>
))}
</div>
</div>
</section>
);
}
Sélectionnez impitoyablement. Trois projets excellents battent dix médiocres. Chaque carte de projet devrait avoir : une capture d'écran (pas un logo), une description en une phrase de ce que vous avez construit et pourquoi, et des tags technologiques. Mettez un lien vers le site live quand c'est possible — les recruteurs veulent cliquer et voir que ça fonctionne.
3. Section À propos et Compétences
La section à propos vous humanise au-delà d'une liste de projets. Restez concis — deux courts paragraphes maximum — et associez-la à une grille de compétences :
<div style={{
display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(120px, 1fr))",
gap: "1rem",
marginTop: "2rem",
}}>
{skills.map((skill) => (
<div
key={skill.name}
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "0.5rem",
padding: "1.25rem",
borderRadius: "var(--radius-md)",
border: "1px solid var(--color-border)",
}}
>
{skill.icon}
<span style={{ fontSize: "0.8125rem", fontWeight: 500 }}>{skill.name}</span>
</div>
))}
</div>
Montrez 8 à 12 compétences maximum. Si vous en listez 30, aucune ne ressemble à un point fort. Regroupez-les logiquement : langages, frameworks, outils. Utilisez des icônes reconnaissables (de lucide-react ou react-icons) au lieu de texte brut — le scan visuel est plus rapide.
4. Section Timeline
Une timeline antéchronologique de votre expérience professionnelle donne aux recruteurs les données structurées qu'ils cherchent. Le pattern visuel est une ligne verticale avec des nœuds :
interface TimelineItem {
date: string;
title: string;
company: string;
description: string;
}
export function Timeline({ items }: { items: TimelineItem[] }) {
return (
<div style={{ position: "relative", paddingLeft: "2rem" }}>
{/* Ligne verticale */}
<div
aria-hidden
style={{
position: "absolute",
left: "7px",
top: 0,
bottom: 0,
width: "2px",
background: "var(--color-border)",
}}
/>
{items.map((item) => (
<div key={`${item.company}-${item.date}`} style={{ position: "relative", paddingBottom: "2.5rem" }}>
{/* Point du nœud */}
<div
aria-hidden
style={{
position: "absolute",
left: "-2rem",
top: "4px",
width: "16px",
height: "16px",
borderRadius: "50%",
background: "var(--color-accent)",
border: "3px solid var(--color-background)",
}}
/>
<span style={{ fontSize: "0.8125rem", color: "var(--color-foreground-muted)" }}>
{item.date}
</span>
<h3 style={{ fontSize: "1.125rem", fontWeight: 600, marginTop: "0.25rem" }}>
{item.title}
</h3>
<p style={{ fontSize: "0.9375rem", color: "var(--color-accent)", fontWeight: 500 }}>
{item.company}
</p>
<p style={{
fontSize: "0.9375rem",
color: "var(--color-foreground-muted)",
lineHeight: 1.6,
marginTop: "0.5rem",
}}>
{item.description}
</p>
</div>
))}
</div>
);
}
Gardez les descriptions à 1-2 phrases chacune. Concentrez-vous sur l'impact ("Réduit le temps de chargement de 40%") plutôt que les responsabilités ("Responsable du développement frontend").
5. Formulaire de contact
La dernière section élimine les frictions. Un formulaire simple avec les champs nom, email et message suffit. Ne demandez pas le numéro de téléphone, la taille de l'entreprise ou le budget — ce n'est pas un formulaire de génération de leads SaaS :
<form
action="/api/contact"
method="POST"
style={{ display: "flex", flexDirection: "column", gap: "1rem", maxWidth: "480px" }}
>
<input
name="name"
placeholder="Nom"
required
style={{
padding: "0.75rem 1rem",
borderRadius: "var(--radius-md)",
border: "1px solid var(--color-border)",
background: "var(--color-surface)",
color: "var(--color-foreground)",
fontSize: "0.9375rem",
}}
/>
<input name="email" type="email" placeholder="Email" required style={{ /* mêmes styles */ }} />
<textarea
name="message"
placeholder="Message"
rows={5}
required
style={{ /* mêmes styles, resize: "vertical" */ }}
/>
<button
type="submit"
style={{
padding: "0.875rem 2rem",
borderRadius: "var(--radius-full)",
background: "var(--color-accent)",
color: "#fff",
fontWeight: 600,
border: "none",
cursor: "pointer",
}}
>
Envoyer le message
</button>
</form>
Ajoutez des liens sociaux (GitHub, LinkedIn, Twitter/X) à côté ou en dessous du formulaire. Certains visiteurs préfèrent envoyer un DM plutôt que remplir un formulaire.
Assembler le portfolio complet
Chaque section ci-dessus est indépendante et autonome. Déposez-les dans un seul composant de page dans l'ordre : Hero, Projets, À propos, Timeline, Contact. Ajoutez une navbar sticky avec des liens ancres (#projects, #about, #experience, #contact) et un footer, et vous avez un portfolio complet.
Au lieu de construire chaque section from scratch, parcourez le catalogue hero d'Incubator pour des variantes de hero soignées, le catalogue portfolio pour des layouts de vitrine de projets, le catalogue de sections about pour les designs de bio et compétences, et le catalogue contact pour les designs de formulaires. Si vous voulez un portfolio complet assemblé à partir de sections pré-construites, le constructeur de page portfolio vous permet de choisir des sections et d'exporter une page complète. Chaque section est TypeScript, Tailwind CSS v4 et Next.js 15 ready — collez, personnalisez votre contenu, et déployez.