Skip to main content

Tech Stack

NordJari is a Next.js 14 app deployed on Vercel, backed by Supabase (PostgreSQL) and the Anthropic Claude API.

Full stack overview

LayerTechnologyNotes
FrameworkNext.js 14 (App Router)Server components by default
LanguageTypeScript (strict mode)any is forbidden
StylingTailwind CSSCustom NordJari palette tokens
DatabaseSupabase (PostgreSQL)Managed Postgres + Auth
AuthSupabase AuthEmail magic link
ORMPrismaSchema in prisma/schema.prisma
AI CoachAnthropic Claude APIclaude-sonnet-4-20250514
DeploymentVercelCron jobs via Vercel Cron
Domainnordjari.comWiki at wiki.nordjari.com

Next.js App Router conventions

  • All pages and layouts are server components unless they need interactivity
  • 'use client' is added only at the lowest component that requires browser APIs or React hooks
  • API routes live in app/api/ and return { data, error } consistently
  • DB queries are always in lib/ — never inlined in route handlers or components

Supabase + Prisma

NordJari uses both Supabase and Prisma together:

  • Prisma for schema definition, migrations, and typed queries (lib/prisma.ts)
  • Supabase Auth for authentication (magic link email flow)
  • Supabase RLS (Row Level Security) on all tables — enforced at the database level

The DATABASE_URL (pooled) and DIRECT_URL (direct) are both configured so Prisma migrations work correctly with Supabase connection pooling.

Authentication pattern

// Always use getUser() — never getSession() for server-side auth
const { data: { user } } = await supabase.auth.getUser()
if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })

// Resolve DB userId via email (Supabase UUID != Prisma UUID in some setups)
const dbUser = await prisma.user.findUnique({ where: { email: user.email! } })
const userId = dbUser?.id ?? user.id

Cron jobs

Two cron jobs run on a schedule:

JobScheduleRoute
Nightly score recalculation0 2 * * *POST /api/cron/score
Monday weekly coach brief0 8 * * 1POST /api/cron/coach-brief

Both routes verify a x-cron-secret header against CRON_SECRET env var.

Environment variables

DATABASE_URL= # Supabase pooled connection string
DIRECT_URL= # Supabase direct connection string (for migrations)
NEXT_PUBLIC_SUPABASE_URL= # Supabase project URL
NEXT_PUBLIC_SUPABASE_ANON_KEY= # Public anon key (safe to expose)
SUPABASE_SERVICE_ROLE_KEY= # Server-only — never NEXT_PUBLIC_
ANTHROPIC_API_KEY= # Server-only — never NEXT_PUBLIC_
CRON_SECRET= # Vercel cron authentication secret

See also