Retour au blog

Construire un portfolio développeur avec des sections React

Publié le 20 mars 2026·7 min read

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 :

  1. Hero — qui vous êtes, ce que vous faites, un CTA
  2. Vitrine de projets — 3 à 6 meilleurs projets en grid ou layout bento
  3. À propos / Compétences — parcours, stack technique, personnalité
  4. Timeline — expérience professionnelle et/ou formation, ordre antéchronologique
  5. 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.

VA

Victor Aubague

Développeur & créateur d'Incubator

Développeur full-stack spécialisé en React, Next.js et TypeScript. J'ai créé Incubator pour aider les développeurs à livrer de belles interfaces plus rapidement — tous les composants sont issus de vrais projets clients et de code en production.

LinkedIn
Construire un portfolio développeur avec des sections React — Incubator