Tech Stack
| Category | Technology |
|---|---|
| Framework | Next.js 16 (App Router), React 19, TypeScript |
| Database | Supabase (PostgreSQL + Auth + Storage + RLS) |
| Auth | Supabase Auth — Magic Link + OTP code fallback (length configurable 6–10 via OTP_LENGTH, default 6; sent via the active email provider) with cross-tab auto-redirect; OAuth providers from a config-driven catalog. |
| Payments | Stripe (Subscriptions + One-time) |
| AI | LangChain + Multi-LLM (OpenAI, Anthropic, Google) |
Provider-agnostic abstraction — Brevo or Mailjet (transactional + newsletter), pluggable via EMAIL_PROVIDER |
|
| Styling | Tailwind CSS 4, shadcn/ui (New York style), hue-parameterised oklch tokens — rotate --brand-h in app/globals.css to rebrand the whole product |
| Fonts | DM Sans (body) + JetBrains Mono (numerals, code, logs), self-hosted via next/font/google |
| Animation | Motion (motion) mounted through LazyMotion on the marketing surface only; dashboards use CSS transitions on the duration/easing tokens |
| i18n | URL-based routing (/fr-FR/..., /en-US/..., /en-CA/..., /fr-CH/...) |
| Security | CSRF, rate limiting (Upstash Redis), CSP headers, recent-auth gates for destructive admin actions |
| Bot Protection | Cloudflare Turnstile (invisible CAPTCHA) |
| Analytics | Google Tag Manager, Google Ads, Meta Pixel, X Pixel (all consent-gated) |
| Live Chat | Crisp (consent-gated user identification) |
| Caching | Upstash Redis (rate limiting, sessions) |
| Background Jobs | Internal scheduler + Supabase Edge Functions |
Third-Party Integrations
The boilerplate includes pre-configured integrations for common SaaS needs:
Stripe
Stripe powers all billing: subscriptions (Free, Pro, Business plans), one-time credit pack purchases, and license payments. It includes Checkout Sessions for payment collection, Customer Portal for self-service subscription management, and a comprehensive webhook flow for lifecycle events (subscription changes, invoice payments, refunds, disputes). The route at app/api/stripe/webhook/route.ts verifies the signature and delegates to core/billing/stripe-webhook.ts; side effects are split across focused modules such as core/billing/mutations.ts, core/billing/customer-events.ts, core/billing/risk-events.ts, and core/billing/free-plan.ts. Plans are defined in config/pricing.ts and matched by Stripe Price ID. Promotion/coupon codes can be enabled at checkout via the allowPromotionCodes flag in the pricing config.
Supabase
Supabase provides the entire backend layer: PostgreSQL database with Row Level Security (RLS), authentication (magic links + OAuth), file storage for media uploads, and Edge Functions for background job execution. Three client types are available: createClient() for user-scoped server requests, createServiceClient() to bypass RLS for admin operations, and supabaseAdmin as a pre-instantiated admin client.
Adding your own account-scoped table
Every domain table carries account_id (never a bare user_id) and is filtered by membership. Replicate this pattern for any new core/app table, and mirror the change into supabase/schema.sql in the same commit. CMS/blog table changes mirror into supabase/cms-schema.sql. Migrations alone are not enough because fresh resets use the relevant full-schema file:
alter table my_table enable row level security;
create policy "members_read" on my_table for select
using (exists (
select 1 from memberships m
where m.account_id = my_table.account_id
and m.user_id = auth.uid()
));
-- INSERT/UPDATE/DELETE must be at least as strict as SELECT
create policy "members_write" on my_table for all
using (exists (
select 1 from memberships m
where m.account_id = my_table.account_id
and m.user_id = auth.uid()
));If a policy needs to query memberships recursively, use a SECURITY DEFINER helper (e.g. user_belongs_to_account()) instead of an inline sub-select to avoid RLS recursion.
AI / Multi-LLM
AI is orchestrated via LangChain behind one server-only LlmClient. AI_LLM_TRANSPORT selects direct OpenAI/Anthropic/Google APIs or OpenRouter for chat and embeddings together; gateway requests enforce ZDR/data-collection-deny routing. A dynamic registry provides chat, code-assistant, translator, writer, RAG, and custom agents without exposing provider SDKs to agent code. Streaming SSE, abort/time ceilings, provider-reported token accounting, bounded embeddings, prompt caching, and model-provider/transport audit persistence are normalized at the boundary.
Cloudflare Turnstile
Turnstile provides invisible bot protection without annoying CAPTCHAs. It's used on contact forms, login, and other sensitive endpoints.
Google Tag Manager, Google Ads, Meta Pixel & X Pixel
Analytics are consent-aware and privacy-by-default. GTM, gtag.js / Google Ads, Meta Pixel, X (Twitter) Pixel, and purchase/signup conversion events only load or fire after the user grants the matching analytics or marketing consent. Consent Mode v2 still starts deny-by-default, but third-party scripts are not mounted before consent. To enable Google Tag Manager, set NEXT_PUBLIC_GTM_ID. For Google Ads conversion tracking, set NEXT_PUBLIC_GOOGLE_ADS_ID and NEXT_PUBLIC_GOOGLE_ADS_CONVERSION_LABEL. Optionally set NEXT_PUBLIC_GA_MEASUREMENT_ID for GA4 via gtag.js (skip if GA4 is already configured in your GTM container to avoid duplicate events). For Meta Pixel, set NEXT_PUBLIC_META_PIXEL_ID. For the X Pixel, set NEXT_PUBLIC_X_PIXEL_ID plus the optional per-action conversion event IDs NEXT_PUBLIC_X_PIXEL_EVENT_PURCHASE / NEXT_PUBLIC_X_PIXEL_EVENT_SIGNUP. A unified trackConversion() method fires purchase events only when marketing consent is granted.
Crisp Live Chat
Crisp provides live chat with automatic user identification for logged-in users. Set the NEXT_PUBLIC_CRISP_WEBSITE_ID variable to enable it. The script and user-identification payload are gated by consent, so Crisp is not loaded before the user grants the configured support/marketing consent category.
Email (Brevo / Mailjet)
Transactional emails (magic links, workspace invitations, contact-form confirmations, license expiration warnings, organization deletion notices) and newsletter management go through a provider-agnostic abstraction in lib/email/. Switching providers is a config-only change — set EMAIL_PROVIDER and supply the matching credentials.
Supported providers:
brevo— Brevo (formerly Sendinblue). Single API key (BREVO_API_KEY) +BREVO_NEWSLETTER_LIST_ID.mailjet— Mailjet Send API v3.1 + Contacts. Two keys (MAILJET_API_KEY_PUBLIC+MAILJET_API_KEY_PRIVATE) + optionalMAILJET_NEWSLETTER_LIST_ID.noop— logs and reports success without delivering. Useful for dev / CI.
The shared sender identity (EMAIL_FROM_NAME, EMAIL_FROM_ADDRESS) and inbox addresses (CONTACT_EMAIL, SUPPORT_EMAIL) apply to every provider. Email templates are bilingual (FR / EN) and live in lib/email/templates.ts; lib/email/translations.ts holds the strings. Failed transactional sends are queued in the pending_emails table and retried with exponential backoff by the process-pending-emails job.
Adding a new provider is a 3-step change: implement the EmailProvider interface under lib/email/providers/<name>.ts, add a credentials slice in config/email.ts, and add a branch in lib/email/provider.ts. No call sites need to change.
Upstash Redis
Upstash Redis provides distributed rate limiting across all API endpoints and shared LLM circuit-breaker state across workers. Set UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN to enable it. Both are required in production — the app refuses to boot without them. Process-local state is per-instance, so behind a load balancer it does not merely fail to share rate limits, it leaves every endpoint effectively unlimited and lets LLM circuit decisions disagree. Local state remains the development default, and a genuinely single-instance production deployment can opt in with ALLOW_INMEMORY_RATE_LIMIT=true.
Serwist (PWA)
Serwist powers the Progressive Web App layer with service worker generation, offline support, and push notifications. It provides automatic precaching of static assets, stale-while-revalidate for resources, network-first for API calls, and an offline fallback page. Push notifications use the Web Push API with VAPID authentication, per-device subscriptions stored in the database, and user notification preferences. Configurator mode keeps Next.js on Turbopack, then @serwist/cli bundles the worker from serwist.config.mjs; SerwistProvider handles browser registration.
Account-Centric Model
Everything is attached to an Account, never directly to a User. This enables multi-tenant architecture with clear separation of resources.