Frequently Asked Questions
Everything you need to know about Vyzora's architecture, security, and developer-focused analytics platform.
Sub-ms Ingestion
Events are enqueued in <1ms using our decoupled architecture.
Privacy First
GDPR ready. No PII storage. Full data ownership by default.
Zero Dependency
SDK is <3KB gzipped with zero external dependencies.
General & Basics
Vyzora is a modern, high-performance event analytics platform built for engineering teams who need deep insights without sacrificing privacy or performance.
It's a streamlined 4-step process: 1. Capture (SDK), 2. Ingest (Scalable API), 3. Queue (Redis/BullMQ), and 4. Persist (PostgreSQL). This decoupled flow ensures your app stays fast while data is processed reliably in the background.
Yes. Integration takes less than 2 minutes. Just install the zero-dependency SDK, initialize it with your API key, and Vyzora automatically begins tracking page views and sessions.
Vyzora is built for developers and product teams who want full control over their data, sub-millisecond tracking latency, and a scalable architecture that grows with their traffic.
Vyzora is designed for horizontal scaling. By using an asynchronous producer-consumer model, it can handle thousands of events per second by simply adding more API or worker replicas.
To prevent your database from becoming a bottleneck. By enqueuing events first, we can acknowledge the request in less than 1ms and let the background workers handle the heavy lifting of saving to the database.
The SDK captures the interaction (like a page view or click), batches it with others, and sends it to our ingestion server. We validate it instantly and push it into a high-speed Redis queue. Workers then pick it up for permanent storage.
Our stack is built for speed: Nginx for load balancing, Express for the API, Redis and BullMQ for reliable queuing, and Prisma with PostgreSQL for scalable data persistence.
Yes. Our system is designed with a 'Buffer First' approach. Even during massive traffic spikes, events are safely held in our high-speed Redis queue until the workers can process them, ensuring no data loss and zero impact on your application's performance.
Architecture & System Design
Shared TypeScript types between SDK, backend, and frontend. Single source of truth for versioning. Atomic commits across multiple packages. Easier dependency management with npm workspaces.
SDK batches events → POST to Nginx → Round-robin to API replica → Zod validation → Redis LPUSH → BullMQ worker picks up → Prisma bulk insert → PostgreSQL with composite index.
Monolithic version had database writes blocking API responses. Decoupling into Producer (API) + Consumer (Worker) achieved sub-millisecond API response by offloading writes to background jobs.
Docker Compose with --scale api=N --scale worker=M. Nginx load balances across API replicas. BullMQ distributes jobs across worker replicas. Each service has tuned connection pool limits.
Redis LPUSH is <0.5ms vs database INSERT ~10-50ms. BullMQ provides retry logic, exponential backoff, job prioritization, and monitoring out of the box. Better separation of concerns.
API returns 500 errors. Events are lost (acknowledged trade-off for speed). Could add: API fallback to direct DB writes, or Redis cluster with replication for HA.
SDK: Full-path guard (pathname + search), tracks lastTrackedPath, only fires on actual navigation changes. Backend: skipDuplicates: true in Prisma createMany.
Composite index on (projectId, createdAt) because all queries filter by project first, then aggregate by time ranges. This makes time-series queries use index-only scans.
Nginx provides keepalive connection pooling (32 connections), health checks, request buffering, and granular routing control. Better performance for HTTP workloads.
Add more API/worker replicas, scale Redis (cluster mode), implement connection pooling at database, add CDN for SDK delivery, consider regional deployments for latency.
PostgreSQL: Supabase handles automated backups. Redis: Ephemeral queue, acceptable to lose in-flight jobs. Frontend: Vercel has automatic rollbacks. Backend: Docker images tagged by version.
Managed connection pooling (transaction mode on port 6543), automated backups, built-in monitoring, easier to scale vertically, and focus on application logic instead of database operations.
Producer (API): Validates requests, enqueues to Redis, returns immediately. Consumer (Worker): Polls Redis, processes batches, handles retries. Decouples request handling from expensive I/O.
Per-worker connection pool limits (5 connections), bulk inserts reduce transaction overhead, workers process jobs sequentially with concurrency limits, monitoring Redis queue depth.
Benchmarked ~4500 req/s on API gateway (M2 Pro, local). Production bottleneck would be worker consumption rate, depends on database IOPS. Scalable by adding workers.
Legacy monolithic version kept for backwards compatibility and simpler deployment scenarios. Scalable version is production-recommended. Allows gradual migration.
Lazy reconstruction at query time. Group events by sessionId, calculate MIN(createdAt) for start, MAX(createdAt) for end, COUNT(*) for depth. No session objects stored.
No server-side overhead, no HTTP header bloat, works in cross-domain scenarios, persists across tab closures, and SDK controls lifecycle directly.
Lost events if Redis crashes (no persistence), eventually consistent writes (async workers), no strict deduplication (accepted minor duplicates), simplified session model.
WebSocket connection from dashboard to backend, Redis Pub/Sub for event broadcasts, worker publishes to channel after DB write, frontend subscribes and updates metrics live.
SDK Design & Frontend
Zero dependencies, vanilla TypeScript, tree-shakeable ESM build, aggressive minification with tsup, batching reduces payload size, no polyfills.
Use splice(0, queue.length) to atomically clear the queue BEFORE transport. If transport fails, events are already gone (acceptable loss). Prevents race conditions during concurrent flushes.
Boolean flushing flag set before transport, cleared in finally block. Guards against simultaneous visibilitychange + pagehide events firing and triggering multiple flushes.
sendBeacon is designed for page unload (queued by browser, survives page close). fetch with keepalive: true is fallback for when sendBeacon unavailable or fails.
Wrap history.pushState and history.replaceState using monkey-patching. Listen to popstate. Fire pageview only when full path (pathname + search) changes, ignore hash-only changes.
localStorage blocked → SDK catches exception → uses in-memory fallback for visitor ID. Session works identically. No crashes, graceful degradation.
Industry standard (Google Analytics uses 30 min). Balances between session continuity and accurate bounce rate. Configurable via SDK but defaults to 30 min.
Singleton guard (historyWrapped flag) ensures window.history patched exactly once. Multiple SDK instances share global registry. First instance wins.
20 events: Prevents unbounded queue growth on high-traffic sites. 10 seconds: Ensures timely delivery on low-traffic sites. Whichever comes first.
Single retry for 5xx or network errors only. Never retry 4xx (client error, won't succeed). No re-insertion into queue (events lost on persistent failure). Prevents infinite loops.
When enabled: false, SDK is completely inert. No listeners attached, no history patching, no localStorage access, no network calls. Zero overhead.
tsup is zero-config, built on esbuild (fast), handles ESM + CJS dual output automatically, tree-shaking by default, perfect for libraries.
Shared Zod schemas in monorepo. SDK imports validation types from backend package. Type mismatches caught at compile time.
Reduces integration friction (just initialize SDK). Most metadata (browser, OS, screen) is universally useful. Users can extend via custom properties.
Currently polling with React Query (refetch interval). Future: WebSocket connection with server-sent events for live metrics.
Security & Privacy
If database compromised, attackers can't use keys to ingest fake data. One-way hashing means keys are validate-only, not retrievable.
Keys only returned once on project creation. Never returned in list endpoints. Frontend never stores keys (user copies immediately). Rate limiting prevents brute force.
Logout requires Referer or Origin header matching backend domain. Prevents attacker's site from making cross-origin logout requests to force user re-authentication.
Prevents deploying with default/weak secrets. Server crashes on boot if JWT_SECRET is "your-secret-key" or empty. Fail-fast for security misconfigurations.
Zod schema enforces scalar values only (string, number, boolean). No nested objects allowed in metadata. Prevents __proto__ injection attacks.
Prevents DoS via massive payloads. 500 events is generous for batching but prevents multi-MB requests. HTTP body size also limited to 2MB at Nginx.
express-rate-limit with different limits per route: Auth (5/min), Project Creation (10/hr), Ingest (1000/min), Metrics (100/min). Sliding window, IP-based.
JavaScript cannot access HttpOnly cookies, preventing XSS attacks from stealing tokens. Cookie automatically sent with requests, no localStorage exposure.
Ingestion endpoint: Reflected origin (accepts all). Dashboard endpoints: Strict whitelist (frontend URL only). Different policies for different security contexts.
Prisma uses parameterized queries. Raw SQL uses bound parameters ($1, $2). Never string concatenation. Zod validates all inputs before queries.
GDPR compliance by design. Visitor/Session IDs are random UUIDs, no user emails/names. IP addresses not stored. User can opt-in to track user IDs via identify().
Automated npm audit in CI/CD. Manual patches when high/critical found (Axios, Next.js, express-rate-limit). Lock files prevent unexpected updates.
Prevents leaking internal database schema, file paths, environment details to attackers. Generic 500 errors only. Detailed logs go to monitoring, not API responses.
Allow multiple active keys per project, add expires_at field, require old key for generating new, grace period where both work, then invalidate old.
Zod schemas at API boundary, validate before business logic, fail fast with 400 + field-level errors, whitelist approach (only allow known fields).
Performance & Optimization
API key validation is database lookup on every ingest request. Caching eliminates 95%+ of DB queries. 5 min balances freshness (key revocation) with hit rate.
Ownership checked on analytics queries (less frequent than ingest). 5 sec prevents race conditions when user creates/deletes projects. Shorter than API key cache.
Replaced ORM with raw SQL for aggregations, composite index on (projectId, createdAt), parallel execution with Promise.all, single batch endpoint instead of 6 separate calls.
Originally 6 sequential requests: pageviews, visitors, sessions, top pages, top events, browser stats. Consolidated into 1 batch request returning all metrics.
createMany sends single SQL statement with multiple VALUES rows. Reduces round-trips, transaction overhead, index updates. ~10x faster for bulk operations.
Prisma maintains pool of reusable connections. Workers configured for 5 connections each. Prevents "too many clients" error. Pool exhaustion causes queuing, not crashes.
Gzip responses reduce bandwidth by 70-90% for JSON. Especially valuable for analytics responses (large arrays). Minimal CPU cost, huge transfer savings.
Use COUNT(DISTINCT) aggregations instead of fetching all records. Prisma include with caution. Raw SQL for complex joins. Dataloader pattern for batch fetching.
PostgreSQL can cast timestamptz to specific timezone during aggregation. Eliminates client-side timezone math, ensures consistent day boundaries, leverages DB's optimized date functions.
Partition events table by date, archive old data to cold storage, materialized views for common aggregations, read replicas for analytics, separate OLAP database.
Reuses TCP connections to upstream API servers. Eliminates handshake overhead. 32 persistent connections handle thousands of requests. Reduces latency by ~20-40ms.
Composite index (projectId, createdAt) allows index-only scans for queries like "events in last 30 days for project X". PostgreSQL can skip table access entirely.
Cleaner error handling, easier to read sequential logic, prevents callback hell, works well with Prisma promises, allows try/catch for graceful failure.
Log warning if Redis queue depth >1000 items. Indicates workers falling behind. Trigger: add more workers, investigate slow queries, check database CPU.
~2-3 seconds: Load environment, initialize Prisma, connect to Redis, compile TypeScript (if dev). Production Docker image is pre-built, so <1 second.
DevOps & CI/CD
Push → GitHub Actions triggers → Parallel jobs (lint, build, test, security audit) for each workspace → Vercel auto-deploys frontend → Manual EC2 deploy for backend.
Fail fast on lint errors (cheap), don't waste time building. Parallel execution saves time. Different teams might own different jobs in production.
Root .env for local dev. Vercel dashboard for frontend env vars. EC2 uses systemd environment files. GitHub Secrets for CI/CD. Never commit secrets.
Reproducible builds, dependency isolation, easy rollbacks (tag versions), works locally and in prod identically, scales horizontally with docker-compose.
Semantic versioning (major.minor.patch). Root version tracks overall system. Each package (SDK, backend, frontend) has independent version. Synchronized via changelogs.
prisma migrate deploy in CI/CD before app deployment. Migrations are forward-only, additive changes preferred. Backwards-compatible schema changes to avoid downtime.
Continuous deployment. Every merged PR triggers deploy. Bug fixes, features, security patches. Averages to ~1 deployment per day. Rapid iteration.
Vercel: One-click rollback to previous deployment. EC2: Deploy previous Docker image tag. Database: Migrations are harder, requires backup restoration.
node_modules, .env, .next, dist, build, Prisma generated client, logs, OS files (.DS_Store), IDE configs.
Native npm support (npm 7+), no extra dependencies, simple for this scale. Turborepo overkill for 3 packages. Lerna is deprecated.
Lock files (package-lock.json), Docker base images pinned to specific versions, Prisma schema versioned, Node.js version specified in .nvmrc.
Faster (Vite-powered), native ESM support, better TypeScript integration, less configuration, modern API. Jest works but Vitest is future.
Unit tests for queue logic, storage wrappers, metadata extraction. Integration tests with mock API. Manual testing in browser for SPA navigation.
Currently: Application logs, Vercel analytics, PostgreSQL metrics from Supabase. Future: Add Sentry for errors, Datadog for APM, Grafana dashboards for Redis/queue metrics.
Run two identical EC2 environments. Deploy to "green" while "blue" serves traffic. Smoke test green. Switch Nginx upstream to green. Keep blue for quick rollback.
Business Logic & Features
Prevent abuse, encourage cleanup of unused projects, simplifies UI (no pagination needed), reduces database bloat, aligns with pricing model (future paid tiers).
Replaces browser's ugly window.confirm(). Styled consistently with app theme. Shows project name for confirmation. Prevents accidental deletions.
Pageviews are automatic, standard across all sites. Custom events are user-defined actions (signups, purchases). Different aggregation needs and use cases.
Define sequence of events (e.g., "view_product → add_to_cart → purchase"). Query events grouped by sessionId, calculate conversion at each step, aggregate across users.
User-agent parsing is consistent, accurate, no integration effort. Users might misconfigure. Auto-capture ensures data quality across all implementations.
versions.json stores release history. Modal fetches on button click, renders version list, shows details for selected version. No backend required.
No password management, no "forgot password" flow, leverages trusted identity provider, developer-focused audience already has GitHub, reduces security surface.
Add Team model, many-to-many with Users, project ownership by team, invite system with email tokens, role-based permissions (owner/admin/viewer).
Most sites have long tail of low-traffic pages. Top 10 pages cover 80%+ of traffic. Keeps UI focused, reduces cognitive load, faster queries.
CSV endpoint: GET /api/projects/:id/export?start=X&end=Y. Stream CSV rows from cursor-based pagination. Background job for large exports, email download link.
Lessons Learned & Future
Timezone drift causing day buckets to misalign between backend aggregation and frontend rendering. Required understanding PostgreSQL timezone functions and JavaScript Date behavior.
Start with microservices architecture from day one. Add comprehensive tests earlier. Use TypeScript strict mode from start. Plan for observability (logging/monitoring) upfront.
Privacy-first requirement, no third-party trackers, full data ownership, learning experience, developer-friendly API, can self-host.
Real-time analytics via WebSockets, user retention cohorts, A/B testing framework, alerts/anomaly detection, data export API, team collaboration, custom dashboards.
MVP first (ingestion + basic metrics), then iterate. Documented features in CHANGELOG. Resisted adding complexity until core was solid. Feedback-driven roadmap.
Vyzora tracking Vyzora (dogfooding). Monitor: SDK load times, API response latency, error rates, deployment frequency, test coverage.
Freemium: 10K events/month free. Paid tiers: higher limits, team features, custom domains, data retention, priority support, SLA guarantees.
Users wanted mobile responsiveness more than advanced analytics. Taught me: solve existing problem perfectly before adding features.
Impact vs. effort matrix. Security/performance > features. User-reported bugs > nice-to-haves. Foundation (auth, ingestion) before polish (charts, animations).
Ship early, iterate based on real usage. 68 deployments taught more than any tutorial. Production bugs drive deep learning. Documentation is code too.
Still have questions?
Can't find what you're looking for? Reach out to our engineering team.
Contact Support