Ad-free QR code generator for university clubs.
Clubs typically use generic QR/link-shortener tools to point students at a Google Form for event signups. Those tools display ads before the redirect, which looks unprofessional and causes students to drop off before they ever reach the form.
Club QR replaces the ad-revenue model with a promotional interstitial: scanning the code takes the student to a page that highlights the club's other upcoming events, then sends them on to the actual signup form. No ads, anywhere in the flow.
- A student scans a QR code and hits
/r/<code> - The short code is looked up and the scan is logged
- If that code is marked "skip promo," the student is redirected straight to the target Google Form
- Otherwise, they see an interstitial with the club's upcoming events and a "Continue to sign-up" button
- Clicking through sends them to the target form
- Google-only admin login — no passwords to manage, self-serve club creation on first sign-in
- Multi-admin clubs, with email invites for teammates
- QR code generation with per-club or per-code branding (colors + logo)
- Event management with a past/upcoming distinction
- Postgres-backed rate limiting on login and redirect routes
- Cached promo pages for fast redirect-page loads, invalidated instantly on branding/event changes
- Framework: Next.js (App Router), TypeScript, Tailwind CSS
- Database: PostgreSQL on Supabase, via Prisma ORM (
@prisma/adapter-pg, pooled/pgbouncer connection) - Auth: NextAuth.js v5 (Auth.js) with Google OAuth, JWT sessions
- QR generation:
qrcode(server-side PNG),sharpfor optional center-logo compositing - Email:
nodemailerfor admin-invite emails - Short codes:
nanoid
Prerequisites: Node.js, a Supabase Postgres instance, and a Google OAuth client (Google Cloud Console).
npm installCreate a .env file in the project root with:
DATABASE_URL= # Supabase pooled (pgbouncer) connection string
AUTH_GOOGLE_ID= # Google OAuth client ID
AUTH_GOOGLE_SECRET= # Google OAuth client secret
AUTH_SECRET= # generate with: npx auth secret
NEXT_PUBLIC_BASE_URL= # base URL used for short-code links and OAuth callback
OWNER_EMAIL= # email allowed to view /dashboard/feedback
# SMTP, for admin-invite emails
SMTP_HOST=
SMTP_PORT=587
SMTP_USER=
SMTP_PASSWORD=
MAIL_FROM= # optional, defaults to SMTP_USER
Then set up the database and start the dev server:
npm run prisma:migrate
npm run devnpm run dev— start the dev servernpm run build/npm start— production build / runnpm run lint— ESLintnpm run prisma:migrate— create/apply a migrationnpm run prisma:studio— browse the database
src/app/dashboard/ admin dashboard — QR codes, events, branding, admins, feedback
src/app/r/[code]/ public redirect + promo interstitial
src/app/api/auth/ NextAuth route handlers
src/lib/ Prisma client, session helpers, rate limiting, mail, promo cache
prisma/ schema + migrations
Targets Vercel. Because Vercel functions are serverless, use a connection-pooling-friendly Postgres URL (e.g. Supabase's pgbouncer connection string) rather than a raw long-lived connection.