Detailed technical architecture of the site: MDX blog system, dynamic technology pages and optimizations with React cache
Publié le 28 août 2025
• Mis à jour le 17 septembre 2025
How this website was designed (before internationalization...)
This portfolio/blog site represents what I consider the best compromise for a developer-maintained website. Beyond the visual aspect, it implements interesting technical solutions that I will detail in this article.
Edit: Since writing this article, I added internationalization to offer this site entirely in English, which complicated certain parts of the code, especially the blog system. However, the basic principles remain the same.
Chosen technical stack
The site is based on a modern and performant stack:
- Next.js 15 with App Router and Turbopack
- Chakra UI v3 for the design system
- TypeScript in strict mode
- React 19 with the latest optimizations
- MDX for the blog system
- pnpm as package manager
📝 Blog system with MDX
One of the most interesting parts of the site is its blog system based on MDX and organized by categories.
File system-based architecture
data/blog/
├── projet/
│ └── conception-du-site.mdx
└── [category]/
└── [slug].mdx
Each folder represents a category, and each .mdx
file is an article with its frontmatter:
---
title: How this website was designed
publishedDate: 2025-08-28
description: Detailed technical architecture...
published: true
---
Reading and parsing articles
The core of the system is in lib/mdx.ts
with several optimized functions:
// Retrieving all categories
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();
});
// Retrieving all posts
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();
);
});
Static route generation
Article pages use Next.js static generation:
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map((post) => ({
slug: post.slug,
}));
}
Custom MDX components
Article rendering uses custom Chakra UI components:
// 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} />,
// ...
};
🛠 Technology pages system
The most sophisticated system on the site is the technology pages, entirely dynamic and based on JSON files.
JSON architecture
Each technology is defined in a JSON file in data/technologies/
:
// data/technologies/nextjs.json
{
"name": "Next.js",
"slug": "nextjs",
"icon": "SiNextdotjs",
"color": "#000000",
"darkColor": "#ffffff",
"category": "js-ts",
"description": "Full-stack React framework with SSR, SSG...",
"showOnHomepage": true,
"sortOrder": 4,
"pageContent": {
"header": "Full-stack React framework...",
"sections": [
{
"title": "Why Next.js?",
"content": [
{
"type": "text",
"text": "Next.js elevates React to the next level..."
},
{
"type": "list",
"title": "Key points",
"items": [
{
"label": "SSR/SSG",
"text": "Server rendering and static generation"
}
]
}
]
}
]
}
}
Dynamic icon system
Icons are managed via a string → React component mapping:
// 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,
};
}
Data reading and transformation
The reading system uses intensive React cache:
// 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);
// Sort by category then by 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;
});
});
Dynamic detailed pages
Each technology generates its own page with rich content:
// app/technologies/[slug]/page.tsx
export async function generateStaticParams() {
const slugs = getTechnologySlugs();
return slugs.map((slug) => ({ slug }));
}
The TechnologyPage
component dynamically renders content based on the JSON structure, supporting different content types (text, lists, etc.).
Smart filtering and sorting
The system supports multiple filtering levels:
- Homepage: only technologies with
showOnHomepage: true
- Technologies page: all technologies, sorted by category and
sortOrder
- By category: filtering via
getTechnologiesByCategory()
⚡ Optimizations with React Cache
One of the key optimizations of the site is the intensive use of React's cache()
to avoid multiple file system reads.
React cache principle
import { cache } from 'react';
export const getAllPosts = cache(async (): Promise<PostMeta[]> => {
// This function will only be executed once
// per request, even if called multiple times
});
Usage examples
- In the blog system:
getAllPosts()
,getPostBySlug()
,getAllCategories()
- In the technology system:
getAllTechnologies()
,getTechnologyBySlug()
,getRawTechnologyBySlug()
This optimization is crucial because these functions are called:
- During static page generation
- In metadata (
generateMetadata
) - In rendering components
Without cache, a file could be read dozens of times during build.
Code quality
The project maintains high code quality with:
- ESLint with strict TypeScript/React rules
- Prettier for automatic formatting
- Husky + lint-staged for pre-commit hooks
Conclusion
This site illustrates how to combine modern technologies to create an architecture that is both performant and maintainable. The dynamic technology system and the MDX blog organized by categories offer maximum flexibility while retaining the advantages of static generation.
The optimizations with React cache show the importance of understanding Next.js mechanisms well to create truly performant sites.