AI-driven, affinity-weighted product recommendation engine for Nykaa Fashion.
Experiment: issue-008 · Stack: Next.js 16, Neon DB, PostHog
Nykaa Fashion's homepage shows the same editorial content to all users regardless of purchase history. Logged-in users who have already signaled brand/category preferences through orders still see generic product feeds, leading to missed conversion opportunities.
A personalisation engine that replaces the static editorial shelf with an AI-scored "Picked for you" strip:
- Historical Affinity (60% weight): Top brands + categories from past orders
- In-Session Intent (40% weight): Recent product clicks within the current session
- A/B Testing: Deterministic SHA-256 cohort split (test vs control)
- Search Re-ranking: Personalised search results for test group users
- User Authentication: User accesses the Nykaa platform.
- Intent Capture: The system captures in-session categorical or product clicks using the intent tracker.
- Affinity Matching: The historical affinity scores of brands and categories are retrieved.
- Personalized Shelf Delivery: The user receives a blend of historically preferred and recently intended products on their homepage shelf.
- Search Re-ranking: Search results are proactively reordered based on the user's affinity profile.
| Layer | Technology |
|---|---|
| Frontend | Next.js (App Router), React, TailwindCSS |
| Backend | Next.js Route Handlers |
| Database | Neon PostgreSQL (Serverless) |
| Analytics | PostHog |
| Error Tracking | Sentry |
┌─────────────┐ ┌──────────────────────┐ ┌──────────┐
│ Homepage │────▶│ GET /api/shelf │────▶│ Neon DB │
│ ForYouShelf │ │ PersonalisationSvc │ │ 3 tables │
└─────────────┘ └──────────────────────┘ └──────────┘
│ │
│ ┌──────┴──────┐
│ │ CohortSvc │
│ │ SHA-256 A/B │
│ └─────────────┘
│
┌──────┴──────┐ ┌──────────────────────┐
│ Product │────▶│ POST /api/ingest │──▶ session_events
│ Click │ │ EventIngestionSvc │
└─────────────┘ └──────────────────────┘
┌─────────────┐ ┌──────────────────────┐
│ Search Page │────▶│ GET /api/rerank │──▶ Re-scored results
└─────────────┘ │ RerankEngine │
└──────────────────────┘
┌─────────────┐ ┌──────────────────────┐
│ Nightly │────▶│ POST /api/rebuild │──▶ user_affinity_profiles
│ Cron │ │ AffinityBuilder │
└─────────────┘ └──────────────────────┘
- Node.js 20+
- A Neon DB project
- PostHog project key (optional for local dev)
cd apps/nykaa-personalisation
cp .env.local.example .env.local
# Fill in DATABASE_URL, CRON_SECRET, and optionally PostHog keys
npm installApply the schema to your Neon database:
# Copy-paste contents of schema.sql into Neon SQL Editor
# Or use psql:
psql "$DATABASE_URL" < schema.sqlSeed test data:
curl -X POST http://localhost:3000/api/seed \
-H "x-cron-secret: YOUR_CRON_SECRET"npm run dev # → http://localhost:3000
npm run build # TypeScript check + production build| Route | Method | Auth | Description |
|---|---|---|---|
/api/personalisation/shelf |
GET | Bearer JWT | Personalised shelf products |
/api/personalisation/ingest-event |
POST | Bearer JWT | Record session click event |
/api/personalisation/rerank |
GET | Bearer JWT | Re-rank search results |
/api/admin/rebuild-affinity |
POST | x-cron-secret | Nightly affinity rebuild |
/api/seed |
POST | x-cron-secret | Seed test data |
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | Neon PostgreSQL connection string |
CRON_SECRET |
Yes | Secret for admin/cron endpoints |
NEXT_PUBLIC_POSTHOG_KEY |
No | PostHog project API key |
POSTHOG_HOST |
No | PostHog host URL |
NEXT_PUBLIC_AB_EXPERIMENT_SALT |
No | Salt for A/B cohort hash |
- Neon DB over Supabase: Direct PostgreSQL access via
@neondatabase/serverless, no ORM overhead - No ML model: Pure arithmetic scoring (brand/category match). Easier to debug, explain, and iterate
- App-level auth: JWT verification in route handlers instead of Supabase RLS
- SessionStorage for intent: FIFO buffer of last 3 clicks, synced to DB via ingest-event API
- In-memory rate limiter: MVP-grade; upgrade to Redis for production
| Event | Source | Trigger |
|---|---|---|
shelf_impression |
Server | Shelf API returns products |
shelf_click |
Client | User clicks shelf product |
search_rerank_impression |
Server | Search API returns results |
add_to_cart |
Client | User adds product to cart |
shelf_load_failed |
Server | Shelf API error/fallback |
rerank_failed |
Server | Rerank API error |
ingest_event_failed |
Server | Ingest API error |
page_viewed |
Client | Page mount |
affinity_rebuild_completed |
Server | Cron success |
affinity_rebuild_failed |
Server | Cron error |