Why I Built This
I wanted a project at the bleeding edge of the Next.js ecosystem — Next.js 16, React 19, the React Compiler, Turbopack — paired with a real product problem rather than a toy demo. Reading release notes isn't enough; I needed an app with authentication, user-generated content, and a CMS to understand how these pieces behave together in production.
A startup pitch platform was the right shape: founders submit pitches, the community upvotes and comments, and — the part I was most excited about — every pitch gets scored by AI. That gave me a reason to wire up a structured LLM pipeline alongside a content-managed marketing site, all inside a single monorepo.

How It Works
Founders sign in with GitHub OAuth (NextAuth v5) and submit a pitch through a multi-step form with a Tiptap/Novel rich-text editor. On submission, the pitch is sent to Google Gemini, which returns a structured analysis — scores for clarity, market positioning, and uniqueness, a weighted overall score, and a handful of actionable suggestions. The response is validated with Zod before it ever touches the UI, so a malformed model output can't break the page.
Content lives in Sanity CMS. The entire marketing surface — homepage hero, logo ticker, top pitches, integrations, FAQ — is a typed page builder editable by non-technical users, with click-to-edit visual editing and live preview wired through GROQ queries and Sanity's live content API.
The browse experience layers category filters, sorting by recent/upvotes/views, and instant client-side fuzzy search with Fuse.js. Upvotes, comments, and view counts are persisted back to Sanity, and each founder gets a public profile generated automatically on first sign-in.

Key Decisions
A pnpm + Turborepo monorepo
The Next.js frontend and the Sanity Studio live in one repo with shared packages for env validation, the Sanity client and generated types, the UI kit, and logging. Turborepo's task graph caches lint/typecheck/build across both apps, and generated Sanity types flow straight into the frontend — so a schema change is type-checked end to end.
Structured AI output over free-form text
The temptation with an LLM feature is to dump prose on the screen. Instead I forced Gemini's response into a strict schema and validated it with Zod, then rendered each dimension as its own scored panel. Treating the model as a typed data source — not a chat box — made the feature reliable enough to ship and trivial to redesign.
Sanity CMS over a custom database
For a content-heavy app with structured, editor-managed data, Sanity beat rolling my own Prisma + Postgres layer. GROQ is expressive, the Studio gives a visual page builder, and live queries mean the feed and homepage update the moment content is published — no redeploy.

What I Learned
Treat LLMs as typed APIs, not chatbots. The moment I constrained Gemini to a schema and validated it, the AI feature went from a fragile party trick to a dependable part of the product. Validation at the boundary is what makes generative features production-safe.
Monorepos pay off when types cross boundaries. Having the Studio schema regenerate the exact TypeScript types the frontend consumes caught a whole class of bugs at compile time. The upfront wiring of Turborepo and shared packages saved time on every subsequent feature.
The newest features need patience. Next.js 16, React 19, and the React Compiler were fast-moving targets with sparse docs. Reading source and GitHub discussions for edge cases no tutorial covered yet was just part of the work — and the payoff was a genuinely fast, modern app.