Cocktail Discovery & Email Marketing Newsletter Subscription Platform – Next.js, React, TypeScript, CocktailDB API, Tailwind CSS, Framer Motion, AI Composer Assist, TanStack Query, Project (including Admin Control Room)
MixMaster (cocktail-mixer) is a full-stack, educational cocktail discovery platform built with the Next.js App Router, React 19, and TypeScript. It combines public pages (search, cocktail details, favorites, newsletter signup) with a production-style newsletter pipeline (double opt-in, unsubscribe, rate limits) and an Admin Control Room for campaigns, subscribers, AI-assisted drafting, resend, cron jobs, queue management, and live API diagnostics.
Data flows from TheCocktailDB (no API key required) and optional Upstash Redis + Resend for email and storage—so you can run a minimal UI-only mode with zero .env secrets, or scale up to a complete “mini product” locally or on Vercel.
- Live Demo: https://cocktails-newsletter.vercel.app
- Security: Private vulnerability reports → SECURITY.md · contact@arnobmahmud.com
- Author: Arnob Mahmud · LinkedIn · GitHub
- What You Will Learn
- Learning Walkthrough
- Features
- Architecture Overview
- Technology Stack
- Keywords
- Prerequisites
- Installation & Quick Start
- Environment Variables
- NPM Scripts
- Project Structure
- Routing & Pages
- API Routes & Backend
- Admin Control Room
- How Key Features Work
- Observability (Sentry)
- Reusing Components in Other Projects
- Testing
- Deployment (Vercel)
- Further Reading
- Contributing
- Conclusion
- License
- Happy Coding
This repository is designed as a progressive learning lab. You can study it layer by layer:
| Layer | Topics | Where to look |
|---|---|---|
| Frontend | App Router, Server vs Client Components, Tailwind, Framer Motion | app/*/page.tsx, src/components/ |
| Data fetching | SSR first paint, TanStack Query sync, query keys | app/page.tsx, src/hooks/use-cocktails-query.ts |
| Types | Shared domain models, API DTOs | src/types/cocktail.ts, newsletter.ts, admin.ts |
| Newsletter | Double opt-in, HMAC tokens, rate limits | src/lib/newsletter/*, app/api/newsletter/* |
| Admin | Passkey login, httpOnly cookies, gated APIs | src/lib/admin-session.ts, app/api/admin/* |
| Broadcast | Drafts, queue, history, CSV export | BroadcastComposer.tsx, broadcast-dispatch.ts |
| AI (optional) | Multi-provider fallback chains | src/lib/admin/ai-provider-models.ts |
| Ops | Sentry tunnel, CI, production guardrails | docs/, .github/workflows/ci.yml |
Core skills:
- Next.js App Router — file-based routes, nested layouts, metadata API, route handlers.
- Server-first rendering — thin
page.tsxshells prefetch data; interactive bodies live in"use client"components. - TypeScript strict mode — contracts between UI, API routes, and Redis/Resend layers.
- Real integrations — TheCocktailDB, Resend, Upstash Redis, optional Groq/Gemini/OpenRouter/Hugging Face.
- Quality tooling — ESLint, Vitest, Playwright smoke tests, GitHub Actions CI.
git clone https://github.com/arnobt78/09-mixmaster.git
cd 09-mixmaster
npm install
npm run devOpen http://localhost:3000. Search cocktails, open /cocktail/[id], save favorites. No .env file is required for this tier—TheCocktailDB is public and defaults are baked into src/lib/api.ts.
- Browser requests
/or/?search=margarita. - Server Component
app/page.tsxreadssearchParams, callsfetchCocktails()on the server. - Results pass as props into Client Component
HomePagefor instant interactivity and TanStack Query hydration. - Further client searches use
useCocktailsQuerywithout a full page reload.
// app/page.tsx — server prefetch pattern
const initialDrinks = await fetchCocktails(searchTerm);
return (
<HomePage initialDrinks={initialDrinks} initialSearchTerm={searchTerm} />
);Copy .env.example → .env.local, set Resend + Upstash + signing secrets. Trace:
NewsletterPageContent → POST /api/newsletter → subscribeToNewsletter() → Redis pending record → Resend confirmation email → user clicks link → POST /api/newsletter/confirm → active subscriber.
Set ADMIN_DASHBOARD_KEY (e.g. 112233 for local learning only). Visit /admin/control-room, enter passkey, explore composer, subscribers, API docs, and API status dashboards.
Add any of GROQ_API_KEY, GEMINI_API_KEY, OPENROUTER_API_KEY, HUGGINGFACE_API_KEY. In the broadcast composer, use AI assist—the server tries providers in order with per-provider model chains. See docs/LLM_MODEL_SELECTION.md.
Configure Sentry (Tier 4), set NEXT_PUBLIC_APP_URL on Vercel, read docs/VERCEL_PRODUCTION_GUARDRAILS.md.
| Area | What it does |
|---|---|
| Home | Search cocktails by name via TheCocktailDB; SSR-friendly initial data with URL ?search= support. |
| Cocktail detail | Dynamic route /cocktail/[id] with ingredients, instructions, and safe image handling. |
| Favorites | Client-side persistence (localStorage) with hydration-safe patterns. |
| About | Server-rendered marketing/educational copy from shared content modules. |
| Newsletter | Public signup; confirm and unsubscribe pages; rate limiting on API routes. |
| Admin overview | Dashboard summary (counts, health hints) when Redis/session are configured. |
| Broadcast composer | Drafts, queue, history, test send, schedule, resend, optional AI fill. |
| Subscribers | Admin CRUD-style management for subscriber records (with auth). |
| API docs (in-app) | Human-readable catalog of HTTP routes from project-api-registry.ts. |
| API status | Live browser + server probes, TheCocktailDB latency, integration flags. |
| Error monitoring | Optional Sentry with same-origin /api/monitoring tunnel (ad-blocker resistant). |
┌─────────────────────────────────────────────────────────────────┐
│ Browser (React 19) │
│ Pages (RSC shells) + Client components + TanStack Query │
└────────────────────────────┬────────────────────────────────────┘
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
TheCocktailDB app/api/**/route.ts localStorage
(public REST) (Next.js handlers) (favorites)
│
┌──────────────┼──────────────┐
▼ ▼ ▼
Upstash Redis Resend API AI providers
(subscribers, (email) (Groq, Gemini,
drafts, queue) OpenRouter, HF)
Design conventions:
- Thin routes —
app/**/page.tsxhandles metadata + server data; UI lives insrc/components/pages/*orsrc/components/admin/*. - Business logic in
src/lib/— newsletter, admin auth, API clients—not inside route files. - Single API doc source —
src/data/project-api-registry.tspowers in-app API documentation and probe lists. - Mutations invalidate queries — admin CRUD refreshes
adminSummaryQueryKey()and related TanStack Query keys.
| Layer | Libraries / Tools | Role |
|---|---|---|
| Framework | Next.js 16, React 19 | App Router, RSC, routing, metadata API. |
| Language | TypeScript 5.9 | Types for components, API bodies, domain models. |
| Runtime | Node.js 24.x | Pinned in package.json engines and .nvmrc. |
| Styling | Tailwind CSS 3.4, tailwind-merge, clsx, CVA |
Utility-first UI, conditional classes, variants. |
| Motion | Framer Motion 12 | Page transitions and admin micro-interactions. |
| Server / client data | TanStack Query 5 | Caching, mutations, devtools for client fetches. |
| UI primitives | Radix Tabs & Tooltip | Accessible tabs and tooltips in admin UI. |
| Icons | Lucide React | Consistent icon set. |
| Toasts | Sonner | User feedback for newsletter and admin actions. |
| Resend | Transactional and broadcast email. | |
| Data store | Upstash Redis | Subscribers, drafts, queue, history (when configured). |
| AI (optional) | Groq, Gemini, OpenRouter, Hugging Face | Composer assist with ordered multi-model fallback. |
| Observability | Sentry (@sentry/nextjs) |
Error tracking via same-origin tunnel. |
| Testing | Vitest, Playwright | Unit + smoke E2E. |
| CI | GitHub Actions | Lint, test, build, audit on push/PR. |
TanStack Query deduplicates requests, exposes isPending / isError for UI, and keeps server state in sync after mutations (e.g. after saving a draft, invalidate summary queries).
// Pattern used in admin components:
const { data, isPending } = useQuery({
queryKey: ["admin", "control-room", "summary"],
queryFn: () =>
fetch("/api/admin/control-room/summary", { credentials: "include" }).then(
(r) => r.json(),
),
});Sonner — lightweight toast notifications; replaces older toast libraries with a simpler API:
import { toast } from "sonner";
toast.success("Draft saved.");class-variance-authority (CVA) — defines component variants (size, intent) in one place; pairs well with Tailwind:
// Conceptual — see src/components/ui/badge.tsx
const badgeVariants = cva("inline-flex rounded-full px-2", {
variants: { intent: { default: "bg-slate-800", success: "bg-emerald-600" } },
});Framer Motion — declarative animations for layout stability and polish without manual CSS keyframes.
Upstash Redis — serverless Redis over HTTPS REST; no persistent TCP connection needed on Vercel serverless functions.
Next.js, React, TypeScript, Tailwind CSS, Framer Motion, TanStack Query, TheCocktailDB, cocktail recipes, newsletter, double opt-in, Resend, Upstash Redis, App Router, server components, educational project, full-stack, admin dashboard, API routes, rate limiting, broadcast email, Sentry, Groq, Gemini, OpenRouter, Hugging Face, MIT License, Arnob Mahmud, MixMaster, Vercel, Playwright, Vitest, SEO, accessibility, Node.js 24.
- Node.js 24.x (see
.nvmrc;nvm userecommended). - npm 10+ (ships with Node 24).
- A modern browser for local development.
- Optional accounts: Resend, Upstash, Groq, Google AI Studio, OpenRouter, Hugging Face, Sentry.
git clone https://github.com/arnobt78/09-mixmaster.git
cd 09-mixmaster
npm install
cp .env.example .env.local # optional — see tiers below
npm run devOpen http://localhost:3000.
| Mode | .env needed? |
What works |
|---|---|---|
| UI + cocktails | No | Search, detail pages, favorites, about |
| Newsletter | Tier 1 | Subscribe, confirm, unsubscribe emails |
| Admin | Tier 1 + 2 | Control room, composer, subscribers |
| AI assist | + Tier 3 | AI draft fill in composer |
| Production monitoring | + Tier 4 | Sentry error tracking |
Copy .env.example → .env.local (Next.js loads this automatically in dev). Nothing from .env* is committed (see .gitignore).
You do not need any environment variables to explore cocktail search and favorites. Add variables only when you enable the corresponding feature.
| Variable | Required? | Purpose |
|---|---|---|
NEXT_PUBLIC_APP_TITLE |
No | Browser title / brand (default: MixMaster). |
NEXT_PUBLIC_APP_URL |
Yes for production email | Canonical URL for links in emails and OG metadata. |
NEXT_PUBLIC_API_BASE_URL |
No | TheCocktailDB base URL (sensible default in code). |
| Variable | Purpose | How to obtain |
|---|---|---|
RESEND_API_KEY |
Send mail | resend.com → API Keys |
RESEND_FROM_EMAIL |
From address | Verified domain in Resend dashboard |
RESEND_REPLY_TO_EMAIL |
Reply-To header | Your contact email |
UPSTASH_REDIS_REST_URL |
Redis REST endpoint | console.upstash.com → database → REST |
UPSTASH_REDIS_REST_TOKEN |
Redis auth token | Same Upstash REST tab |
NEWSLETTER_UNSUBSCRIBE_SECRET |
HMAC for unsubscribe links | openssl rand -base64 48 |
NEWSLETTER_CONFIRM_SECRET |
Optional separate confirm key | Falls back to unsubscribe secret |
| Variable | Purpose | How to obtain |
|---|---|---|
ADMIN_DASHBOARD_KEY |
6-digit admin passkey | Any 6 digits locally; strong random in prod |
ADMIN_SESSION_SECRET |
Signs httpOnly session cookie | openssl rand -base64 32 |
CRON_DIGEST_SECRET |
Protects weekly digest route | openssl rand -base64 32 |
| Variable | Purpose |
|---|---|
GROQ_API_KEY |
First provider (fast inference) |
GEMINI_API_KEY or GOOGLE_AI_API_KEY |
Second provider |
OPENROUTER_API_KEY |
Third provider (:free models) |
HUGGINGFACE_API_KEY |
Fourth provider (optional; skipped if unset) |
GROQ_MODEL, GEMINI_MODEL, etc. |
Override default model chains |
Provider order and model chains: docs/LLM_MODEL_SELECTION.md.
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_SENTRY_DSN |
Client errors — set at Vercel build time |
SENTRY_DSN |
Optional server alias |
SENTRY_ORG, SENTRY_PROJECT |
Source map upload (project slug, not org name) |
SENTRY_AUTH_TOKEN |
CI/build-only auth token |
Full setup guide: docs/Redis_Sentry_PostHog_INTEGRATION_GUIDE.md.
| Script | Command | Description |
|---|---|---|
| Dev server | npm run dev |
Next.js dev (Turbopack). |
| Production build | npm run build |
Optimized build (NEXT_TELEMETRY_DISABLED=1). |
| Start | npm run start |
Run production server locally after build. |
| Lint | npm run lint |
ESLint over the repo. |
| Unit tests | npm run test |
Vitest (newsletter, admin auth, AI model registry). |
| E2E smoke | npm run test:e2e |
Playwright — home and about pages. |
app/ # Next.js App Router
layout.tsx # Root layout, metadata, providers, JSON-LD
page.tsx # Home (server prefetch + client search)
about/ favorites/ newsletter/ # Public pages (RSC shells + *PageContent clients)
cocktail/[id]/ # Dynamic cocktail detail
admin/control-room/ # Admin UI (composer, subscribers, api-docs, api-status)
api/ # Route handlers (REST JSON)
newsletter/ # Public subscribe, confirm, unsubscribe, weekly-brief
admin/ # Session, control-room, subscribers, AI assist
monitoring/ # Sentry same-origin tunnel
robots.ts # Crawl policy + AI bot blocks
global-error.tsx # Sentry-aware root error boundary
src/
components/
pages/ # Feature page bodies (client where needed)
admin/ # Control room UI (composer, dashboard, panels)
layout/ # Navbar, footer, app shell
ui/ # Reusable primitives (card, badge, safe-image, …)
context/ # Newsletter + admin shell React context
data/ # Static copy + project-api-registry.ts
hooks/ # TanStack Query wrappers, media query
lib/
api.ts # TheCocktailDB fetch helpers
newsletter/ # Subscribe flow, mailer, Redis repo, security tokens
admin/ # AI composer, provider model chains
admin-session.ts # Passkey + HMAC cookie session
favorites-storage.ts # localStorage favorites
sentry-*.ts # Sentry DSN helpers and noise filters
providers/ # QueryClient provider
types/ # cocktail, newsletter, admin DTOs
tests/ # Vitest unit tests
e2e/ # Playwright smoke specs
docs/ # Integration guides, styling, LLM selection
.github/workflows/ci.yml # CI pipeline
Convention: Route page.tsx files stay thin; feature UI in src/components/*; business logic in src/lib/*.
| Path | Type | Description |
|---|---|---|
/ |
Dynamic SSR | Home search; reads ?search= |
/about |
Static shell | Educational copy |
/favorites |
Static shell | Saved cocktails (localStorage) |
/newsletter |
Static shell | Signup form |
/newsletter/confirm |
Client page | Double opt-in confirmation |
/newsletter/unsubscribe |
Client page | Unsubscribe with token |
/cocktail/[id] |
Dynamic SSR | Single cocktail detail |
/admin/control-room |
Protected | Admin dashboard |
/admin/control-room/composer |
Protected | Broadcast composer |
/admin/control-room/subscribers |
Protected | Subscriber management |
/admin/control-room/explore |
Protected | Explore / tools |
/admin/control-room/api-docs |
Protected | In-app HTTP API catalog |
/admin/control-room/api-status |
Protected | Live diagnostics dashboard |
All HTTP APIs are Next.js Route Handlers under app/api/**/route.ts. They return JSON unless noted (CSV export).
| Method | Path | Summary |
|---|---|---|
POST |
/api/newsletter |
Subscribe — validates, rate-limits, sends confirmation email |
POST |
/api/newsletter/confirm |
Activate subscriber with signed token |
POST |
/api/newsletter/unsubscribe |
Unsubscribe with signed token |
POST |
/api/newsletter/weekly-brief |
Cron-protected weekly digest (CRON_DIGEST_SECRET) |
Example — subscribe:
POST /api/newsletter
Content-Type: application/json
{
"firstName": "Ada",
"lastName": "Lovelace",
"email": "ada@example.com"
}{ "ok": true, "message": "Check your inbox to confirm your subscription." }| Method | Path | Summary |
|---|---|---|
POST |
/api/admin/session/login |
{ "passkey": "112233" } → httpOnly cookie |
POST |
/api/admin/session/logout |
Clears session |
| Method | Path | Summary |
|---|---|---|
GET |
/api/admin/control-room/summary |
Dashboard aggregates |
POST |
/api/admin/control-room/save-draft |
Save composer draft |
PATCH/DELETE |
/api/admin/control-room/drafts |
Edit or delete drafts |
POST |
/api/admin/control-room/send-post |
Send or schedule broadcast |
GET/PATCH/DELETE |
/api/admin/control-room/queue |
Queue management |
POST |
/api/admin/control-room/process-queue |
Process due scheduled sends |
DELETE |
/api/admin/control-room/history |
Clear resend history |
POST |
/api/admin/control-room/resend-post |
Resend from draft/history |
GET |
/api/admin/control-room/export |
CSV download of subscribers |
GET |
/api/admin/control-room/diagnostics |
Server probes + integration flags |
| Method | Path | Summary |
|---|---|---|
GET/PATCH/DELETE |
/api/admin/subscribers |
Subscriber CRUD |
POST |
/api/admin/ai/composer-assist |
AI draft from { "brief": "..." } |
| Method | Path | Summary |
|---|---|---|
POST |
/api/monitoring |
Sentry same-origin tunnel (not for manual use) |
Single source of truth for in-app docs: src/data/project-api-registry.ts.
Auth pattern: Admin routes call assertAdminSession() which verifies the HMAC-signed httpOnly cookie set at login.
- Visit
/admin/control-room. - Enter the 6-digit passkey matching
ADMIN_DASHBOARD_KEY. - Navigate via sidebar: Overview, Composer, Subscribers, Explore, API Docs, API Status.
Local learning example (demo only — use strong secrets in production):
ADMIN_DASHBOARD_KEY=112233
ADMIN_SESSION_SECRET=local-dev-session-secret-change-meLog in with passkey 112233.
Without ADMIN_DASHBOARD_KEY, the UI explains that the control room is disabled. Sending mail and persisting drafts additionally require Resend + Upstash configuration.
src/lib/api.ts builds URLs from NEXT_PUBLIC_API_BASE_URL (defaults to the free v1 JSON API). Server components call fetchCocktails(term) and fetchCocktailById(id) for SSR.
// Simplified — search by name
const url = `${baseUrl}/search.php?s=${encodeURIComponent(term)}`;No API key is required. Respect TheCocktailDB terms of use for production traffic.
src/lib/favorites-storage.ts wraps localStorage with guards so SSR and client renders do not mismatch—read favorites after mount, dispatch sync events across tabs.
src/lib/newsletter/security.ts signs confirm/unsubscribe URLs. Tokens cannot be forged without NEWSLETTER_*_SECRET server keys.
Flow:
Subscribe → pending record in Redis → confirmation email
→ user clicks link → POST /api/newsletter/confirm → active subscriber
→ welcome email (optional path in service layer)
Composer → save-draft (Redis) → send-post or schedule → queue → process-queue (cron or manual) → Resend batch → history for resends.
Always set NEXT_PUBLIC_APP_URL in production so email links resolve to your real domain.
src/lib/newsletter/rate-limit.ts uses Upstash Redis sliding windows on public newsletter routes and admin AI assist.
app/layout.tsx exports rich metadata (Open Graph, Twitter, JSON-LD). app/robots.ts allows marketing pages, disallows /api/ for crawlers, and blocks common AI user-agents.
When NEXT_PUBLIC_SENTRY_DSN is set at build time, Sentry captures client and server errors. The SDK POSTs to same-origin /api/monitoring instead of ingest.sentry.io, bypassing ad blockers.
- Config:
instrumentation.ts,instrumentation-client.ts,sentry.server.config.ts - Noise filters:
src/lib/sentry-filters.ts(extensions, transport failures) - SDK is disabled when DSN is empty — safe for local dev without keys
See docs/Redis_Sentry_PostHog_INTEGRATION_GUIDE.md §2A.
| Piece | File(s) | Reuse idea |
|---|---|---|
| Safe image | src/components/ui/safe-image.tsx |
Next/Image wrapper with fallbacks for broken URLs. Guide: docs/SAFE_IMAGE_REUSABLE_COMPONENT.md |
| Ripple button | src/components/ui/ripple-button.tsx |
Accessible button with feedback animation. Guide: docs/RIPPLE_BUTTON_EFFECT.md |
| Card / Badge / Input | src/components/ui/* |
Copy into another Tailwind + CVA project. |
| Query provider | src/providers/query-provider.tsx |
Standard TanStack Query + Devtools wiring. |
| Newsletter context | src/context/newsletter-context.tsx |
Client signup state + Sonner toasts pattern. |
| API registry | src/data/project-api-registry.ts |
Document your APIs in one typed array; render in-app docs. |
| AI fallback | src/lib/admin/ai-provider-models.ts |
Portable provider + model chain registry. |
| Newsletter lib | src/lib/newsletter/* |
Adapt double opt-in + HMAC pattern to other products. |
When porting, replace @/… imports with your alias and align tailwind.config.ts theme tokens.
npm run test # Vitest — newsletter routes, admin auth, AI model registry
npm run test:e2e # Playwright smoke — / and /about
npm run lint
npm run build| Test file | Covers |
|---|---|
tests/newsletter-routes.test.ts |
Newsletter handler validation |
tests/admin-api-auth.test.ts |
Admin session gate |
tests/ai-provider-models.test.ts |
AI model chains and retriable errors |
e2e/smoke.spec.ts |
Basic page loads |
CI runs on push/PR to main: .github/workflows/ci.yml.
- Connect github.com/arnobt78/09-mixmaster to Vercel.
- Set environment variables per tier (minimum:
NEXT_PUBLIC_APP_URLfor production). - Deploy — Node 24.x is used via
enginesinpackage.json. - Verify newsletter confirm links and admin login on the production URL.
- Optional: Sentry DSN at build time, Vercel Firewall per
docs/VERCEL_PRODUCTION_GUARDRAILS.md.
| Resource | Description |
|---|---|
docs/LLM_MODEL_SELECTION.md |
Free-tier AI providers and fallback strategy |
docs/Redis_Sentry_PostHog_INTEGRATION_GUIDE.md |
Sentry, Redis, observability setup |
docs/VERCEL_PRODUCTION_GUARDRAILS.md |
Production checklist |
docs/UI_STYLING_GUIDE.md |
Design tokens and patterns |
SECURITY.md |
Private vulnerability reporting |
| Next.js Docs | App Router reference |
| TheCocktailDB API | External cocktail data |
| TanStack Query | Server state on the client |
| Resend Docs | Email API |
| Upstash Redis REST | Serverless Redis |
Issues and pull requests are welcome: bug fixes, documentation improvements, and small focused features. Please run npm run lint, npm run test, and npm run build before submitting.
For security-sensitive findings, email contact@arnobmahmud.com per SECURITY.md—do not open public issues for vulnerabilities.
MixMaster is both a usable cocktail explorer and a structured learning lab for modern full-stack patterns—SSR + client state, typed APIs, email flows, optional AI, and a gated admin surface. Start with zero configuration, then enable environment tiers one at a time until you reach a full newsletter + control room deployment. Adapt the lib/ modules and UI primitives into your own projects with confidence.
This project is licensed under the MIT License. Feel free to use, modify, and distribute the code as per the terms of the license.
This is an open-source project - feel free to use, enhance, and extend this project further!
If you have any questions or want to share your work, reach out via GitHub or my portfolio at https://www.arnobmahmud.com.









