Zero-backend, encrypted family tree visualizer stored entirely in the URL.
No accounts. No servers. No tracking. Just share a link + passphrase.
Live Demo · How It Works · Getting Started · Contributing
- Completely client-side — no server, no database, no accounts
- End-to-end encrypted — AES-256-GCM via the Web Crypto API; PBKDF2 with 600k iterations
- URL-as-database — the entire family tree is Brotli-compressed, encrypted, and stored in the URL hash
- Share by link — give someone the URL + passphrase and they can view & edit the tree
- Interactive tree visualization — Buchheim-Reingold-Tilford layout algorithm with couple containers, pan/zoom, smooth animations
- Relationship types — parent-child, spouse, and sibling relationships with auto-inferred suggestions
- Multilingual — English, Arabic (RTL), and Turkish
- Export/Import — JSON, PNG, and SVG export; JSON import
- Undo/Redo — full history stack (up to 50 snapshots)
- Privacy-first — data never leaves your browser; passphrase kept in memory only
Roots stores the entire family tree in the URL fragment (hash). The data pipeline runs entirely in the browser:
┌──────────┐ ┌────────────────┐ ┌─────────────────┐ ┌────────────┐ ┌──────────┐
│ JSON │────▸│ Brotli │────▸│ AES-256-GCM │────▸│ Base64url │────▸│ URL │
│ (Family │ │ Compress │ │ Encrypt │ │ Encode │ │ Hash │
│ Tree) │ │ (WASM, q=11) │ │ (Web Crypto) │ │ │ │ #... │
└──────────┘ └────────────────┘ └─────────────────┘ └────────────┘ └──────────┘
Loading reverses the pipeline: URL hash → base64url decode → AES-GCM decrypt → Brotli decompress → JSON
Encrypted bytes have maximum entropy and don't compress well. By compressing the raw JSON first (which is highly compressible), we get the best size reduction before encryption makes the data incompressible.
| Parameter | Value |
|---|---|
| Algorithm | AES-256-GCM |
| Key derivation | PBKDF2 with 600,000 iterations |
| Salt | 16 bytes (random, prepended to ciphertext) |
| IV | 12 bytes (random, prepended after salt) |
| Wire format | [16B salt][12B IV][ciphertext] |
The tree visualization uses the Buchheim-Reingold-Tilford algorithm — an O(n) tree layout via contour comparison:
- BFS from root assigns generation tiers (0 = root, negative = ancestors, positive = descendants)
- Couple containers merge spouse pairs into single layout units
- First walk (post-order) assigns preliminary x-positions via contour comparison (
apportion) - Second walk (pre-order) accumulates modifiers for final x-coordinates
- Third walk shifts everything to non-negative x
Guarantees: parents centered over children, subtrees never overlap, identical subtrees drawn identically, middle siblings evenly spaced.
| Layer | Technology |
|---|---|
| Framework | React 19 |
| Language | TypeScript (strict) |
| State | Zustand |
| Layout math | D3 (hierarchy only — no D3 selections/DOM) |
| Animation | Framer Motion |
| Styling | Tailwind CSS v4 |
| Build | Vite 7 |
| Validation | Zod |
| Compression | Brotli (WASM) |
| Encryption | Web Crypto API |
- Node.js 18+ (20+ recommended)
- npm 9+
git clone https://github.com/AbdAsh/FamilyTreeVisualizer.git
cd FamilyTreeVisualizer
npm installnpm run dev # Start Vite dev server (http://localhost:5173)
npm run build # Production build with TypeScript checking
npm run preview # Preview the production build locally
npm run typecheck # Run TypeScript compiler without emittingsrc/
├── app/
│ └── App.tsx # Root component — header, tree, panels
├── components/
│ ├── editor/ # Edit panel, add-relative modal/form
│ ├── tree/
│ │ └── FamilyTreeView.tsx # SVG tree renderer (pan/zoom/interactions)
│ └── ui/ # Reusable UI primitives (Button, Modal, Panel, etc.)
├── hooks/
│ ├── useAuth.ts # Passphrase auth (Zustand store)
│ ├── useKeyboardShortcuts.ts
│ ├── useSave.ts # Auto-save (debounced hash write)
│ └── useTree.ts # Tree state + undo/redo (Zustand store)
├── lib/
│ ├── compression.ts # Brotli WASM compress/decompress
│ ├── crypto.ts # AES-256-GCM encrypt/decrypt via Web Crypto
│ ├── i18n.tsx # Translations (en, ar, tr) + context
│ ├── passphrase.ts # Strength scoring + brute-force throttle
│ ├── tree-utils.ts # Buchheim-Reingold-Tilford layout + helpers
│ ├── url.ts # Save/load pipeline (JSON ↔ compress ↔ encrypt ↔ hash)
│ └── validation.ts # Zod schemas for all data types
├── styles/
│ └── globals.css # Tailwind v4 theme + custom properties
└── types/
└── family.ts # Core data types (FamilyMember, Relationship, FamilyTree)
- URL size limit: ~8 KB max hash (
MAX_HASH_BYTES). All tree data must fit after compression + encryption. - No server calls: Everything runs in the browser. Web Crypto API and Brotli WASM are the only non-trivial runtime dependencies.
- Brotli WASM is excluded from Vite's
optimizeDepsto avoid bundling issues. - Relationship semantics:
from= parent,to= child forparent-childtype. Spouse/sibling are symmetric but stored with a canonical direction.
Roots is deployed on Cloudflare Pages. The wrangler.jsonc config points to the dist/ build output. To deploy:
npm run build
npx wrangler pages deployThe public/_redirects file handles SPA routing for Cloudflare Pages (and Netlify). Any static hosting service works — just ensure all paths resolve to index.html.
See CONTRIBUTING.md for development guidelines, code conventions, and how to submit changes.
For reporting security vulnerabilities (especially crypto-related), see SECURITY.md.
This project is licensed under the Apache License 2.0 — see LICENSE for details.
Abdulrahman Mahmutoglu — Senior Frontend Engineer
- Website: abdash.net
- GitHub: @AbdAsh
- LinkedIn: abdash