Built my own agency site from scratch on Next.js 16 (App Router, TypeScript, Turbopack) with React 19 and Tailwind CSS v4. Not a template, not a page builder a full product with a custom backend, admin panel, and scroll-driven motion design.
Core Stack
Framework: Next.js 16 (App Router, TypeScript, Turbopack)
UI: React 19
Styling: Tailwind CSS v4 (token-based theme)
Motion: GSAP + ScrollTrigger for scroll-driven animation, Lenis for smooth scrolling
Database: Postgres via Neon (serverless driver)
File Storage: Vercel Blob (public, for admin-uploaded images/videos)
Hosting: Vercel
What's on the site
The homepage is a single scroll experience: a moon-to-crescent hero video (scroll-scrubbed), About section, horizontal-scrolling Services carousel, horizontal-scrolling Work/case-studies carousel, a Process section (also scroll-scrubbed video), a real FAQ block with FAQPage schema, and a CTA.
Dynamic pages for
/work/[id] and /services/[id] are statically generated from the database at build time. The /contact page has an embedded Calendly booking widget and a working contact form.Admin Dashboard
A password-protected
/admin dashboard handles full CRUD for case studies and services, media uploads via Vercel Blob, and contact form submission viewing. Session-based auth via middleware gates access behind a signed cookie.Backend & Data
All data queries hit Postgres directly through
@neondatabase/serverless. REST-ish route handlers back the admin panel (/api/case-studies, /api/services, /api/contact, /api/upload, /api/admin/login). Auth checks passwords against an env var set in Vercel, never committed to the repo.SEO & Discoverability
Auto-generated
sitemap.ts and robots.ts that include every case study and service page. Per-page metadata and JSON-LD markup (Organization, Service, CreativeWork, FAQPage) throughout. An llms.txt route serves a machine-readable site summary for AI crawlers, generated live from the database.Motion & Performance
The hero and Process videos are encoded all-intra (every frame a keyframe) so scroll-scrubbing seeks are instant (~2-3ms) instead of stuttering. A centralized
motion.ts module handles prefersReducedMotion() checks and a debounced resize-refresh helper so GSAP's pinned sections stay in sync on mobile. Admin routes deliberately skip Lenis (native scroll only) since Lenis doesn't track dynamically-growing content well.
Homepage hero scroll experience
4 likes · 19 views · Jul 3, 2026
Custom-built agency portfolio on Next.js , React , Tailwind CSS, Scroll-driven GSAP animations REST APIs, and full SEO with JSON-LD deployed on Vercel.