Site personnel

Illustration du projet Illustration du projet
Publié le Mis à jour le

Versions

  1. 1.0 (10/2024)
  2. 1.1 (04/2025)

Statut

Terminé

Contexte du projet

La plupart des développeurs disposent d’un portfolio en ligne pour présenter leurs projets, compétences et expériences. J’en avais réalisé un en 2023, mais il ne correspondait plus à mes attentes. J’avais besoin d’une solution plus facile à maintenir, mais aussi plus complète et ambitieuse, un véritable site vitrine qui dépasse le simple affichage de quelques informations.

J’ai donc décidé de créer ce site personnel pour me présenter à vous. Bien qu’il soit principalement axé sur le développement web et logiciel, j’en profite également pour y exposer ma deuxième passion : l’écriture.


Fonctionnalités / améliorations futures

  • Tri des projets : J’envisage de mettre en place la possibilité de trier les projets pour faciliter la navigation.
  • Améliorations de l’accessibilité : Je souhaite rendre le site accessible à tous, notamment en améliorant la navigation au clavier.
  • Améliorations des articles : Je prévois de mieux structurer les articles de projet et possiblement de les étoffer.

Stack technique

Pour la réalisation de ce site, j’ai opté pour Astro, un framework moderne qui excelle dans la création de sites statiques ultra-performants. Bien qu’il s’agisse de ma première expérience avec ce framework, je l’ai trouvé très intuitif, et sa documentation bien structurée, m’a permis de l’adopter rapidement.

L’utilisation d’Astro m’a naturellement conduit à programmer en TypeScript, une première également pour moi. Ce projet m’a ainsi permis de découvrir cette version améliorée de JavaScript, avec son typage fort qui apporte une plus grande sécurité dans le développement.

Pour la mise en forme, mon choix s’est porté sur Tailwind CSS, un framework que j’ai désormais adopté pour la plupart de mes projets pour ses classes utilitaires et son haut degré de personnalisation.

Concernant la gestion des contenus, j’ai choisi de stocker les informations dans des fichiers JSON et MDX (une version améliorée du Markdown qui permet d’inclure du JSX). Cela me permet de modifier facilement les contenus sans toucher au code. L’idéal eut été de concevoir une base de données, mais il me fallait une solution rapide et économique.

Enfin, le site a été déployé sur Vercel, une plateforme de déploiement continue que j’apprécie pour sa simplicité et sa rapidité. En quelques clics seulement, le site est en ligne, ce qui optimise grandement le processus de développement et de mise à jour.


Organisation du code

L’organisation suit le modèle MVC (Modèle-Vue-Contrôleur) pour garantir une séparation claire des responsabilités et une maintenabilité optimale du code. La structure du projet est la suivante :

Nom du répertoire Responsabilité
Assets Stocke les images et les fichiers statiques du site.
Config Centralise les configurations de l'application, notamment les constantes et la gestion de la navigation.
Controller Contient la logique du site.
Content Contient les fichiers de contenu MDX pour le portfolio.
Repositories Centralise l'ensemble des données du site contenu dans des sous-dossiers « data » et « metadata ».
Types Définit les types et interfaces TypeScript utilisés dans le projet.
Pages Contient les « vues » du site.
UI Centralise les composants et les layouts d'interface réutilisables.
Utils Regroupe des fonctions utilitaires et des classes d'assistance pour le formatage des dates, et le mapping des données.

Développement

Rien n’est inscrit en dur sur le site, toutes les infos sont dynamiques !

Comme je l’ai signalé, je n’utilise pas de base de données pour ce projet. Pour autant, je me suis employée à structurer les données de manière à ce qu’elles soient faciles à manipuler et à maintenir dans les fichiers JSON. Par exemple, les icônes de technologies, visibles sur plusieurs pages, sont stockées dans un fichier JSON avec leurs noms, descriptions et classes associées. Cette approche facilite les modifications : en cas de changement, il suffit de mettre à jour le JSON, sans toucher au code ou à chaque fichier MDX des pages du site. Ainsi, dans chaque page, je ne passe qu’un tableau de noms d’icônes, et la logique en place se charge de récupérer les informations complètes depuis le JSON dédié.

Par exemple, pour cette page, le frontmatter du fichier MDX contient simplement :

languages: ["HTML", "CSS", "TypeScript"]
technologies: ["Tailwind CSS", "Astro", "JSON", "Markdown"]
tools: ["Figma", "NPM", "Git", "Vercel"]

Pour enrichir ces données avec les informations des icônes, j’utilise une fonction utilitaire qui effectue la recherche dans le JSON que je passe comme reference :

export const enrichIcons = (element: string, reference: any) => {
    const foundIcon = reference.icons.find((icon: Icon) => icon.name === element) as Icon;
    if (!foundIcon) throw new Error(`L'icone ${element} n'a pas été trouvé`);

    return foundIcon
};

Ce système évite les duplications de données et simplifie grandement la maintenance, rendant le code plus lisible et évolutif.

Affichage de tous les projets

Sur la page qui liste mes projets, y compris la sélection des trois projets phares, seules les technologies principales sont affichées. Les outils et langages associés à des frameworks, comme Spring pour Java, sont exclus pour ne conserver que les éléments les plus significatifs.

Pour cela, je stocke les associations entre langages et frameworks dans un autre fichier JSON :

{
    "CSS": ["Bootstrap", "Sass", "Tailwind CSS"],
    "Java": ["Spring"],
    "JavaScript": ["Astro","Preact", "React"],
    "PHP": ["Symfony"],
    "TypeScript": ["Angular", "Astro", "Preact", "React"]
}

Pour déterminer si un langage doit être affiché, j’utilise la fonction suivante. Cela permet d’exclure HTML, qui est toujours utilisé, et d’afficher un langage uniquement si aucun de ses frameworks n’est présent dans le projet.

export const shouldIncludeLanguage = (lang: Icon, technologies: string[]) => {
	if (lang.name === "HTML") return false;

	const associatedTechnologies = keyLanguagesTechnologies[lang.name as keyof typeof keyLanguagesTechnologies] || [];
	return !associatedTechnologies.some((tech) => technologies.includes(tech));
}

Cette fonction prend en paramètre un langage et un tableau de technologies. Elle commence par vérifier si le langage est HTML, auquel cas, elle renvoit false pour éviter l’affichage.

Ensuite, elle récupère les frameworks associés au langage en question depuis le fichier JSON keyLanguagesTechnologies. Ici, on utilise lang.name comme clef pour accéder aux technologies associées dans keyLanguagesTechnologies. Si aucune technologie n’est trouvée pour ce langage (par exemple, si keyLanguagesTechnologies[lang.name] retourne undefined), l’expression par défaut || [] garantit que associatedTechnologies sera au moins un tableau vide, évitant ainsi les erreurs lors de l’étape suivante.

La méthode some vérifie si au moins une technologie dans associatedTechnologies existe dans le tableau technologies. Si une technologie associée au langage est effectivement présente dans le tableau technologies, cela signifie que le projet utilise un framework ou une technologie spécifique liée à ce langage. Dans ce cas, la fonction retourne false, car il n’est pas nécessaire d’afficher ce langage indépendamment de ses frameworks associés.

Si aucune technologie associée n’est trouvée dans technologies, la fonction retourne true, indiquant que le langage peut être inclus, car il n’a pas de technologies associées dans ce contexte.

Enfin, je rassemble tous les projets depuis une collection Astro nommée portfolio et je traite les données comme suit pour afficher les projets et leurs icônes associées :

export async function getProjects() {
	const projects = await getCollection("portfolio");

	const allProjects = projects.map((project) => {
		const { technologies, languages, endDate, startDate } = project.data;

		return {
			...project,
			technologies: technologies.map((tech) => enrichIcons(tech, dataTechnologies)) as Icon[],
			languages: languages
				.map((lang) => enrichIcons(lang, dataLanguages))
				.filter((lang) => shouldIncludeLanguage(lang, technologies)) as Icon[],
			date: endDate || startDate as Date,
		};
	});

	return allProjects.sort((a, b) => b.date.getTime() - a.date.getTime());
}

Cette fonction est asynchrone avec le mot clef async, car elle fait appel à une collection Astro pour récupérer l’ensemble des projets que je stocke dans la variable projects. Le mot clef await signifie que le code attend la résolution de la promesse retournée par getCollection. Cela permet de s’assurer que projects contient les données avant de continuer.

Ensuite, je procède au traitement des données reçues en utilisant la méthode map qui permet de parcourir l’ensemble des projets. Pour chaque projet, je récupère les technologies et les langages utilisés, ainsi que les dates de début et de fin grâce à la déstructuration.

Après quoi, je retourne un objet qui contient l’ensemble des données du projet, enrichies des icônes correspondantes pour les technologies et les langages. Pour cela, je fais appel à la fonction enrichIcons que j’ai définie précédemment. J’applique cette fonction à chaque technologie et à chaque langage, et je filtre les langages pour ne conserver que ceux qui doivent être affichés.

J’ajoute un attribut date qui contient la date de fin du projet si elle est renseignée, sinon la date de début afin de pouvoir trier les projets par date un peu plus loin dans le code.

Et pour tout le reste de l’objet que je retourne, je conserve les données du projet initial en utilisant l’opérateur spread (...project).

Enfin, je retourne l’ensemble des projets triés par date de fin ou de début, selon ce qui est renseigné.