Minimal, artful landing page built with Next.js App Router + Tailwind CSS (v4), featuring:
- Dark/light theme toggle (next-themes)
- Divizend logo linking to the main site
- Hero headline: “Shape the Future of FinTech”
- Email sign-up form for the beta program
- Optional Cloudflare Turnstile (invisible) verification
- Server-side sign-up API with Drizzle ORM + PostgreSQL
- Tracking of UTM parameters, locale, timezone, referrer, user agent, and client IP
- Next.js 15 (App Router, RSC-first)
- Tailwind CSS 4
- next-themes (dark/light)
- Drizzle ORM + drizzle-kit + postgres (PostgreSQL driver)
- Optional: Cloudflare Turnstile (invisible)
- Install dependencies
pnpm install-
Configure environment variables (see .env example below)
-
Initialize database schema (requires
DATABASE_URL)
pnpm db:push
# or, if you prefer migration files
pnpm db:generate
pnpm db:migrate- Run the dev server
pnpm devCreate .env.local with:
# Required for database (PostgreSQL connection string)
DATABASE_URL=postgres://USER:PASSWORD@HOST:PORT/DB
# Optional: Cloudflare Turnstile (invisible)
# If NOT provided, Turnstile is skipped (form will still submit)
NEXT_PUBLIC_TURNSTILE_SITE_KEY=your_public_site_key
TURNSTILE_SECRET_KEY=your_secret_keyDrizzle schema lives in lib/db/schema.ts.
Table: beta_signups
idUUID (PK)emailtext (unique, not null)user_agenttextreferrertextip_addresstextutm_sourcetextutm_mediumtextutm_campaigntextutm_termtextutm_contenttextlanguagetexttimezonetextcreated_attimestamp default now()
CLI scripts (package.json):
pnpm db:push # push schema directly to DB (great for prototyping)
pnpm db:generate # generate SQL migrations based on schema
pnpm db:migrate # apply generated migrations
pnpm db:studio # open Drizzle StudioEndpoint: POST /api/submit-email
Request body (JSON):
{
"email": "user@example.com",
"token": "<turnstile-token-or-null>",
"utmSource": "...",
"utmMedium": "...",
"utmCampaign": "...",
"utmTerm": "...",
"utmContent": "...",
"language": "en-US",
"timezone": "Europe/Berlin"
}Behavior:
- If
TURNSTILE_SECRET_KEYis set, a validtokenis required (server verifies with Cloudflare). - If
TURNSTILE_SECRET_KEYis NOT set, Turnstile is skipped. - Idempotent: Re-submitting an existing email returns success without creating duplicates.
Sample responses:
{ "success": true, "message": "Email submitted successfully" }
{ "success": true, "message": "Email already registered" }
{ "success": false, "message": "Invalid email address" }
{ "success": false, "message": "Verification failed" }- Page:
app/page.tsx - Theme provider:
components/theme-provider.tsx - Theme toggle:
components/theme-toggle.tsx - Email form (client):
components/email-form.tsx
The email form collects UTM parameters from the URL, and locale/timezone from the browser. It conditionally renders the Turnstile component when NEXT_PUBLIC_TURNSTILE_SITE_KEY is present.
pnpm build
pnpm lint- The
drizzledirectory (migrations/output) is ignored by Git via.gitignore. - Images from
divizend.comare allowed vianext.config.tsremotePatterns. - The Divizend logo is served from
public/divizend-tworows-white.svg(linked to https://divizend.com).