Skip to content

Architecture Overview

Navigation: Documentation HomeArchitecture → 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 { const projects = await getProjects();

// 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`} > Skip to main content

{children}