Skip to content
Espejo Case study

Inside Espejo

I wanted a digital journal where nobody could read my entries. Not the server, not the database, not even me if I lose the password. That's how Espejo started.

Offline-first with IndexedDB as source of truthClient-side encryption: the server only sees blobsWell-being UX, no gamification
espejo.day ↗
Problem

Journaling apps push streaks and gamification. And none offer real privacy: your entries live in plain text on someone else's servers.

Architecture

Offline-first with optional sync. The server never sees the content.

  • IndexedDB (Dexie) as source of truth, works without connection
  • AES-256-GCM + PBKDF2 (310k iterations) encryption on client
  • Supabase for optional cross-device sync (encrypted blobs only)
  • Soft-delete + last-write-wins for conflict resolution
UX

Designed from well-being psychology.

  • Emotional check-in before writing, less friction on hard days
  • Contemplation mode: immersive reading with serif typography
  • Semantic favorites (clarity, seed, anchor, victory, scar)
  • Year in Review, Spotify Wrapped style
  • Nudges with priority: care > insight > celebration
Stack
  • Next.js 16 (App Router) + React 19 + TypeScript
  • TipTap (rich text), Tailwind 4, shadcn/ui (Radix)
  • Vitest + Testing Library (unit) / Playwright (E2E)
  • Vercel (deploy) + Sentry (errors) + CSP/HSTS headers
Next.jsReactTypeScriptSupabaseDexieTipTapTailwindPlaywright
Learnings

When people actually use it, you think about code differently.

Offline-first with E2EE is not trivial. Migrating schemas in IndexedDB, resolving sync conflicts without seeing the data, handling password loss when it's the encryption key. Every problem pushes you past the happy path.

All of the above sounds good in theory. But you don't have to take my word for it — try it yourself.

Try the encryption
Write something. Watch it encrypt. Try reading it without the key.
Write
Key
Encrypted
Decrypted