Skip to main content

Building This Portfolio Website

ReactTypeScriptViteTailwind CSSFramer MotionVercelDocusaurus

A Question You've Probably Asked​

You're a software engineer.

You've shipped production backends, deployed cloud infrastructure, written hundreds of tests, passed a stack of professional certifications. You can design a distributed system, explain garbage collection in depth, and architect a microservices platform from scratch.

And yet β€” when a recruiter asks "Can I see your work?" β€” what do you send?

A PDF. A LinkedIn profile. Maybe a GitHub link with some green squares.

There's a better answer. And building it taught me more than I expected.


Why a Portfolio Website?​

Before writing a single line of code, the honest question is: why bother?

A CV lists what you claim to know. A portfolio demonstrates what you actually built. The difference matters β€” especially in a field where everyone has the same certifications and similar job titles.

Here's what I wanted to accomplish:

🎯 Show, don't list

Replace "proficient in React" with a live application built in React that recruiters can actually interact with.

🧠 Own the infrastructure

Control the domain, hosting, CDN, security headers, and deployment pipeline end-to-end.

πŸ“š Build a knowledge hub

Attach a living technical knowledge base that grows with my career β€” what you're reading right now.

⚑ Apply what I know

Use the same engineering practices I apply professionally: CI/CD, security headers, performance budgets, accessibility.

The result is what you're looking at: oussamazbair.engineer β€” a portfolio and engineering knowledge base, built the way I'd build any professional product.


Architecture Overview​

The system is split into two independent deployments that share a domain:

oussamazbair.engineer β†’ Portfolio (React SPA)
docs.oussamazbair.engineer β†’ Knowledge Base (Docusaurus)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Vercel Edge Network β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ oussamazbair.engineerβ”‚ docs.oussamazbair.engineer β”‚
β”‚ β”‚ β”‚
β”‚ React + Vite (SPA) β”‚ Docusaurus (Static Site) β”‚
β”‚ Tailwind + Framer β”‚ MDX + Custom CSS β”‚
β”‚ Motion β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
↑ ↑
GitHub repo Same GitHub repo
/ (root) /docs subfolder

Both sites live in the same GitHub repository, deployed as two separate Vercel projects pointed at different root directories. This keeps everything in one place while serving two distinct purposes.


The Tech Stack​

Portfolio β€” oussamazbair.engineer​

TechnologyVersionPurpose
React18UI component model, state management
TypeScript5.xType safety across the entire codebase
Vite8.xBuild tool β€” sub-second HMR, Rolldown bundler
Tailwind CSS3.xUtility-first styling, design tokens, dark mode
Framer Motion12.xDeclarative animations and page transitions
next-themes0.3Theme persistence (dark mode locked)
React Router v66.xClient-side routing with future v7 flags

Knowledge Base β€” docs.oussamazbair.engineer​

TechnologyPurpose
Docusaurus 3.10Static site generator optimized for documentation
MDXMarkdown + React components in the same file
Prism.jsSyntax highlighting (Java, TypeScript, Bash, YAML, Docker…)
Custom CSSPurple/dark theme matching the main portfolio palette

Design System​

Design Principles​

The portfolio follows four principles that I apply to production software as well:

  1. Dark-first β€” one mode, done right. Dark mode forced via forcedTheme="dark" at the ThemeProvider level and class="dark" on the <html> element from the start β€” no flash.

  2. Motion with purpose β€” every animation serves a function: it either guides attention, communicates state, or provides feedback. No animations for decoration alone.

  3. Content hierarchy β€” typography and spacing carry more weight than color. The design works in grayscale.

  4. Performance budget β€” the main bundle is code-split into vendor chunks (Three.js isolated, Framer Motion isolated, markdown renderer isolated). The first meaningful paint is under 1.5s on a mid-tier connection.

Color Palette​

/* Design tokens β€” HSL variables */
--primary: 262 83% 58%; /* Purple β€” primary actions, links, accents */
--accent: 340 82% 52%; /* Pink β€” secondary highlights */
--info: 185 95% 50%; /* Cyan β€” tech indicators */
--success: 142 76% 45%; /* Green β€” available status, success states */

/* Background layers */
--background: 240 10% 3.9%; /* Near-black page background */
--card: 240 10% 6%; /* Slightly lighter card surfaces */
--muted: 240 5% 15%; /* Subtle backgrounds */
--border: 240 5% 15%; /* Borders and dividers */

Typography​

/* Font stack */
font-family: 'Inter', system-ui, sans-serif; /* UI text */
font-family: 'Space Mono', 'Fira Code', monospace; /* Code, clocks, mono data */

/* Loaded via Bunny CDN β€” GDPR-compliant Google Fonts alternative */
@import url('https://fonts.bunny.net/css?family=inter:300,400,500,600,700|space-mono:400,700');

Notable Engineering Decisions​

Why Vite Over Next.js?​

The portfolio is a static SPA β€” there is no server-side rendering requirement, no dynamic data fetching at the server level, and no need for server components. Vite builds faster, the bundle is smaller, and the configuration is minimal.

Next.js would have added complexity without any measurable user benefit here.

Why Docusaurus for the Knowledge Base?​

A custom markdown renderer was the first approach. It worked β€” but maintaining a bespoke parser for tables, code blocks, nested lists, and syntax highlighting is not the best use of engineering time. Docusaurus is purpose-built for exactly this: MDX rendering, syntax highlighting, search, sidebar navigation, and responsive layout all work out of the box.

The right tool for the job beats the clever custom solution.

Why One Repository for Two Sites?​

  • Single source of truth for all content
  • One git push triggers both deployments
  • Shared commit history across portfolio and docs
  • No context switching between repositories

The tradeoff is a larger repository β€” acceptable for a personal project.

Live Clock and Geolocation​

The visitor greeting banner fetches the visitor's approximate location via geojs.io β€” a public, CORS-friendly, key-free IP geolocation API. The clock uses setInterval updating every second with the visitor's local timezone applied via Intl.DateTimeFormat.

No tracking, no analytics, no cookies. Just a friendly welcome.


Security Implementation​

Security on a static portfolio is mostly about hardening HTTP headers and avoiding exposure of credentials. Here is exactly what was implemented and why.

Content Security Policy​

Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline' https://fonts.bunny.net;
font-src 'self' https://fonts.bunny.net;
img-src 'self' data: https:;
connect-src 'self' https://api.emailjs.com https://get.geojs.io https://flagcdn.com https:;
frame-src https://calendly.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self' https://api.emailjs.com;

unsafe-inline and unsafe-eval are required by React (inline event handlers) and Vite's module system respectively. In a CSP-strict environment you would use nonces β€” acceptable for a personal portfolio, not for a financial application.

Security Headers​

X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), bluetooth=()

All headers are served via Vercel's vercel.json or the public/_headers file for Netlify-compatible deployments.

What Is Deliberately NOT Protected​

  • Right-click and DevTools blocking β€” this is security theater. Any developer can bypass it in under three seconds. More importantly, it actively degrades the experience for screen readers, power users, and accessibility tools. Removed.
  • Obfuscating JavaScript β€” the source code is open on GitHub. Obfuscation of a public repo adds no security.

What Actually Matters​

  • No secrets in the client bundle β€” EmailJS public keys are safe to expose (they are scoped to a specific domain). No private keys, database credentials, or internal API keys exist in the frontend.
  • HTTPS everywhere β€” enforced by Vercel. HSTS preload ensures browsers never make insecure requests.
  • No third-party tracking β€” no Google Analytics, no Facebook Pixel, no session recording. Zero telemetry sent to external services.

Performance​

Build Optimization​

The Vite build uses manualChunks to split the bundle into isolated vendor chunks:

manualChunks(id: string) {
if (id.includes('three') || id.includes('@react-three')) return 'vendor-three';
if (id.includes('framer-motion')) return 'vendor-motion';
if (id.includes('highlight.js') || id.includes('marked')) return 'vendor-markdown';
if (id.includes('react-dom') || id.includes('react-router')) return 'vendor-react';
if (id.includes('@radix-ui')) return 'vendor-radix';
}

This allows the browser to cache vendor chunks independently. When only the application code changes, users only re-download the index-*.js chunk, not the entire Three.js library.

highlight.js Tree Shaking​

The full highlight.js library is ~950 KB. By importing only the core and registering the 12 languages actually used (Java, TypeScript, Bash, YAML, JSON, SQL, Docker, Python, Kotlin, XML, Plaintext), the chunk drops to ~98 KB β€” a 90% reduction.

import hljs from 'highlight.js/lib/core'; // core only
import java from 'highlight.js/lib/languages/java';
hljs.registerLanguage('java', java);
// ... 11 more

Image Strategy​

Project screenshots are served from /public/projects/ as static files β€” no image CDN required for a portfolio. Images are lazy-loaded with loading="lazy". Projects with multiple screenshots use a CSS opacity cross-fade controlled by setInterval β€” no additional library needed.


The Knowledge Base Integration​

The docs site is linked from the portfolio in three places:

  1. Navbar β€” "Docs" link opens docs.oussamazbair.engineer in a new tab
  2. Hero section β€” "Engineering Docs" button in the profile card
  3. Writing section β€” dedicated card alongside the Medium blog link

The two sites share the same visual identity: purple primary color, Space Mono for code, Inter for prose, dark default mode.


Lessons Learned​

1. Start with the domain name. oussamazbair.engineer communicates profession instantly. The .engineer TLD is cheap and more memorable than .dev or .io for this use case.

2. One repository is simpler than two. The temptation is to create separate repositories. Resist it. A monorepo for a personal project reduces cognitive overhead significantly.

3. Docusaurus for documentation, custom code for everything else. The custom markdown renderer I built first worked β€” but Docusaurus renders MDX with tables, code blocks, admonitions, search, and sidebar navigation better than anything I would build myself in a reasonable time. Choose boring technology for problems that are already solved.

4. Security headers are free. Adding X-Frame-Options, HSTS, and a CSP takes thirty minutes. There is no reason not to do it on every project, regardless of size.

5. Animation frameworks need discipline. Framer Motion is powerful. It is also easy to overuse. Every animation was reviewed against one question: does this help the user understand something, or is it just moving pixels? Many animations were removed.

6. Visitor experience details compound. The live clock, flag image, localized greeting, typing animation, and scroll progress bar each take an hour to build. Together they make the portfolio feel alive rather than static. Small investments in UX compound.


Summary​

βš›οΈ
React 18 + TypeScript
UI framework
⚑
Vite 8 + Rolldown
Build tooling
🎨
Tailwind CSS
Styling
🎬
Framer Motion
Animations
πŸ“š
Docusaurus 3.10
Knowledge base
πŸš€
Vercel
Hosting + CDN
πŸ”’
CSP + HSTS
Security headers
🌍
geojs.io
Geolocation