Documentation

urnext.app starter kit

Everything you need to clone, configure, and ship. This kit pairs Next.js 16, MongoDB, and Prisma with email/password & Google auth, multi-tenant routing, and white-label branding out of the box.

Overview

urnext.app is a production-ready SaaS starter kit. The public landing page lives at /; once a user signs in they get a dedicated tenant space at /{displayName} with their own dashboard and profile.

  • Custom JWT auth in HTTP-only cookies (no third-party auth vendor lock-in)
  • Approval-gated signup: applicants request access, admins approve/deny from a dashboard
  • Password reset and optional Google OAuth (also subject to approval)
  • Role-based access control (USER / MODERATOR / ADMIN)
  • White-label branding & theme color driven by env vars or the database
  • Accessible (WCAG AA) light/dark theming and a responsive sidebar shell

Tech stack

  • Framework: Next.js 16 (App Router, Turbopack)
  • Language: TypeScript, React 19
  • Database: MongoDB via Prisma 6
  • Auth: Custom JWT (jose) + bcryptjs
  • UI: Tailwind CSS 4 + shadcn/ui (Radix)
  • Email: Nodemailer (SMTP)
  • State: Zustand + TanStack Query
  • Deploy: Vercel-ready

Quick start

Get a local instance running in about five minutes:

# 1. Install dependencies
npm install

# 2. Create your own .env (copy the example) and fill in your values
cp .env.example .env

# 3. Once your environment is configured, prepare the database in one step
#    (generates the Prisma client, pushes the schema, seeds branding)
npm run db:setup

# 4. Create your first admin (to review access requests)
ADMIN_EMAIL=you@example.com npm run seed:admin

# 5. Start the dev server
npm run dev   # http://localhost:3000

Generate a strong JWT_SECRET with:

node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

Environment variables

Create your own .env file in the project root — copy .env.example as a starting point and fill in your own values:

cp .env.example .env

Required for production: DATABASE_URL, JWT_SECRET, APP_URL. Every available variable — database, auth, email, Google OAuth, and white-label branding — is listed and documented inline in .env.example. Everything else is optional.

Project structure

app/
  (home)/            # Public SPA landing
  [workspace]/       # Authenticated tenant dashboard (URL = displayName)
  auth/              # Login, signup, verify, reset, complete-profile
  docs/              # This documentation page
  api/auth/          # signup, login, verify, google, me, logout
  api/user/          # profile, email, password, theme
lib/                 # auth (JWT), email, google, site-config, rate-limit
components/          # auth forms, dashboard shell, shadcn/ui primitives
prisma/schema.prisma # MongoDB models (User, Account, Session, ...)
proxy.ts             # Route gating (Next.js 16 middleware equivalent)

Authentication & access requests

Registration is approval-gated. The signup page is a short application (name + email only) — it does not create a password. The flow:

  1. Applicant submits name + email at /auth/signup → a PENDING account is created (no password).
  2. An admin reviews it at /admin and approves or denies.
  3. On approval, the system generates a temporary password and emails it to the applicant.
  4. The applicant logs in and is forced to set a new password before reaching the app.

Approved users receive a JWT in a secure HTTP-only cookie (7-day expiry). Google OAuth (when configured) is subject to the same approval gate. Bootstrap your first admin with ADMIN_EMAIL=you@example.com npm run seed:admin.

Route protection is centralized in proxy.ts. Read the current user on the client with the useAuth() hook.

Multi-tenancy

Each user gets a unique displayName slug at signup. Their tenant URL is /{displayName} — backed by the dynamic app/[workspace] route. Unauthenticated visits to a protected route redirect to /auth/login; authenticated users without a displayName are sent to /auth/complete-profile.

White-label & theming

Branding (company name, logo, site title/description, primary color) resolves with the priority database > environment variable > default. Set env vars and run the seed script, or edit the site_config collection directly.

npm run seed:site-config

The primary theme color is injected at runtime from BRAND_PRIMARY / BRAND_PRIMARY_DARK and sanitized before being written to a <style> tag. For deeper restyling (neutrals, per-component vars) edit the CSS variables in app/globals.css.

Database

MongoDB through Prisma 6. There are no migration files — sync the schema with prisma db push and regenerate the client with prisma generate after any schema change.

npm run db:setup      # first-time: generate client + push schema + seed branding
npx prisma generate   # regenerate client after schema changes
npx prisma db push    # sync schema to MongoDB
npx prisma studio     # browse data in a GUI

Stay on Prisma 6.x — Prisma 7 dropped MongoDB support.

Deployment

The kit is Vercel-ready (vercel.json is included). Set the environment variables in your hosting dashboard, point DATABASE_URL at MongoDB Atlas, and deploy. The build command runs prisma generate && next build.

npm run build   # prisma generate && next build
npm start       # run the production server

FAQ

Where do I change the brand name? Set COMPANY_NAME / SITE_TITLE in .env (or the site_config collection), then re-seed.

How do I add an admin? Set a user's role to ADMIN in the database. Admin routes are gated in proxy.ts.

Is there a test suite? Not yet — wire up your preferred runner (Vitest / Playwright) as you build.

Full reference lives in README.md at the repo root.