Ahmed Abdelaziz
Back to projects
Portfolio screenshot

Portfolio

This website — a static, MDX-driven portfolio built with Next.js 16, TypeScript, Tailwind CSS, and shadcn/ui. Projects and skills are plain Markdown/MDX files discovered at build time, with first-class Design Docs for architecture and deep dives.

next.jsreacttypescripttailwindcssshadcn-uimdxnext-themesyamlmermaid

August 2026

Overview

This portfolio is a fully static site where projects and skills are written as MDX files. Any file dropped into projects/ is automatically discovered at build time and rendered — no code changes, no registry, no database. A git push triggers a rebuild on the hosting provider.

It is also documentation-aware: projects can attach multiple Design Docs (local MDX or notion links) that appear as a Design Docs button beside Source Code, with an index page and per-document rendering — all without a second content system.

Key capabilities:

  • Projects and skills authored as Markdown/MDX with YAML frontmatter.
  • Automatic project discovery via fast-glob + gray-matter.
  • Server-rendered MDX with a custom styled component map.
  • Light/dark theming with next-themes (follows system by default).
  • Mermaid diagrams rendered directly inside MDX, theme-aware.
  • Obsidian-style callouts with typed icons and theme tokens.
  • Design Docs for any project — local MDX or external Notion — via content/design-docs/<slug>/meta.yaml.
  • Build-time SEO: sitemap, robots, manifest, Open Graph, and JSON-LD.

Tech Stack

LayerChoice
FrameworkNext.js 16 (App Router, Server Components)
LanguageTypeScript (strict)
ContentMDX via next-mdx-remote (RSC) + yaml
Frontmattergray-matter + yaml
Discoveryfast-glob
StylingTailwind CSS v4 + shadcn/ui (Radix primitives)
Themingnext-themes
DiagramsMermaid (client-rendered, light/dark aware)
AnimationMotion (Framer Motion)
Iconslucide-react
DeployVercel

Project Structure

portfolio/
├── projects/                         # project content lives here
│   └── <project-folder>/
│       ├── <slug>.md / .mdx          # frontmatter + MDX body (auto-discovered)
│       └── banner.png                # co-located images (referenced relatively)
├── content/
│   ├── skills.mdx                    # skills shown on the home page
│   └── design-docs/
│       └── <project-slug>/
│           ├── meta.yaml             # DesignDoc[] for that project
│           ├── how-to-use.mdx        # local doc example
│           └── architecture.mdx
├── public/                           # site-wide static assets (logos, placeholders)
└── src/
    ├── app/                          # App Router routes + generated metadata
    │   ├── layout.tsx                # fonts, ThemeProvider, global metadata, JSON-LD
    │   ├── page.tsx                  # home page
    │   ├── projects/[slug]/page.tsx  # project detail page
    │   ├── projects/[slug]/design-docs/page.tsx        # Design Docs index
    │   ├── projects/[slug]/design-docs/[docId]/page.tsx # local doc page
    │   ├── project-assets/[...path]/route.ts  # serves co-located assets
    │   ├── sitemap.ts / robots.ts / manifest.ts
    │   └── opengraph-image.tsx / twitter-image.tsx
    ├── components/
    │   ├── Header.tsx / Footer.tsx
    │   ├── ProjectCard.tsx / ProjectGrid.tsx
    │   ├── DesignDocCard.tsx         # doc index card (local vs Notion)
    │   ├── Mermaid.tsx               # client-side Mermaid renderer
    │   ├── MermaidViewer.tsx
    │   ├── Callout.tsx
    │   ├── Reveal.tsx / Stagger.tsx / ThemeToggle.tsx
    │   └── ui/                       # shadcn/ui primitives
    └── lib/
        ├── config.ts                 # name, tagline, links, site URL
        ├── projects.ts               # glob -> parse -> typed Project + merge designDocs
        ├── design-docs.ts            # DesignDoc type, validation, loader
        ├── skills.tsx                # reads content/skills.mdx
        ├── mermaid.ts                # centralized Mermaid theme config
        ├── mdx.tsx                   # MDXRemote + styled component map + remark plugins
        └── utils.ts                  # cn(), formatDate()

How It Works

  1. Discoverysrc/lib/projects.ts runs fast-glob over projects/ and returns every .md and .mdx file, in any subfolder.
  2. Parsing — each file is passed through gray-matter; YAML frontmatter becomes the typed Project object, the rest becomes the MDX body. If the frontmatter contains designDocs[], it is validated (unique id, local or notion, URL check).
  3. Design Docs merge — for each slug, loadDesignDocsFromMeta() tries content/design-docs/<slug>/meta.yaml (via yaml). Frontmatter docs and YAML docs are merged; duplicate id throws at build time.
  4. Listing — the home page sorts projects by date (then order) and renders a ProjectCard grid. featured: true files go into a separate "Featured" section. Cards show a Docs button only when project.designDocs?.length > 0.
  5. DetailgenerateStaticParams() derives every slug up front; /projects/[slug] renders the body with next-mdx-remote (RSC) using a custom component map (src/lib/mdx.tsx). The Design Docs button beside Source Code links to the index.
  6. Assets — co-located files are referenced with relative paths in MDX. The remarkProjectAssets plugin rewrites them to /project-assets/<folder>/<file>, served straight from projects/.

Project File Schema

Add projects/<your-project>/<your-project>.md (or .mdx). Frontmatter:

---
title: My Project              # required — shown on card + page
slug: my-project               # optional — defaults to the file name
description: Short blurb.      # card + meta description
tags: [react, typescript]      # optional — rendered as badges
image: banner.png              # optional — card + hero image
github: https://github.com/…   # optional — "Source" link
demo: https://demo.example.com # optional — "Live Demo" link
featured: true                 # optional — pin to Featured on home page
visible: true                  # optional — hide from the site (defaults true)
date: 2026-08-01               # sort key, shown on the page
order: 1                       # tiebreaker when dates match
# optional — inline docs (or use content/design-docs/<slug>/meta.yaml)
designDocs:
  - id: architecture
    title: Architecture
    description: System design.
    type: local
    source: architecture.mdx
  - id: api
    title: API Design
    type: notion
    source: https://www.notion.so/your-page-id
    updatedAt: "2026-08-24"
---
Body content…                  # any MDX: headings, lists, code, <Component/>

Prefer content/design-docs/<slug>/meta.yaml for local docs so MDX files live next to their metadata. Frontmatter designDocs is ideal for a quick Notion link. Both sources merge — the UI just sees Project.designDocs[].

Design Docs

Design Docs are the portfolio's answer to "where is the architecture?" without turning every project into a docs site.

Adding docs to a project

  1. Create content/design-docs/<slug>/ (slug from projects/<folder>/<file>.md).
  2. Add meta.yaml:
designDocs:
  - id: how-to-use
    title: How to Use
    description: Two-minute setup.
    type: local
    source: how-to-use.mdx
  - id: architecture
    title: Architecture
    description: How it works.
    type: local
    source: architecture.mdx
  1. Add the MDX files (how-to-use.mdx, architecture.mdx) beside meta.yaml. They support the same MDX features as project pages — Mermaid, code, tables, callouts, images, custom components, TOC.
  2. For Notion:
- id: api
  title: API Design
  type: notion
  source: https://www.notion.so/your-page-id

No build-code changes needed. The next npm run build validates and emits /projects/<slug>/design-docs and /projects/<slug>/design-docs/<id> for local docs.

Source-agnostic

The index just receives DesignDoc[]. local renders MDX, notion links out. Adding github or pdf later means extending the union type, not the UI.

Validation

  • id unique within the project.
  • type must be local or notion.
  • notion requires a valid http(s) URL.
  • local requires the file at content/design-docs/<slug>/<source>.
  • updatedAt is optional, shown as Updated … when present.

This portfolio's own docs live at content/design-docs/portfolio/ — see How to Use and Architecture there for a live example.

Callouts

Documentation blocks support Obsidian-style callouts. A blockquote whose first line starts with [!<type>] is transformed at build time into a themed, icon-bearing callout — the type is parsed automatically and the body renders full Markdown (lists, links, code, tables, even nested callouts).

Syntax:

> [!warning] Title
> Body content with **Markdown** support.

Omitting the title falls back to the capitalized type name ([!tip] → "Tip"), and unknown types fall back to note styling. Icons come from Lucide; colors are design tokens defined per theme in src/app/globals.css.

TypeIconUse for
noteStickyNoteGeneric notes
infoInfoNeutral, helpful context
tipLightbulbHints and best practices
warningTriangleAlertCaution — things that can go wrong
dangerCircleAlertCritical risks and destructive actions
successCircleCheckConfirmations and completed outcomes
questionCircleHelpFAQs and open questions
bugBugKnown issues and regressions
exampleBookOpenWorked examples
quoteQuoteQuotations

Example output

Note

This is a plain note.

Info

Neutral context that is helpful but not critical.

Tip

Omit the custom title and the callout simply reads "Tip".

Watch out

Warnings use a soft orange tint in light mode and a darker one at night.

Danger zone

Critical information stands out without relying on color alone — each type also has its own icon and label.

Deployed

The build passed and shipped successfully.

Can callouts contain rich content?

Yes. Bodies support full Markdown:

  • Bold, italic, inline code, and links
  • Lists, tables, and fenced code blocks
const ready = true;

A second paragraph after a blank line.

Known issue

Callouts can flag regressions in release notes.

Multiline example

First paragraph of the example.

  • Step one
  • Step two

Quote

Documentation should be part of the product.

Callouts can be nested by indenting an inner blockquote:

Do callouts nest?

Yes — place another callout inside the body:

Inner callout

Fully themed, with its own icon and tokens.

Adding a New Project

  1. Create projects/my-new-project/ and add my-new-project.md (or .mdx) with the frontmatter above.
  2. Drop screenshots/documents next to the file and reference them with relative paths — no need to touch public/.
  3. Optional: add content/design-docs/my-new-project/meta.yaml + MDX files for deep dives.
  4. Run npm run dev and open the project page. When ready, commit and push — the hosting provider rebuilds and ships it automatically.

Theming

Design tokens live in src/app/globals.css (Tailwind v4 + shadcn variables). ThemeProvider in layout.tsx uses next-themes with defaultTheme="system", so dark mode follows the device; ThemeToggle.tsx overrides to light/dark explicitly. Mermaid diagrams adapt automatically: the diagram theme is mapped from the page theme in src/lib/mermaid.ts and re-renders on theme change without a reload. The view-transition API powers the theme toggle's circular wipe.

SEO & Metadata

Generated at build time by Next.js route conventions:

  • sitemap.ts/sitemap.xml — rebuilt every build; one URL per project and per Design Doc page.
  • robots.ts/robots.txt — allows crawling, points to the sitemap.
  • manifest.ts/manifest.webmanifest — PWA metadata.
  • Open Graph / Twitteropengraph-image.tsx + twitter-image.tsx generate default share images; project and doc pages set per-page openGraph metadata.
  • JSON-LDPerson (layout), WebSite + ProfilePage (home), Article (project and doc pages) via src/components/JsonLd.tsx.

Set NEXT_PUBLIC_SITE_URL to the production URL — it is used for the sitemap, canonical URLs, Open Graph, and JSON-LD.

Performance & Accessibility

  • Static generation for every project and doc (SSG via generateStaticParams).
  • Reveal/Stagger animations respect prefers-reduced-motion.
  • TOC via TableOfContents with DeepLink highlight and scroll-spy.
  • Keyboard-navigable cards and buttons, visible focus rings, aria-label for external Notion links ("opens in new tab").
  • next/image for hero images, loading="eager" + fetchPriority="high" for the first card.

Commands

CommandPurpose
npm run devLocal dev server (hot reload)
npm run buildType-check + static build
npm startServe the production build
npm run lintESLint