Building My Personal Website as a Product-Grade Platform

Published on January 23, 20249 min read read
Share:

Personal Website as a Full-Stack Platform

Introduction

Most personal websites are static portfolios: a homepage, an about page, and a blog powered by a third-party CMS.

I chose a different path.

I engineered my site as a full-stack publishing and operations platform — with a production-style dashboard, an immersive visual editor, structured content workflows, media management, analytics, access controls, and deployment-ready infrastructure from day one.

This isn’t a “it works on my machine” side project. It is a real system I can iterate, monitor, and extend — and this post documents exactly how it’s built.



1. Why This Architecture?

A personal site can be one of two things:

  • A one-time front-facing page you rarely touch, or
  • A long-term platform you iterate like a real product.

I built for option two. The design goals were clear from the start:

GoalWhat It Means in Practice
Fast public UXReader-first, SEO-first, server-rendered by default
Strong authoring workflowDashboard + visual editor, no redeploy needed for content changes
Operational controlHealth endpoints, audit logs, runtime error monitoring
ExtensibilityS3-compatible storage, integration hooks, site-scoped modules

The decision that shaped everything else:

Changing the menu or turning off a page section should not require a deploy.

This led to a configuration-as-data design — navigation, page sections, and layout controls all live in the database. The site behaves like a product, not a hardcoded template.


2. System Architecture

The platform has two distinct user flows.

Visitor (Public) Flow

Public traffic enters through Cloudflare, hits the Next.js App Router for server-side rendering using React Server Components, then reads from PostgreSQL via Prisma for content and RustFS for media assets. SEO signals (sitemap, RSS, Open Graph, JSON-LD) are all generated server-side.

Admin (Management) Flow

Admin traffic is gated by NextAuth session validation. Dashboard and Editor routes are kept intentionally separate — the Editor lives at /editor/* to provide a focused, distraction-free authoring context. All mutations go through typed API routes, which write to the database, trigger the media pipeline, and log to the audit trail.


3. Core Stack

LayerTechnologyWhy
FrameworkNext.js 16 (App Router)Server-first rendering, file-based routing, collocated API routes
LanguageTypeScriptEnd-to-end type safety across UI, API, and data access
UIReact 19 + Tailwind CSS 4 + design tokensScalable component system with semantic, consistent styling
Data AccessPrisma ORMType-safe queries, structured migrations, clear schema
AuthNextAuthSession management and route protection
Abuse ProtectionCloudflare Turnstile + rate limitingCAPTCHA on sign-in; per-endpoint limits on public APIs
ContentMarkdown + MDX pipelineRich article rendering with embedded dynamic blocks
MediaUpload + sharp optimizationImage resizing and format conversion on ingest
StoragePostgreSQL + RustFS (S3-compatible)Relational content in Postgres; binary assets in RustFS
ObservabilitySentry + health endpointsRuntime error capture and uptime diagnostics
TestingJest + PlaywrightUnit/integration tests + E2E smoke coverage
InfraDocker / Docker ComposeReproducible local environment and production deployment
SecurityCSP, strict headers, security.txtBrowser hardening and XSS/clickjacking protection

4. Public-Site Features (Reader Experience)

What the reader sees

  • Home, About, Blog, Contact, and configurable Custom Pages
  • Archive and tag-based blog exploration (/blog/tag/[tagSlug], /blog/archive)
  • Reading progress bar and sticky reading chrome
  • Table of Contents with scroll-linked active-section highlighting
  • Copy/share actions — Web Share API, social deep-links, URL copy fallback
  • Related post recommendations at article end

SEO & Discoverability

Every route generates:

Code
/sitemap.xml          → Full site map, auto-generated
/robots.txt           → Crawler rules
/feed.xml             → RSS (all posts)
/feed/tag/[tag]       → Per-tag RSS feeds

Plus per-route Open Graph, Twitter Cards, and JSON-LD structured data (Article / WebPage schema). Unpublished drafts can be shared via expiring preview tokens — no authentication needed for the reviewer.

Rendering Strategy

ModeWhere Used
Server Components (RSC)All content-heavy public pages
Client ComponentsInteractive islands only (TOC scroll, share actions)
ISR / Edge CacheBlog index, tag pages
Bundle analysisnpm run analyze for optimization

5. Dashboard & Admin Features (Operator Experience)

This is where the site behaves like a product.

Content Lifecycle

The post lifecycle mirrors a real editorial workflow:

Code
Draft → Review → Scheduled → Published → Archived

Key capabilities:

  • Post versioning — every save creates a snapshot; restore to any point
  • Scheduled publishing — set a future publishedAt, auto-promoted by the system
  • Notes/drafts separation — lightweight scratch pad separate from the formal post pipeline
  • Tag management — create, merge, and remove orphaned tags
  • Custom pages CRUD — arbitrary pages with ordering and visibility controls

Editing Surface

Two modes, each for a different purpose:

ModeUse Case
Builder modeDrag-and-drop sections, toggle visibility, reorder blocks
Raw markdown modeDirect authoring with a live preview

In-context editing covers Home, About, Contact, and all custom pages. Section ordering and show/hide controls write directly to the database — no redeploy required.

Operational Panels

  • Analytics dashboard — page views, event breakdown, per-path traffic
  • Audit log — records every mutation (useful for debugging unexpected changes)
  • Health checks/api/live (liveness, no DB) and /api/health (readiness + DB ping)
  • Media operations — upload, optimize, track usage, clean up orphans
  • Content find-and-replace — bulk edits across all posts (great for URL migrations)


6. Storage Architecture: Why RustFS?

The key storage decision was treating object storage as an interface, not an implementation.

The application uses the AWS S3 SDK as its only storage interface. Switching from MinIO (development) to RustFS (production) required exactly one thing: a configuration change. Zero application code was touched.

This is the interface abstraction principle applied to infrastructure:

Code to the interface, not the implementation.

RustFS was chosen for production because it is more lightweight than MinIO while remaining fully S3-compatible and self-hostable on a single VM — no distributed storage cluster required.


7. CI/CD, Verification, and Reliability

Every change goes through a composite quality gate before it reaches production.

Quality Gates

GateToolWhat It Catches
LintESLintCode style, unused vars, import order
Type checktsc --noEmitType errors, without emitting output
Unit testsJestLogic correctness, component behavior
Buildnext buildBundling errors, missing environment variables
E2E smokePlaywrightCritical user paths in a real browser
Security auditnpm auditKnown CVEs in dependencies (informational)

Playwright E2E Coverage

Current smoke tests cover:

  • / — Home page renders
  • /blog — Blog index loads
  • /contact — Contact form is interactive
  • /auth/signin — Auth entry renders correctly
  • /dashboard — Redirect behavior for unauthenticated users

Runtime Hardening

Secure HTTP headers configured in next.config:

Code
Content-Security-Policy   — prevents XSS and inline script injection
X-Frame-Options           — clickjacking protection
Referrer-Policy           — controls referrer leakage
Permissions-Policy        — restricts browser feature access
Strict-Transport-Security — HSTS toggle for HTTPS production

Sentry captures runtime exceptions in both server and client contexts, with source maps for readable stack traces in production.


8. Design System & UI Engineering

I recently completed a significant UI architecture uplift:

BeforeAfter
Hardcoded Tailwind color classesSemantic design tokens (--color-surface, --color-text-primary)
One-off page layoutsShared primitives: PublicPageShell, PublicSection, DashboardPageHeader
Inconsistent empty statesStandardized EmptyState component

Light-only design is intentional. Dark mode adds significant ongoing maintenance cost — every token needs a dark variant, every image needs consideration. For a personal site optimized for readability, the tradeoff clearly favors one well-crafted light theme.


9. What This Website Is (And Is Not)

This website isThis website is not
A personal brand surfaceA static one-time portfolio
A technical writing platformA no-code template
A lightweight CMS + visual editing systemA front-end-only project
A production-practice sandboxA throwaway prototype

10. What’s Next

Near-Term

  • Expand Playwright smoke suite across more dashboard and editor paths
  • Add stricter accessibility checks in CI (axe-core)
  • Monitor Core Web Vitals continuously — LCP and CLS budgets

Mid-Term

  • Deeper bundle splitting for heavy client islands
  • Structured SEO templates for topic clusters
  • Better analytics attribution for contact and conversion paths

Long-Term

  • Turn selected internal modules into reusable starter kits
  • Add personalization and recommendation layers
  • Evolve from personal site to reusable content platform architecture

Conclusion

I built this project to prove a point:

A personal website can be treated with the same engineering rigor as a production product.

By combining modern web architecture (Next.js App Router, React Server Components), a robust admin and visual editor workflow, S3-abstracted object storage, a full CI/CD pipeline with quality gates, and systematic observability — this platform became more than a portfolio.

It became a living full-stack system that I can iterate, monitor, and extend like any real product I’d ship professionally.

If you’re building your own site, my biggest recommendation is simple:

Don’t just design pages. Design workflows, reliability, and iteration velocity. That’s where long-term value compounds.

Leave a comment via GitHub (Giscus). Sign in with GitHub to post.