Building zaidxshaikh: foundation → viral hooks in one week
How v2 of my portfolio went from empty branch to WebGL + ⌘K palette + streaming AI chat + audience switching, in a week of focused work.
A portfolio that looks like every other portfolio is a portfolio nobody remembers. The brief for v2 was simple: make people screenshot it within ten seconds of landing.
This is the rough log of how it came together.
The stack decision
Next.js 15 + React 19 framework + concurrent rendering
Tailwind 4 (@theme OKLCH) CSS-first config, perceptually uniform colors
Three.js + R3F + drei 3D hero
Framer Motion 12 UI micro-interactions
Zustand 5 UI state
cmdk + nuqs command palette + URL state
Vercel AI SDK + @ai-sdk/groq streaming AI chat
Upstash Redis sliding-window rate limit
Resend + Cloudflare Turnstile contact form + anti-botOKLCH tokens were the unsung hero — perceptually uniform color spaces make the 6-theme studio (which came later) trivial. Lightening or shifting hue stays visually consistent across themes.
The four hooks
I designed the site around four "screenshot moments":
- The 3D hero — a glyph field with 20 project orbs in a logarithmic spiral. The wordmark
zaidxshaikhfloats at origin. Click an orb → smooth view transition to the case study. - ⌘K command palette — fuzzy search over every project, action, deep-link. URL state via
nuqsso?cmd=hire-medeep-links into a pre-filtered palette. - Streaming AI chat — Groq Llama 3.3 70B via Vercel AI SDK, grounded in my actual resume via a ~6KB system prompt. Sub-300ms TTFT. Refuses jailbreaks, rate-limited.
- Audience switching —
?as=recruiter|dev|clientretints the 3D scene, swaps the hero copy, changes the primary CTA, and shifts the AI's system prompt. Triptych screenshot = comparison shot for virality.
Build-time RAG, no vector DB
For the AI chat I considered the standard "vector DB + embeddings" route. At resume scale (~6 KB of source material) that's overkill. Instead, a build script flattens profile.ts + projects.ts + experience.ts + case-studies.ts + services.ts into a single markdown blob, which becomes the system prompt at request time.
Cheaper, faster, and removes Pinecone/Weaviate as a dependency. Doesn't scale past ~20 KB of source, but for a personal site it's perfect.
Bugs along the way
- React 19 + R3F v9 mismatch — the initial scaffold pinned React 18, R3F 8. Production crashed with
Cannot read properties of undefined (reading 'ReactCurrentBatchConfig'). Fix: bump to React 19 + R3F 9 + drei 10. - Scroll capture bug — covered in a separate post on removing Lenis.
- Suspense boundary errors —
useSearchParamsvianuqsrequires a Suspense boundary in App Router. Wrapped<Nav />and<CommandPalette />accordingly.
What the week shipped
By end of week one: WebGL hero, ⌘K palette, AI chat, audience switching, 20 projects with full case studies, services + pricing, contact form, resume page, sitemap + robots + JSON-LD, dynamic OG images, Vercel deploy with env vars on Production + Preview.
The fun part is what came after: theme studio (6 themes), Konami easter egg, view transitions, sound design, PWA + offline shell. Each one earns its own post.
The whole repo is public: github.com/zaidxshaikh/shaikh-zaiid.github.io.