A personal portfolio site built with Angular 21, styled as a code-editor workspace. The entire application is statically generated (SSG) and served from a hardened NGINX container on Kubernetes.
Live: faisalkhan.dpdns.org
| Layer | Technology |
|---|---|
| Framework | Angular 21.2.7 (standalone components, signals, zoneless, OnPush) |
| Rendering | SSG via @angular/ssr — 12 static routes prerendered at build time |
| Styling | Scoped component CSS + global design tokens (src/styles.css) |
| Blog | Markdown files rendered via marked + highlight.js |
| Container | Multi-stage Docker — node:24-alpine builder, nginxinc/nginx-unprivileged:1.27-alpine runtime |
| Web Server | NGINX with gzip, granular cache policies, and full security headers |
| Language | TypeScript 5.9 (strict mode, no any) |
- Glass Workspace UI — Glassmorphism design system with
backdrop-filter, custom design tokens, dark/light mode - Static Site Generation — All routes prerendered at build time for instant load and SEO
- Blog Engine — Markdown posts with syntax highlighting, reading progress bar, prev/next navigation
- Adaptive Favicon — SVG favicon with dark/light mode support, multi-size ICO, apple-touch-icon, PWA manifest icons
- Full SEO — Per-route
<title>,og:*,twitter:*meta tags, canonical URLs,sitemap.xml,robots.txt - Accessibility — Skip-to-content link, WCAG AA focus styles, ARIA attributes on all interactive elements
- Security Headers — CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy
- Comprehensive Test Suite — Component and service tests with Vitest, run in CI on every PR
- Automated CI/CD — GitHub Actions quality gate, Release Please versioning, multi-arch Docker publish, Trivy scanning
git clone https://github.com/faisalkhan91/Fluxus.git
cd Fluxus
npm install
ng serve --port 4300Open http://localhost:4300/ in your browser.
- Node.js 22+ (or 24 LTS)
- npm
- Angular CLI 21+ (
npm install -g @angular/cli@latest) - Docker (for containerized builds)
npm run build:prodThis runs ng build --configuration production followed by node scripts/inject-blog-meta.mjs, which replaces default OG/Twitter/title tags in each prerendered blog HTML with post-specific values.
Build output goes to dist/fluxus/browser/ — 12 prerendered static routes as directories with index.html inside each.
docker build -t fluxus .docker run --rm -p 8080:8080 fluxusOpen http://localhost:8080/ in your browser.
The container runs as non-root (UID 101) on port 8080, ready for Kubernetes deployment with readOnlyRootFilesystem: true and all capabilities dropped.
The image includes a HEALTHCHECK that polls /healthz every 30 seconds, so Docker reports container health without relying on an external orchestrator.
Multi-architecture builds (linux/amd64, linux/arm64) are produced automatically on every tagged release.
Unit tests use Vitest with jsdom:
npm test # watch mode
npm test -- --watch=false # single run (CI)| Workflow | Trigger | What it does |
|---|---|---|
| CI Quality Gate | PR to main |
Lint, format check, type check, unit tests, production build |
| Release Please | Push to main |
Creates/updates a release PR with changelog and version bump |
| Docker Publish | v* tag (from Release Please merge) |
Multi-arch Docker build, GHCR push, Trivy scan, GitOps deploy |
Dependabot is configured for weekly updates across npm, Docker, and GitHub Actions dependencies, with grouping for Angular and ESLint packages.
All base images in the Dockerfile are pinned by SHA256 digest for reproducible builds. The CI workflows pin every action by commit SHA.
Project Structure
src/
├── app/
│ ├── core/
│ │ ├── services/ # BlogService, SeoService, ThemeService, etc.
│ │ └── shell/ # Main layout (sidebar + tab bar + content + mobile nav)
│ ├── features/
│ │ ├── hero/ # Landing page
│ │ ├── about/ # About section
│ │ ├── experience/ # Work timeline
│ │ ├── skills/ # Technical skills grid
│ │ ├── projects/ # Project showcase
│ │ ├── certifications/ # Certification cards
│ │ ├── contact/ # Contact form
│ │ ├── blog/ # Blog index + post viewer
│ │ └── not-found/ # Glitch terminal 404 page
│ ├── ui/ # Reusable components (glass-card, glow-button, sidebar, etc.)
│ └── shared/ # Shared utilities
├── assets/
│ ├── blog/
│ │ ├── posts.json # Blog post manifest
│ │ └── posts/ # Markdown blog posts
│ ├── icons/ # PWA icons, skill icons
│ └── images/ # Profile, portfolio, certification images
├── styles.css # Global design tokens
└── index.html # App shell with meta tags, font loading, favicon links
Routes
| Route | Component | SSG |
|---|---|---|
/ |
HeroComponent | Prerendered |
/about |
AboutComponent | Prerendered |
/experience |
ExperienceComponent | Prerendered |
/skills |
SkillsComponent | Prerendered |
/projects |
ProjectsComponent | Prerendered |
/certifications |
CertificationsComponent | Prerendered |
/contact |
ContactComponent | Prerendered |
/blog |
BlogComponent | Prerendered |
/blog/:slug |
BlogPostComponent | Prerendered (dynamic) |
** |
NotFoundComponent | Client-side (CSR fallback) |
Blog Authoring
Posts are Markdown files in src/assets/blog/posts/. Metadata lives in src/assets/blog/posts.json:
{
"slug": "my-post",
"title": "My Post Title",
"date": "2026-04-01",
"excerpt": "A short description.",
"tags": ["angular", "typescript"],
"readingTime": "5 min read"
}The post-build script scripts/inject-blog-meta.mjs injects post-specific OG/Twitter meta into each prerendered blog HTML.
Faisal Khan
- GitHub: @faisalkhan91
- LinkedIn: Faisal Khan
MIT — see LICENSE for details.
See CHANGELOG.md for a detailed history of releases.