Architecture technique détaillée du site : système de blog MDX, pages technologies dynamiques et optimisations avec React cache
Publié le 28 août 2025
Comment ce site a été conçu
Ce site portfolio/blog représente ce que je considère comme le meilleur compromis pour un site tenu par un développeur. Au-delà de l'aspect visuel, il implémente des solutions techniques intéressantes que je vais détailler dans cet article.
Stack technique choisie
Le site repose sur une stack moderne et performante :
- Next.js 15 avec App Router et Turbopack
- Chakra UI v3 pour le système de design
- TypeScript en mode strict
- React 19 avec les dernières optimisations
- MDX pour le système de blog
- pnpm comme gestionnaire de paquets
📝 Système de blog avec MDX
L'une des parties les plus intéressantes du site est son système de blog basé sur MDX et organisé par catégories.
Architecture basée sur le système de fichiers
data/blog/
├── projet/
│ └── conception-du-site.mdx
└── [category]/
└── [slug].mdx
Chaque dossier représente une catégorie, et chaque fichier .mdx
est un article avec son frontmatter :
---
title: Comment ce site a été conçu
publishedDate: 2025-08-28
description: Architecture technique détaillée...
published: true
---
Lecture et parsing des articles
Le cœur du système se trouve dans lib/mdx.ts
avec plusieurs fonctions optimisées :
// Récupération de toutes les catégories
export const getAllCategories = cache(async (): Promise<string[]> => {
const categories = await readdir(POSTS_DIRECTORY, { withFileTypes: true });
return categories
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name)
.sort();
});
// Récupération de tous les articles
export const getAllPosts = cache(async (): Promise<PostMeta[]> => {
const categories = await getAllCategories();
const posts: PostMeta[] = [];
for (const category of categories) {
const categoryPath = path.join(POSTS_DIRECTORY, category);
const files = await readdir(categoryPath);
const mdxFiles = files.filter((file) => file.endsWith('.mdx'));
for (const file of mdxFiles) {
const filePath = path.join(categoryPath, file);
const fileContent = await readFile(filePath, 'utf-8');
const { data } = matter(fileContent);
const frontmatter = data as PostFrontmatter;
const slug = file.replace(/\.mdx$/, '');
if (frontmatter.published) {
posts.push({
...frontmatter,
category,
slug,
fullSlug: slug,
});
}
}
}
return posts.sort((a, b) =>
new Date(b.publishedDate).getTime() - new Date(a.publishedDate).getTime() ;
);
});
Génération statique des routes
Les pages d'articles utilisent la génération statique de Next.js :
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map((post) => ({
slug: post.slug,
}));
}
Composants MDX personnalisés
Le rendu des articles utilise des composants Chakra UI personnalisés :
// components/blog/MDXComponents.tsx
export const MDXComponents: MDXRemoteProps['components'] = {
h1: (props) => <Heading as="h1" size="3xl" mb="6" mt="8" {...props} />,
h2: (props) => <Heading as="h2" size="2xl" mb="4" mt="8" {...props} />,
p: (props) => <Text mb="4" lineHeight="1.7" {...props} />,
code: (props) => <Code {...props} />,
// ...
};
🛠 Système de pages technologies
Le système le plus sophistiqué du site est celui des pages technologies, entièrement dynamique et basé sur des fichiers JSON.
Architecture JSON
Chaque technologie est définie dans un fichier JSON dans data/technologies/
:
// data/technologies/nextjs.json
{
"name": "Next.js",
"slug": "nextjs",
"icon": "SiNextdotjs",
"color": "#000000",
"darkColor": "#ffffff",
"category": "js-ts",
"description": "Framework React full-stack avec SSR, SSG...",
"showOnHomepage": true,
"sortOrder": 4,
"pageContent": {
"header": "Framework React full-stack...",
"sections": [
{
"title": "Pourquoi Next.js ?",
"content": [
{
"type": "text",
"text": "Next.js élève React au niveau supérieur..."
},
{
"type": "list",
"title": "Points clés",
"items": [
{
"label": "SSR/SSG",
"text": "Rendu serveur et génération statique"
}
]
}
]
}
]
}
}
Système d'icônes dynamique
Les icônes sont gérées via un mapping string → composant React :
// lib/technologies-config.ts
const ICON_MAP: Record<string, IconType | ComponentType<unknown>> = {
FaReact: FaReact,
SiNextdotjs: SiNextdotjs,
SiTypescript: SiTypescript,
// ...
};
export function transformTechnologyData(data: TechnologyData): Technology {
return {
...data,
icon: ICON_MAP[data.icon] || FaReact,
darkColor: data.darkColor || data.color,
showOnHomepage: data.showOnHomepage ?? false,
sortOrder: data.sortOrder ?? 999,
};
}
Lecture et transformation des données
Le système de lecture utilise intensivement le cache React :
// lib/technologies-server.ts
export const getAllTechnologies = cache((): Technology[] => {
const technologiesDir = getTechnologiesDir();
const files = fs.readdirSync(technologiesDir);
const jsonFiles = files.filter((file) => file.endsWith('.json'));
const technologies: Technology[] = jsonFiles
.map((file) => {
const slug = path.basename(file, '.json');
const data = getRawTechnologyBySlug(slug);
if (!data) return null;
return transformTechnologyData(data);
})
.filter((tech): tech is Technology => tech !== null);
// Tri par catégorie puis par sortOrder
const categoryOrder = ['js-ts', 'php', 'fundamentals', 'databases', 'tools'];
return technologies.sort((a, b) => {
const categoryDiff = categoryOrder.indexOf(a.category) - categoryOrder.indexOf(b.category);
if (categoryDiff !== 0) return categoryDiff;
const aSortOrder = a.sortOrder ?? 999;
const bSortOrder = b.sortOrder ?? 999;
return aSortOrder - bSortOrder;
});
});
Pages détaillées dynamiques
Chaque technologie génère sa propre page avec du contenu riche :
// app/technologies/[slug]/page.tsx
export async function generateStaticParams() {
const slugs = getTechnologySlugs();
return slugs.map((slug) => ({ slug }));
}
Le composant TechnologyPage
rend dynamiquement le contenu basé sur la structure JSON, supportant différents types de contenu (texte, listes, etc.).
Filtrage et tri intelligent
Le système supporte plusieurs niveaux de filtrage :
- Homepage : seules les technologies avec
showOnHomepage: true
- Page technologies : toutes les technologies, triées par catégorie et
sortOrder
- Par catégorie : filtrage via
getTechnologiesByCategory()
⚡ Optimisations avec React Cache
L'une des optimisations clés du site est l'utilisation intensive du cache()
de React pour éviter les lectures multiples du système de fichiers.
Principe du cache React
import { cache } from 'react';
export const getAllPosts = cache(async (): Promise<PostMeta[]> => {
// Cette fonction ne sera exécutée qu'une seule fois
// par requête, même si appelée plusieurs fois
});
Exemples d'utilisation
- Dans le système de blog :
getAllPosts()
,getPostBySlug()
,getAllCategories()
- Dans le système de technologies :
getAllTechnologies()
,getTechnologyBySlug()
,getRawTechnologyBySlug()
Cette optimisation est cruciale car ces fonctions sont appelées :
- Lors de la génération des pages statiques
- Dans les métadonnées (
generateMetadata
) - Dans les composants de rendu
Sans cache, un fichier pourrait être lu plusieurs dizaines de fois lors du build.
Qualité du code
Le projet maintient une qualité de code élevée avec :
- ESLint avec règles strictes TypeScript/React
- Prettier pour le formatage automatique
- Husky + lint-staged pour les pre-commit hooks
Conclusion
Ce site illustre comment combiner des technologies modernes pour créer une architecture à la fois performante et maintenable. Le système de technologies dynamique et le blog MDX organisé par catégories offrent une flexibilité maximale tout en conservant les avantages de la génération statique.
Les optimisations avec le cache React montrent l'importance de bien comprendre les mécanismes de Next.js pour créer des sites vraiment performants.