
bossstrong-dev — This Portfolio
A full-stack developer portfolio with its own single-admin content dashboard — built end to end, from database schema and row-level security to the public site you're looking at right now.
Role: Sole developer — planning, database design, frontend, backend, and deployment.
Problem
A portfolio needs to prove full-stack ability, not just claim it — a static site with hardcoded content doesn't demonstrate database design, auth, or backend logic. It also needed a way to publish blog posts and manage a resume without hand-editing code for every update.
Goals
Build a public marketing/evidence site (Home, About, Projects, Blog, Contact) backed by a single-admin dashboard for managing blog content — with real authentication, real data validation, and security handled properly rather than superficially.
Solution
A Next.js App Router site with two distinct registers: a public marketing site and a denser, lower-motion /admin dashboard, both built on the same design-token system. Supabase provides Postgres, authentication (email/password and Google OAuth), and file storage; all writes go through validated Server Actions, and draft content is invisible at the database level, not just hidden in the UI.
Architecture
Route groups separate the public site from the admin dashboard so they can never accidentally share marketing styling. Server Components fetch data directly from Supabase for reads; Server Actions handle every write. Middleware (proxy.ts on Next 16) refreshes the auth session and guards every /admin/* route centrally, rather than repeating auth checks per page.
Frontend
Tailwind CSS v4 design tokens (color, type scale, spacing, radius) drive both registers from one source, with a manual dark-mode class strategy that respects system preference by default. Forms use react-hook-form with zod schemas for client-side validation; markdown content (blog posts) renders through react-markdown with syntax highlighting, deliberately without raw-HTML passthrough, to close off a real XSS vector.
Backend
Server Actions handle post/category CRUD, resume upload, and contact-form submission. The contact form's rate limiting is enforced by a single atomic Postgres function (submit_contact_message), not application-level logic, closing a race condition a naive insert-then-check approach would leave open. Email notifications send via Resend, and a failed send never blocks the saved submission.
Database
Postgres via Supabase, with Row-Level Security on every table — draft posts are invisible to public queries at the database level, not filtered client-side. Categories, posts, and contact submissions are hand-written, version-controlled SQL migrations. Deleting a category reassigns its posts to a non-deletable "Uncategorized" category via ON DELETE SET DEFAULT rather than orphaning or cascading.
Challenges
A few real walls came up during the build: shadcn/ui's CLI turned out to inject its own color palette that silently collided with the project's existing design tokens (including a real WCAG contrast bug caught in the process); Next.js 16 renamed middleware.ts to proxy.ts mid-project; a migration ran in the wrong order against the live database and had to be diagnosed and fixed; and a Google OAuth redirect_uri_mismatch turned out to be a blank field in the Google Cloud Console, found by decoding the actual error payload rather than guessing.
Technical Decisions
Chose Next.js route groups to physically separate the admin and public layouts rather than conditionally styling one shared layout. Chose a single atomic database function over a separate rate-limiting table or external service for the contact form, matching the actual scale of the problem instead of over-engineering it. When a needed icon (GitHub/LinkedIn/WhatsApp logos) wasn't available in the project's icon library, chose hand-authored inline SVG over adding a new dependency.
Results
The full site is live and deployed on Vercel: public pages, a working admin dashboard with authentication (including Google OAuth), category and post management with image cleanup on delete, resume upload, and a contact form backed by real validation, rate limiting, and email notification.
Lessons Learned
Verifying assumptions costs less than debugging them later — a database migration that silently ran in the wrong order, and commits that sat unpushed to GitHub for several sessions without anyone noticing, were both caught by checking actual state rather than trusting that a prior step had completed. Production environment variables are genuinely separate from local ones and need to be verified independently, not assumed to carry over.