Evva is a Luma clone built for India — a place to discover and host events across 30 major Indian cities. I built it to see how far Next.js Server Components and Supabase could take a full-stack app without writing a separate backend: no REST layer, no service you have to deploy and babysit, just server-rendered pages and Server Actions talking straight to Postgres.
It's not a mockup. RSVPs, sign-ups, and event creation all hit a real Supabase project — create an account and the data is really there tomorrow.
🌐 Live: evva-gray.vercel.app
- Browse events by city or category, or search across all of them
- Every city gets its own landing page with local imagery
- Hosts can create events — venue or online, free or priced in INR, with a capacity cap and an optional AI-generated cover banner (pick a style — vibrant, minimal, cinematic — and Gemini generates the image)
- Events can be saved as drafts (only the host sees them) or published; hosts can also require approval before an RSVP is confirmed instead of auto-accepting everyone
- Email/password auth, with sessions handled server-side via cookies
| Layer | Choice |
|---|---|
| Framework | Next.js 16 (App Router, Server Components, Server Actions, Turbopack) |
| Language | TypeScript |
| Styling | Tailwind CSS v4 |
| Database | Supabase Postgres (ap-south-1, Mumbai) |
| Auth | Supabase Auth — email/password, cookie-based SSR sessions |
| Validation | zod |
| Deployment | Vercel (app) + Supabase (everything else) |
The short version of why this combination: Server Components let pages read straight from Postgres with no API layer in between, Server Actions do the same for writes (sign up, create an event, RSVP), and Supabase gives you a real Postgres database with row-level security instead of you writing your own auth and permissions layer. The result is an app with no backend to deploy, patch, or scale on its own — Vercel and Supabase handle that.
src/
app/
page.tsx # Homepage — hero, popular cities, featured events
discover/page.tsx # Search + filter all events (city, category, text)
city/[slug]/page.tsx # Per-city landing page (one of the 30 cities)
e/[slug]/page.tsx # Event detail page + RSVP
create/page.tsx # Host: create-event form (auth required)
dashboard/page.tsx # Attendee/host dashboard (auth required)
login/page.tsx, signup/page.tsx
components/ # Header, Footer, EventCard, CityGrid, forms, etc.
lib/
supabase/
client.ts # Browser Supabase client
server.ts # Server Component / Server Action Supabase client
middleware.ts # Session-refresh logic used by proxy.ts
database.types.ts # Generated Postgres types (Database, Tables<>)
actions/
auth.ts # signUp / signIn / signOut Server Actions
events.ts # createEvent / registerForEvent / cancelRegistration
data.ts # Typed read helpers (getCities, getUpcomingEvents, …)
imagegen.ts # AI cover-banner generation (Gemini) → Supabase Storage
utils.ts # cn(), date/price formatters, slugify
proxy.ts # Refreshes the Supabase session on every request
Next.js 16 renamed
middleware.tstoproxy.ts(export function proxy). This project already uses the new convention, in case you're used to the old one.
Everything lives in the public schema with Row Level Security turned on:
profiles— one row per user, created automatically by a trigger when someone signs upcities— the 30 seeded Indian citiescategories— 12 event categories (Tech, Music, Business, Comedy, …)events— title, description, city/category, venue or online link, start/end time (stored UTC, shown inAsia/Kolkata), capacity, price, statusregistrations— one row per RSVP, each with a unique ticket codesaved_events— bookmarks
The RLS policies boil down to: published events and city/category lists are public; hosts can only read/write their own events; and everyone can only touch their own registrations — except hosts, who can also see who's registered for their events.
npm install
cp .env.example .env.local # fill in your Supabase project URL + anon key
npm run devThen open http://localhost:3000.
NEXT_PUBLIC_SUPABASE_URL= # Project Settings → API
NEXT_PUBLIC_SUPABASE_ANON_KEY= # Project Settings → API (publishable/anon key)
NEXT_PUBLIC_MAPBOX_TOKEN= # optional — powers the city map
GOOGLE_AI_API_KEY= # optional — powers AI-generated cover banners
The schema (tables, RLS policies, the handle_new_user trigger, and the
30-city/12-category seed data) was built directly via Supabase migrations
during development. To run this against a fresh project, recreate that same
schema — tables, then RLS policies, then seed data — before starting the app.
npm run dev # start the dev server (Turbopack)
npm run build # production build + typecheck
npm run start # run the production build
npm run lint # eslint- No hand-rolled API routes for writes. Sign up/in/out, creating an event, RSVPing, cancelling — all Server Actions.
- RLS does the security work. The browser never sees a service-role key;
every read and write is scoped by Postgres row-level security tied to
auth.uid(). - Built India-first, not internationalized. Times default to
Asia/Kolkata, prices default to INR, and the city list is the fixed set of 30 — this isn't trying to be a generic multi-country app. - RSVP button stays snappy without giving up Server Components.
RegisterButtonis a small client island wrapping the Server Action inuseTransition, so the rest of the event page can stay server-rendered. - City imagery is bundled, not fetched. Cards and city pages read from a
local slug → image map (
lib/cityImages.ts) backed bypublic/cities/, so there's no dependency on a remote image host at runtime.
The app is deployed on Vercel and connected to this repo, so pushes to main
go live automatically. If you fork this, you'll need to set the same
environment variables in your Vercel project settings and point them at your
own Supabase project.