Architecture Overview¶
Navigation: Documentation Home → Architecture → Overview
Table of Contents¶
Introduction¶
This is a modern portfolio website built for Simon Stijnen, showcasing projects, achievements, and technical skills. The application is designed with performance, maintainability, and developer experience as top priorities.
Project Goals¶
- Performance: Optimized for speed with Next.js 15 static generation and Turbopack
- Maintainability: Content-driven architecture with JSON data sources
- Scalability: Modular component design with shadcn/ui
- Developer Experience: TypeScript-first with comprehensive tooling
- SEO & Discoverability: Structured data, OpenGraph, and LLM-readable content
Target Audience¶
This documentation serves:
- Developers joining or contributing to the project
- Employers & Clients evaluating technical capabilities
- Future maintainers understanding architectural decisions
Tech Stack¶
Core Framework¶
Next.js 15 with App Router
- Static site generation (SSG) for optimal performance
- React Server Components by default
- Turbopack for blazing-fast development builds
- Standalone output mode for Docker deployment
```1:19:/workspaces/website/next.config.ts import type { NextConfig } from "next"; import bundleAnalyzer from "@next/bundle-analyzer";
// Import site configuration for URL parsing import { siteConfig } from "./lib/config";
// Extract domain from site URL const siteUrlDomain = new URL(siteConfig.url).hostname;
// Common domains for images const commonDomains = [ "github.githubassets.com", "github.com", "colorsplash.vercel.app", "flagcdn.com", ]; const domains = ["localhost", siteUrlDomain, ...commonDomains];
const nextConfig: NextConfig = { images: {
### Frontend Stack
| Technology | Purpose | Version |
| ------------------ | ----------------------------------------------- | ---------- |
| **React 19** | UI library | `^19.0.0` |
| **TypeScript 5** | Type safety | `^5` |
| **Tailwind CSS 4** | Utility-first styling | `^4` |
| **shadcn/ui** | Component library (New York style, Slate theme) | Various |
| **Radix UI** | Accessible primitives | `^1.x` |
| **Lucide React** | Icon system | `^0.513.0` |
### Data & State Management
- **Content Storage**: JSON files in `content/` directory
- **Type Definitions**: TypeScript interfaces in `lib/` directory
- **Data Access**: Async server-side functions
- **Theme Management**: `next-themes` for dark/light mode
### UI Component Libraries
**shadcn/ui Components** (New York style, Slate base color):
- Button, Card, Badge, Dialog, Tabs, Carousel
- Navigation Menu, Tooltip, Dropdown Menu
- Table, Separator, Input
**Custom Components**:
- Skills Data Table with `@tanstack/react-table`
- Project & Achievement Cards
- Timeline component for experience display
### Developer Tools
| Tool | Purpose | Configuration |
| --------------- | ----------------- | ------------------- |
| **ESLint 9** | Code linting | `eslint.config.mjs` |
| **Prettier 3** | Code formatting | `.prettierrc.json` |
| **Husky** | Git hooks | `.husky/` |
| **lint-staged** | Pre-commit checks | `package.json` |
| **Jest 30** | Unit testing | `jest.config.js` |
| **TypeScript** | Type checking | `tsconfig.json` |
### Analytics & Monitoring
- **Vercel Analytics**: User analytics and insights
- **Vercel Speed Insights**: Performance monitoring
- **Custom Analytics Component**: Google Analytics & GTM integration
- **Structured Data**: Schema.org JSON-LD for SEO
### Deployment
- **Docker**: Production containerization with standalone Next.js build
- **Vercel**: Recommended hosting platform
- **Docker Compose**: Development and production orchestration
---
## Key Features
### 1. Content-Driven Architecture
All content is stored as JSON and dynamically rendered:
```1:28:/workspaces/website/content/projects/audionome.json
{
"title": "Audionome: Music Genre Classification",
"shortDescription": "Using a Support Vector Machine (SVM) to classify music clips based on their genre.",
"description": "For the AI Machine Learning course at VIVES University of Applied Sciences, I worked with Lynn Delaere on Audionome: an AI-powered system for music genre classification.\nWe trained several models (including logistic regression, SGD, and random forest) to automatically recognize and accurately classify music clips based on their genre. The project combines audio processing, machine learning, and a user-friendly interface built with Streamlit.",
"technologies": [
"AI",
"SVM",
"Streamlit",
"Python",
"Pandas",
"NumPy",
"Librosa",
"Scikit-learn"
],
"images": [
{
"src": "/images/projects/audionome/preview.jpg",
"alt": "Audionome application interface showing music genre classification"
},
{
"src": "/images/projects/audionome/quick-demo.webp",
"alt": "Demo screenshot of Audionome classifying a music track with SVM model results"
}
],
"demoUrl": "https://audionome.streamlit.app/",
"githubUrl": "https://github.com/SimonStnn/Audionome"
}
2. Dynamic Skills Generation¶
Skills are automatically extracted from project technologies:
```18:49:/workspaces/website/lib/skills.ts
export async function getSkills(): Promise
// Create a map to collect unique skills and their projects
const skillsMap = new Map
projects.forEach((project) => { project.technologies.forEach((tech) => { const skillId = generateSkillId(tech);
if (skillsMap.has(skillId)) {
// Add project to existing skill
const existingSkill = skillsMap.get(skillId)!;
if (!existingSkill.projects.includes(project)) {
existingSkill.projects.push(project);
}
} else {
// Create new skill
skillsMap.set(skillId, {
id: skillId,
name: tech,
projects: [project],
});
}
});
});
// Convert to array and sort by name return Array.from(skillsMap.values()).sort((a, b) => { return a.name.localeCompare(b.name); }); }
### 3. Type-Safe Data Layer
Strong TypeScript interfaces ensure data consistency:
```9:19:/workspaces/website/lib/projects.ts
export interface Project {
slug: string;
title: string;
shortDescription: string;
description: string;
technologies: string[];
images: ProjectImage[];
demoUrl?: string;
githubUrl?: string;
order?: number;
}
4. Static Generation with Dynamic Routes¶
Projects are statically generated at build time:
```20:27:/workspaces/website/app/projects/[slug]/page.tsx // Generate static params for all projects at build time export async function generateStaticParams() { const projects = await getProjects();
return projects.map((project) => ({ slug: project.slug, })); }
### 5. SEO & Structured Data
Comprehensive metadata and Schema.org structured data:
```23:75:/workspaces/website/app/layout.tsx
export const metadata: Metadata = {
metadataBase: new URL(siteConfig.url),
title: {
template: `%s | Software Engineer & AI`,
default: `${siteConfig.name} | Software Engineer & AI`,
},
description: siteConfig.description,
keywords: [
siteConfig.author.name,
"Software Engineer",
"AI",
"Artificial Intelligence",
"Machine Learning",
"Developer",
"Portfolio",
"Full Stack",
"TypeScript",
"Junior Software Engineer",
],
authors: [{ name: siteConfig.author.name }],
creator: siteConfig.author.name,
publisher: siteConfig.author.name,
robots: {
index: true,
follow: true,
},
openGraph: {
type: "website",
locale: "en_US",
url: siteConfig.url,
siteName: "Software Engineer & AI Portfolio",
title: `${siteConfig.name} | Software Engineer & AI`,
description: siteConfig.description,
images: [
{
url: "/images/profile-meta.jpg",
width: 1200,
height: 630,
alt: "Simon Stijnen - Software Engineer & AI Specialist",
},
],
},
twitter: {
card: "summary_large_image",
title: `Software Engineer & AI | ${siteConfig.name}`,
description: siteConfig.description,
images: ["/images/profile-meta.jpg"],
},
manifest: "/site.webmanifest",
other: {
"llms-txt": `${siteConfig.url}/llms.txt`,
},
};
High-Level Architecture¶
System Architecture Diagram¶
graph TB
subgraph "Client Browser"
UI[React UI Components]
Theme[Theme Provider]
end
subgraph "Next.js App Router"
Layout[Root Layout]
Pages[Page Components]
API[Server Components]
end
subgraph "Data Layer"
LibProjects[lib/projects.ts]
LibAchievements[lib/achievements.ts]
LibSkills[lib/skills.ts]
LibConfig[lib/config.ts]
end
subgraph "Content Storage"
ProjectJSON[content/projects/*.json]
AchievementJSON[content/achievements/*.json]
Images[public/images/]
end
subgraph "Component Library"
ShadcnUI[shadcn/ui Components]
CustomComponents[Custom Components]
end
subgraph "External Services"
VercelAnalytics[Vercel Analytics]
GA[Google Analytics]
SpeedInsights[Speed Insights]
end
UI --> Theme
UI --> ShadcnUI
UI --> CustomComponents
Theme --> Layout
Pages --> API
Layout --> Pages
API --> LibProjects
API --> LibAchievements
API --> LibSkills
API --> LibConfig
LibProjects --> ProjectJSON
LibAchievements --> AchievementJSON
LibSkills --> LibProjects
Pages --> Images
Layout --> VercelAnalytics
Layout --> GA
Layout --> SpeedInsights
style UI fill:#e1f5ff
style API fill:#fff4e1
style LibProjects fill:#e8f5e9
style LibAchievements fill:#e8f5e9
style LibSkills fill:#e8f5e9
style ProjectJSON fill:#f3e5f5
style AchievementJSON fill:#f3e5f5
Request Flow¶
sequenceDiagram
participant User
participant Browser
participant NextJS
participant DataLayer
participant ContentFiles
User->>Browser: Navigate to /projects/[slug]
Browser->>NextJS: Request page
NextJS->>NextJS: Check if page is statically generated
alt Page exists in build
NextJS-->>Browser: Return cached HTML
else Page needs generation
NextJS->>DataLayer: getProjectBySlug(slug)
DataLayer->>ContentFiles: Read JSON file
ContentFiles-->>DataLayer: Return project data
DataLayer-->>NextJS: Return typed Project
NextJS->>NextJS: Render React components
NextJS-->>Browser: Return generated HTML
end
Browser-->>User: Display page
Core Patterns¶
1. Server Components by Default¶
All pages and layouts use React Server Components for optimal performance:
``77:136:/workspaces/website/app/layout.tsx
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" className="scroll-smooth" suppressHydrationWarning>
<head>
{/* AI agent discovery: structured site info for LLMs */}
<link
rel="alternate"
type="text/plain"
href="/llms.txt"
title="LLM-readable site information"
/>
<Analytics />
<VercelAnalytics />
<SpeedInsights />
<PersonJsonLd
name={siteConfig.author.name}
url={siteConfig.url}
sameAs={[siteConfig.social.linkedin, siteConfig.social.github]}
jobTitle={siteConfig.person.jobTitle}
homeCountry={siteConfig.location.country}
worksFor={siteConfig.person.worksFor}
alumniOf={siteConfig.person.alumniOf}
hasCredential={siteConfig.person.hasCredential}
knowsAbout={siteConfig.person.knowsAbout}
email={siteConfig.author.email}
/>
</head>
<body
className={${geistSans.variable} ${geistMono.variable} selection:bg-primary/80 selection:text-accent flex min-h-screen flex-col antialiased`}
>