Avertissement : Ce contenu a été traduit automatiquement. Envoyer un feedback

Site de portefeuille personnel

Un portfolio moderne, axé sur le contenu, construit avec Next.js 16, React 19 et TypeScript. Construit à partir de zéro avec un développement assisté par l'IA.

Role: Développeur et concepteurdéc. 2025

Pendant des années, je me suis dit que je créerais un site personnel. Le domaine a pris la poussière. L'idée n'a jamais quitté l'application des notes.

Puis j'ai arrêté de planifier et j'ai commencé à construire. Deux jours plus tard, ce site était en ligne.

Ce qui a changé

J'avais l'habitude de penser que la mise en ligne d'un site nécessitait des semaines de travail. Choix des polices. Déboguer les mises en page. Rédiger des textes qui ne semblaient jamais corrects. Cette fois-ci, j'ai adopté une approche différente : construire avec l'IA, livrer rapidement, améliorer plus tard.

Résultat : 51 validations en 48 heures. Un blog avec une narration audio. Un formulaire de contact qui fonctionne réellement. Un système de conception que je peux étendre. Pas parfait, mais réel.

Ce qui m'a surpris, ce n'est pas la vitesse. C'est l'impression que j'ai eue de travailler. Moins de recherche, plus de construction. Moins de remises en question, plus d'expéditions. Les outils s'occupaient des tâches fastidieuses, ce qui me permettait de me concentrer sur l'essentiel.

Cette page présente l'histoire complète. Comment cela s'est fait, ce qu'il y a sous le capot et ce que je ferais différemment la prochaine fois.

Cursor IDE
Cursor IDE avec l'assistant Claude

Tech Stack

CatégorieTechnologieVersionObjectif
FrameworkNext.js (App Router)16.0.10Composants serveur, routage, SSR
Bibliothèque UIReact19.2.3Architecture de composants
LangageTypeScript5.xSécurité des types
StylesTailwind CSS4.xStyles utility-first
Composantsshadcn/uiNew YorkComposants accessibles pré-construits
Contenunext-mdx-remote5.0.0Rendu MDX avec frontmatter
Parsinggray-matter4.0.3Extraction frontmatter
IcônesLucide React0.561.0Bibliothèque d'icônes SVG
EmailSendGrid8.1.6Livraison de formulaire de contact
AnalyticsGoogle Analytics 4Suivi du trafic et du comportement
AudioElevenLabs Audio NativeSynthèse vocale pour les articles de blog
Dependencies
Dépendances du package.json

Architecture

L'application suit les conventions de Next.js App Router avec une séparation claire entre les pages, les composants, le contenu et les utilitaires.

ftchvs_26/
├── app/ # Next.js App Router pages
│ ├─── layout.tsx # Root layout with fonts and nav
│ ├── page.tsx # Home page
│ ├── globals.css # Styles globaux et jetons de design
│ ├── about/ # About page
│ ├── blog/ # Liste des blogs et pages [slug].
│ ├── projets/ # Liste de projets et pages [slug].
│ ├── contact/ # Formulaire de contact avec route API
│ └─── resume/ # Page de CV
├── composants/ # Composants React
│ ├── nav.tsx # Navigation avec feuille mobile
│ ├── theme-toggle.tsx # Bascule mode sombre/lumineux
│ ├── tts-player.tsx # ElevenLabs audio player
│ ├── mdx-content.tsx # MDX renderer
│ └── ui/ # shadcn/ui components
├── content/ # MDX content files
│ ├─── blog/ # Articles de blog
│ ├─── pages/ # Pages statiques (environ)
│ └─── projets/ # Études de cas de projets
├── lib/ # Utilitaires
│ ├── content.ts # Chargement et analyse du contenu
│ ├── constants.ts # Configuration du site
│ ├─── schema.tsx # JSON-LD structured data
│ └── utils.ts # Fonctions d'aide
└── public/ # Static assets

Flux de données

Le contenu passe des fichiers MDX aux pages en passant par lib/content.ts :

  1. Les fichiers MDX stockent le contenu avec la matière première YAML
  2. matière grise analyse la matière première en objets typés
  3. next-mdx-remote sérialise le MDX pour un rendu React
  4. Composants de page récupère le contenu au moment de la demande
  5. composants MDX rendent le HTML final
GitHub Repo
Structure du dépôt GitHub

Typographie Système de conception

J'ai construit un système de typographie basé sur des jetons pour un style cohérent sur toutes les pages. Chaque jeton correspond à une classe spécifique de Tailwind.

TokenCSS ClassStylesUsage
Page Title.text-page-titletext-xl font-medium mb-6H1 headings
Section Title.text-section-titletext-lg font-medium mt-8 mb-4H2 headings
Subsection.text-subsection-titletext-base font-medium mt-6 mb-3H3 headings
Body.text-bodytext-[15px] leading-relaxed text-foreground/90Paragraphs
Body Container.text-body-containerspace-y-4 text-[15px] leading-relaxedMultiple paragraphs
Meta.text-metatext-[14px] text-muted-foregroundDates, tags, labels
Link.text-linkunderline underline-offset-2 hover:text-foregroundInline links
List Item.text-list-itemtext-sm text-foreground/80Card and list text

Pile de polices

  • Geist Sans** - Police de caractères principale pour le corps du texte et l'interface utilisateur.
  • Geist Mono** - Blocs de code et logo du terminal

Les deux polices se chargent via next/font/google avec display: swap pour des performances optimales.

Système de couleurs

Les couleurs utilisent l'espace colorimétrique OKLCH pour des ajustements perceptuels uniformes. Le système comprend :

  • Mode clair - Niveaux de gris neutres avec des accents subtils
  • Mode sombre** - arrière-plans profonds avec texte très contrasté
  • Couleurs sémantiques : primaires, secondaires, sourdes, accentuées, destructives, réussies.

Caractéristiques principales

Mode sombre

Les préférences de thème persistent dans localStorage et respectent les paramètres du système lors de la première visite. Un script de blocage dans <head> empêche le flash du mauvais thème.

``tsx


### Synthèse vocale


Le lecteur se charge dynamiquement pour éviter les problèmes d'hydratation :

```tsx
const TTSPlayer = dynamic(
  () => import('@/components/tts-player').then((mod) => mod.TTSPlayer),
  { ssr: false }
);

Créer un clone de voix sur ElevenLabs

Pour créer votre propre clone vocal pour le lecteur audio :

  1. Inscrivez-vous ou connectez-vous à [ElevenLabs] (https://elevenlabs.io).
  2. Accédez à la section Voix dans le dashboard (barre latérale gauche)
  3. Cliquez sur Ajouter une nouvelle voix
  4. Choisissez Clone vocal professionnel (recommandé pour une meilleure qualité) ou Clone vocal instantané (plus rapide, nécessite moins d'audio).
  5. Téléchargez des échantillons audio :
    • Clone de voix professionnel : Clone vocal professionnel** : au moins 1 heure d'audio de haute qualité (idéalement 2 à 3 heures pour de meilleurs résultats)
    • Clone vocal instantané** : Minimum 30 secondes d'audio propre
    • Divisez les longs enregistrements en échantillons d'environ 30 minutes pour faciliter le téléchargement.
    • Utilisez des enregistrements propres, de haute qualité, sans bruit de fond, écho ou sons indésirables.
  6. Possibilité d'enregistrer directement dans l'interface en utilisant Enregistrez-vous avec les exemples de scripts fournis.
  7. Nommez et étiquetez votre clone vocal
  8. Confirmez que vous avez le droit et l'autorisation de cloner la voix.
  9. Cliquez sur Enregistrer la voix pour entraîner le modèle.
  10. Une fois l'entraînement terminé, copiez l'ID de la voix à partir des paramètres de la voix.
  11. Configurez-le dans le projet en mettant à jour ELEVENLABS_VOICE_ID dans lib/constants.ts

Exemple de voix

Voici un échantillon de la voix clonée :

Échantillon de voix
0:00 / 0:00

Pour générer l'échantillon audio, vous pouvez utiliser le [ElevenLabs Text to Speech playground] (https://elevenlabs.io/app/speech-synthesis/text-to-speech). Sélectionnez votre voix clonée, entrez le texte, cliquez sur Generate, puis téléchargez et enregistrez le fichier sous le nom voice-sample.mp3 dans le répertoire public/audio/.

Narration audio du blog (Kokoro local)

Mise à jour — avr. 2026. Les billets du blog et la page About utilisent désormais Kokoro-82M, un modèle TTS sous licence Apache 2.0 (82M de paramètres, voix af_heart) exécuté localement sur CPU via kokoro-onnx. La génération est déterministe, gratuite et ne dépend d'aucune API externe. L'échantillon de voix ci-dessus est conservé comme la voix clonée originale d'ElevenLabs — seule la narration du blog a été migrée.

La configuration est à faire une seule fois : npm run setup:kokoro crée un dossier .kokoro/ ignoré par Git avec un venv Python et le modèle ONNX (~340 Mo). Ensuite, npm run generate-audio narre tout le contenu anglais et met à jour public/audio/manifest.json avec des hashes par billet. Sur un Apple M4 Max, le modèle tourne à environ 5× le temps réel — un billet de trois minutes se génère en une trentaine de secondes.

Formulaire de contact

Le formulaire de contact utilise SendGrid pour l'envoi des courriels avec :

  • Validation côté client
  • Route API côté serveur à /contact/api.
  • En-tête Reply-to pour les réponses directes
  • Gestion des erreurs avec des messages conviviaux

SEO

Chaque page comprend :

  • Métadonnées - Titre, description, mots-clés
  • Open Graph** - Images et texte de partage social
  • Twitter Cards** - formatage spécifique à Twitter
  • JSON-LD** - Données structurées pour la personne, le site web, l'article, le fil d'Ariane.
  • Sitemap** - Auto-généré à /sitemap.xml.
  • Robots** - Règles d'exploration des moteurs de recherche
Vercel Dashboard
Tableau de bord Vercel

Processus de construction

Phase 1 : Configuration du projet

Démarrage avec create-next-app en utilisant le modèle App Router. Ajout de TypeScript, Tailwind CSS 4, et configuration de shadcn/ui avec la variante de style New York.

``bash npx create-next-app@latest ftchvs_26 --typescript --tailwind --app npx shadcn@latest init


### Phase 2 : Mise en place du noyau

Nous avons construit la mise en page principale avec des polices Geist et une navigation réactive. La navigation comprend un menu de bureau et une feuille mobile (tiroir coulissant) provenant de shadcn/ui.

Le logo du terminal (`>_`) utilise une animation chatoyante qui s'adapte aux modes clair et foncé.

### Phase 3 : Système de contenu

Création de `lib/content.ts` pour gérer le chargement MDX :

- `getAllPosts()` - Récupère tous les articles du blog triés par date
- `getPostBySlug()` - Récupère un seul article avec un MDX sérialisé
- `getAllProjects()` - Récupère tous les projets
- `getProjectBySlug()` - Récupère un seul projet avec un MDX sérialisé
- `getPageBySlug()` - Récupère les pages statiques comme About

Chaque fonction lit le système de fichiers, analyse la matière première avec gray-matter, et sérialise le contenu avec next-mdx-remote.

### Phase 4 : Pages

Construction de six routes principales :

- **Home** - Introduction avec une liste d'écrits récents
- A propos** - Historique professionnel chargé à partir de MDX
- Blog** - Liste des articles et pages d'articles individuels
- Projets** - Présentation des projets (cette page)
- CV** - Expérience professionnelle et compétences
- Contact** - Formulaire avec intégration SendGrid

### Phase 5 : Intégrations

**SendGrid** : Route API configurée pour envoyer des courriels à partir du formulaire de contact. L'expéditeur doit être vérifié dans les paramètres d'authentification de SendGrid.

**Google Analytics 4** : Ajouté via `@next/third-parties` avec un chargement conditionnel basé sur `NEXT_PUBLIC_GA_ID`.


### Phase 6 : Conception du système

Implémentation des jetons de conception typographique dans `globals.css`. Ajout de variables de couleur OKLCH pour les modes clair et foncé. Création d'une animation de chatoiement avec les propriétés CSS personnalisées.

### Phase 7 : Référencement

Ajout de métadonnées à chaque page. Création des générateurs sitemap et robots.txt. Mise en œuvre de données structurées JSON-LD pour les schémas Person, WebSite, Article et BreadcrumbList.

### Phase 8 : Déploiement

Poussé sur GitHub et importé dans Vercel. Configuration des variables d'environnement :

- `NEXT_PUBLIC_SITE_URL` - URL canonique
- `NEXT_PUBLIC_GA_ID` - Google Analytics
- `SENDGRID_API_KEY` - Livraison par email
- `CONTACT_EMAIL_TO` - Adresse du destinataire
- `CONTACT_EMAIL_FROM` - Expéditeur vérifié

{/* TODO : Ajouter une capture d'écran - Déploiement de Vercel */}
<figure>
  <img src="/images/projects/portfolio/vercel-deployment.png" alt="Vercel Deployment" />
  <figcaption>Détails du déploiement Vercel</figcaption>
</figure>

---

## Changelog

Une chronologie détaillée du développement, extraite de l'historique des livraisons Git. Dernier en date.

### Statistiques de développement

- **Total commits** : 51
- **Temps de développement** : ~2 jours
- Principales fonctionnalités** : 8
- Corrections de bogues** : 12
- **Refactors** : 15
- **Mise à jour du style** : 16

---

### December 13, 2025 - Day 2 : Polish & Features

#### Nuit : Polissage final

| Le jour de l'inauguration de la nouvelle version de l'album, un nouveau site a été mis en ligne.
|--------|-------------|
| `20107f4` | **Majeur** : Amélioration complète du référencement.
| `e3ec3c2` | Simplification de l'intro de la page à propos `e3ec3c2` | Simplification de l'introduction de la page à propos
| `c21eb7b` | Configuration centralisée de l'identifiant vocal | `e3ec3c2` | Introduction simplifiée de la page d'accueil
| `9311bc6` | Configuration de la voix personnalisée ElevenLabs | `f8533bf` | Configuration de la voix personnalisée ElevenLabs
| `f8533bf` | **Majeur** : Optimisation d'ElevenLabs pour réduire les coûts d'API.
| `f8d31cf` | Positionnement du lecteur TTS au-dessus du résumé
| `7da002a` | Mise à jour de la typographie de la liste des blogs | `7da002a` | Mise à jour de la typographie de la liste des blogs
| `872a6ba` | Révision de l'article de blog pour plus de clarté et de narration | `7da002a` | Mise à jour de la typographie de la liste du blog

#### Soirée : Optimisation audio

| Commit | Description |
|--------|-------------|
| `fc9cea4` | Utilisation du ref pour insérer du HTML (correction de l'hydratation) |
| `fb9303f` | Utilisation d'une iframe pour l'Audio Native | `fb9303f` | Utilisation d'une iframe pour l'Audio Native
| `7384478` | Restauré le wrapper Suspense pour la compatibilité MDX |
| `ff9ca71` | **Majeur** : API Audio Native avec projets de contenu complets
| `76e6e08` | Suppression de Suspense pour Audio Native SSR'd
| `36c512c` | Chargement de l'Audio Native via useEffect | `5073332` | Chargement de l'Audio Native via useEffect
| `5073332` | ID d'utilisateur public codé en dur
| `ea4e5c7` | Utilisation de next/script pour le chargement de l'Audio Native
| `a5ee20` | Simplifié pour l'intégration de l'Audio Native
| `32fa6ca` | Ajout d'un script pour vider le cache audio
| `61382ca` | Ajout d'une mise en cache audio pour réduire les appels à l'API
| `0ab2855` | Pré-génération de l'audio TTS au chargement de la page
| `d59988f` | Ajout d'une journalisation de débogage pour le lecteur TTS

#### Fin d'après-midi : Sprint d'intégration audio

| Commit | Description |
|--------|-------------|
| `d018e8e` | Suppression du pied de page ElevenLabs du lecteur |
| `156eda5` | Rendre le sélecteur de vitesse plus minimaliste |
| `b87be4f` | Suppression de l'en-tête ElevenLabs du lecteur
| `1d6af2f` | Afficher immédiatement l'interface utilisateur complète du lecteur audio |
| `a90ebf` | Mise à jour du lecteur TTS avec les composants de l'interface ElevenLabs
| `738ebb4` | Remplacement de l'Audio Native par l'API TTS | `fa6fd42` | Mise à jour du lecteur TTS avec les composants d'interface ElevenLabs
| `fa6fd42` | Ajout de la configuration et des règles de l'IDE Cursor
``fa6fd42` | Ajout de la configuration et des règles de l'IDE Cursor | `5152aec` | Enveloppe de MDXRemote avec Suspense pour React 19
| `c7b54b2` | **Majeur** : Ajout de l'intégration de ElevenLabs Audio Native TTS.

#### Après-midi : Typographie et CV

| Le site web de l'entreprise est maintenant disponible en anglais et en français.
|--------|-------------|
| `2ff75d4` | Mise à jour de la déclaration d'apprentissage dans l'article de blog
| `0c9b17b` | Mise à jour de l'accroche du CV | `3e6b185` | Mise à jour du tiret dans l'article de blog
| `3e6b185` | Ajout d'un préfixe tiret aux éléments de compétences et d'éducation |
| `3371a03` | Suppression de la section "Looking For" du CV
| `649010d` | Amélioration de la typographie et de l'alignement du CV
| `f03743e` | Mise à jour de la taille des polices pour les éléments de la liste, les liens, le méta texte |

#### Fin de matinée : Stratégie de contenu

| Engagement | Description |
|--------|-------------|
| `c5d35e4` | Ajout d'un ciblage de l'audience à la page "À propos".
| `c5d35e4` | Ajout d'un ciblage de l'audience à la page À propos
| `b7a8cd8` | Mise à jour du README pour le nouveau positionnement
| `ae9f3a3` | Positionnement affiné du growth marketer sur l'ensemble des pages
| `701a36f` | Repositionnement du site en tant que "Growth Marketer who codes" | `db41419` | Repositionnement du site en tant qu' "Growth Marketer who codes" |
| `db41419` | Remplacement de la page des projets par un espace réservé "Coming Soon" |

#### Morning : Système de conception

| Description de la page
|--------|-------------|
| `4b7f5ff` | Mise à jour du README avec les tokens de typographie, SendGrid, la documentation GA4 |
| `d850b7e` | Menu nav réorganisé : A propos, Blog, Projets, CV, Contact |
| `a17536b` | **Majeur** : Conception typographique mise en place d'un système de jetons
| `a91398f` | Mise à jour du favicon avec l'icône de l'invite du terminal (`>_`)
| `c85a8c8` | Suppression de la bordure de l'anneau de mise au point sur le logo du terminal |
| `af50e11` | Refonte de l'animation de chatoiement pour le support du mode clair/obscur |

---

### December 12, 2025 - Jour 1 : Fondation

| Engagement | Description |
|--------|-------------|
| `0b4e0a2` | **Majeur** : Intégration de SendGrid pour le formulaire de contact avec une interface utilisateur améliorée.
| `5bf8d6a` | Correction de la sélection de texte sur le clic du logo du terminal |
| `2ab20b1` | Désactivation de la génération statique pour MDX (correction de la compatibilité avec React 19) |
| `0d1a813` | Ajout de pages, de composants et de fonctionnalités de base |
| `891648c` | Fusion de la documentation README
| `08bc5e0` | **Majeur** : Site portfolio Next.js initial avec blog et structure de projets.
| `7850991` | Initialisation du dépôt sur GitHub
`20aed18` | Initial commit from Create Next App | `20aed18` | Initial commit from Create Next App

**Jalons clés:**
- Structure complète de l'App Router avec 6 routes
- Système de contenu MDX avec analyse de la matière grise
- Intégration de la bibliothèque de composants shadcn/ui
- Formulaire de contact avec envoi d'email
- Navigation réactive avec tiroir mobile

{/* TODO : Ajouter une capture d'écran - Historique des livraisons GitHub */}
<figure>
  <img src="/images/projects/portfolio/commit-history.png" alt="Commit History" />
  <figcaption>Historique des livraisons de GitHub</figcaption>
</figure>

---

## Captures d'écran

<div className="bento-grid my-8">
  <div className="bento-item bento-large overflow-hidden">
    <img
      src="/images/projects/portfolio/homepage-light.png"
      alt="Page d'accueil claire"
      className="w-full h-full object-cover"
    />
    <figcaption className="image-caption">Mode clair de la page d'accueil</figcaption>
  </div>
  <div className="bento-item bento-medium bento-medium-top overflow-hidden">
    <img
      src="/images/projects/portfolio/homepage-dark.png"
      alt="Page d'accueil sombre"
      className="w-full h-full object-cover"
    />
    <figcaption className="image-caption">Mode sombre de la page d'accueil</figcaption>
  </div>
  <div className="bento-item bento-medium bento-medium-middle overflow-hidden">
    <img
      src="/images/projects/portfolio/mobile-nav.png"
      alt="Navigation mobile"
      className="w-full h-full object-cover"
    />
    <figcaption className="image-caption">Navigation mobile</figcaption>
  </div>
  <div className="bento-item bento-small bento-small-left overflow-hidden">
    <img
      src="/images/projects/portfolio/contact-form.png"
      alt="Formulaire de contact"
      className="w-full h-full object-cover"
    />
    <figcaption className="image-caption">Formulaire de contact</figcaption>
  </div>
  <div className="bento-item bento-small bento-small-center overflow-hidden">
    <img
      src="/images/projects/portfolio/blog-post.png"
      alt="Article de blog"
      className="w-full h-full object-cover"
    />
    <figcaption className="image-caption">Article de blog avec lecteur audio</figcaption>
  </div>
</div>

---

## Ce que j'ai appris

**Le développement assisté par l'IA est une réalité.** Cursor et Claude se sont occupés de la paperasse, ont détecté les erreurs et ont suggéré des modèles que je n'aurais pas trouvés seul. Le goulot d'étranglement est passé de la saisie à la réflexion.

**Les erreurs de type ont permis de détecter les bogues avant qu'ils n'atteignent le navigateur. Le temps d'installation initial a permis d'économiser des heures de débogage.

**Définir les classes de typographie une seule fois a permis d'obtenir un style cohérent sans avoir à prendre de décisions constantes. Les changements se propagent automatiquement.

**MDX est flexible.** Les schémas Frontmatter permettent au contenu de piloter l'interface utilisateur. L'ajout d'un nouveau champ ne prend que quelques minutes, pas de refactorisation.

---

## Prochaines étapes

- Ajouter d'autres projets en tant qu'études de cas
- Mise en place d'une recherche et d'un filtrage des articles de blog
- Ajouter un flux RSS pour les abonnés au blog
- Explorer l'ISR (Incremental Static Regeneration) pour les mises à jour de contenu

Outcomes

  • Construit à partir de zéro avec un développement assisté par l'IA (Cursor + Claude)
  • Mise en œuvre d'un système de jetons de conception typographique personnalisé
  • ElevenLabs Audio Native intégré pour la synthèse vocale
  • Configuration de SendGrid pour la fonctionnalité du formulaire de contact
  • Déployé sur Vercel avec un référencement complet

Links