! Not yet ready. Currently in development phase.
A production-ready (soon) SaaS dashboard starter built with TanStack Start, Convex, and Clerk — with end-to-end encryption baked in from day one.
Use this starter as the foundation for any SaaS that handles sensitive user data: note-taking apps, document editors, personal finance tools, health trackers, or anything where users deserve to know their data is private by design.
Most SaaS products store user data in plaintext. This means the company (and anyone who gains access to their infrastructure) can read your users' data at any given time.
This starter takes a different approach. Sensitive data is encrypted on the client before it ever leaves the browser. The server only stores ciphertext and it has no access to decryption keys or plaintext, ever.
A full-stack SaaS dashboard template where you can build features on top of a privacy-first foundation:
- Authenticated dashboard with per-user encrypted document storage
- Client-side encryption — data is encrypted before upload, decrypted after download
- Zero-knowledge key management — keys are derived from the user's password, never sent to the server
- Real-time sync via Convex, without ever exposing plaintext to the backend
- Drop-in auth via Clerk with seamless key provisioning on sign-up/sign-in
- Frontend: TanStack Start, TanStack Router, TanStack Query
- Backend: Convex (real-time database)
- Auth: Clerk
- Encryption: OpenPGP.js (ECC P-256), @scure/bip39 (passphrase generation)
- UI: Tailwind CSS, shadcn, Radix UI
- State: Jotai
This is the foundation of a zero-knowledge architecture: the service provider genuinely cannot read what users store.
sequenceDiagram
participant U as User
participant C as Client
participant Cl as Clerk
participant Cx as Convex
note over U,Cx: Sign-Up
U->>C: email + password
C->>Cl: signUp.create()
Cl-->>U: verification email
U->>C: verification code
C->>Cl: attemptEmailAddressVerification()
Cl-->>C: createdSessionId
Note over C: createPassphrase() → 12-word BIP39 mnemonic
Note over C: encryptPassphrase(passphrase, password) → PGP symmetric
Note over C: generateKeyPair(email, passphrase) → ECC nistP256
C->>Cx: createUserKeys({ encryptedPassphrase, encryptedPrivateKey, publicKey })
Note over C: passphrase stored in Jotai atom (memory only, never persisted)
note over U,Cx: Sign-In
U->>C: email + password
C->>Cl: signIn.create()
Cl-->>C: session
C->>Cx: getUserKeys()
Cx-->>C: { encryptedPassphrase, encryptedPrivateKey, publicKey }
Note over C: decryptPassphrase(encryptedPassphrase, password)
Note over C: passphrase restored to Jotai atom (memory only)
note over U,Cx: Encrypt Data
C->>Cx: getUserKeys() → publicKey
Note over C: encryptWithPublicKey(plaintext, publicKey) → Armored encrypted message
C->>Cx: store ciphertext
note over U,Cx: Decrypt Data
C->>Cx: getUserKeys() → encryptedPrivateKey (reactive, already in memory)
C->>Cx: fetch ciphertext (application data)
Cx-->>C: encryptedPrivateKey + ciphertext
Note over C: passphrase from Jotai atom (memory only)
Note over C: decryptWithPrivateKey(encryptedData, encryptedPrivateKey, passphrase) → Decrypted plaintext
C-->>U: plaintext (never sent to server)
- Key generation: PGP key pairs (ECC nistP256) generated client-side in
src/lib/crypto.tsduring signup - Passphrase: 12-word BIP39 mnemonic (128-bit entropy), encrypted with the user's Clerk password before storage
- Storage: Convex stores only encrypted blobs—
encryptedPrivateKey,publicKey,encryptedPassphrase - Usage:
useEncryptionhook (src/hooks/useEncryption.ts) encrypts/decrypts data client-side; Convex cannot decrypt
npm install # Install dependencies packages
cp .env.example .env.local # Edit .env.local with your keys
npm run start # Starts Convex dev + Vite (port 3666)Required environment variables (from .env.example):
| Variable | Description |
|---|---|
VITE_CLERK_PUBLISHABLE_KEY |
From Clerk Dashboard |
CLERK_JWT_ISSUER_DOMAIN |
For Convex JWT verification |
VITE_CONVEX_URL, CONVEX_DEPLOYMENT |
Run npx convex init or set manually |
| Script | Description |
|---|---|
npm run start |
Convex dev + Vite dev server |
npm run dev |
Vite only (port 3666) |
npm run build |
Production build |
npm run test |
Vitest |
npm run lint |
ESLint |
npm run format |
Prettier |
src/routes/— TanStack Router file-based routingsrc/hooks/—useSignUp,useSignIn,useEncryptionsrc/lib/crypto.ts— PGP key generation, encrypt/decryptconvex/— Convex backend (userKeys, auth config)src/integrations/— Clerk, Convex providers
Paraglide i18n: Messages in project.inlang/messages; URLs localized via Paraglide Vite plugin and router rewrite hooks.
shadcn: Add components with pnpm dlx shadcn@latest add button
T3 Env: Type-safe environment variables. Add variables in src/env.ts, use via import { env } from '@/env'.