·

Our Tech Stack

A deep dive into the tools, libraries, and infrastructure that power Visitors and the reasoning behind each choice.

Daryl
Daryl
Founder
Our Tech Stack

I've had a few people ask me what Visitors is built with, so I thought I'd write up a quick breakdown of the tech stack and why I chose each piece.

Some of the decisions were made based on personal preferences, others were made based on the needs of the project.

Building an analytics platform isn't a simple task. You need to prepare for the worst case scenario as you never know who might sign up and use your service. You need to understand that you might be processing millions of events per day and you need to be able to handle it with little to no downtime.

Our entire infrastructure is self-hosted in Europe on Hetzner. We use their servers for our database and application servers.

We use Cloudflare for DNS, DDoS protection for most of our services. Their free tier is generous and the performance is excellent.

Let's dive into the tech stack.

Backend

Bun

Every backend service runs on Bun. I chose Bun not only because it's the hot new thing, but because it's very fast and it has native TypeScript support which makes local development a breeze.

We serve all requests using Bun's native HTTP server. The apps are extremely simple so there was little need to use any frameworks.

We have three separate apps. One for event ingestion, one for our API layer and one for our background workers which handle the processing of events, emails and more.

tRPC

The API is built with tRPC, which gives us end-to-end type safety between the frontend and backend.

Honestly, tRPC has been a game changer. One of the greatest things about it is the ability to batch requests. For example, when adding a dashboard filter, we can update all widgets in a single request, instead of one request per widget.

These small saves add up and it's what makes our app feel snappy.

PostgreSQL

For general application data like users, projects, and settings, we use PostgreSQL with Drizzle as our ORM. Drizzle comes with a lot of great features, including migrations, a simple query builder and type safety.

There are some complex batch updating cases where we use raw SQL queries instead of Drizzle, but for the most part, Drizzle is a perfect fit.

For Postgres itself we use Bun's native SQL bindings which is orders of magnitude faster than the official PostgreSQL driver.

ClickHouse

All analytics data lives in ClickHouse, separate from the application database. I made a choice to separate the application data from analytics data as I feel like different technologies are better suited for different things.

ClickHouse is a columnar database built for fast writes and aggregations, PostgreSQL is a relational database built for complex queries and relationships.

For the kind of queries Visitor's needs, like aggregating millions of rows by time range or grouping by referrer, these would choke PostgreSQL eventually, whereas Clickhouse handles them effortlessly.

I've also read enough stories from other analytics services who started with something else and eventually had to migrate to ClickHouse anyway, so it only made sense.

Redis

Redis is a huge part of our tech stack. It's primary use case is for queueing our events for background processing.

When events roll in, we don't insert them right away, we anonymise them and queue them for background processing where they are then processed and batch inserted into Clickhouse.

We also use Redis for rate limiting, caching and pub/sub for realtime dashboard updates.

We use Bun's native Redis driver, again because it's so fast.

WebSockets

We use WebSockets for realtime updates. This means when a visitor lands on your site, you'll see them on the globe within a second. No refreshing, no waiting.

This also powers the live counts, but where it really shines is visitor journeys. You can open a visitor's profile and watch as they navigate your site in realtime. See what page they're on, how long they stay, when they click something.

Once again, Bun has native WebSocket support, so no extra dependencies are needed.

Frontend

TanStack Start

The frontend is built with TanStack Start. It's pretty new to the scene, but I fell in love with it practically instantly.

It gives us server-side rendering, file-based routing, and deep integration with TanStack Query. Not to mention it pairs beautifully with tRPC.

I've been using Next.js for a long time, but for an app so client-heavy, I honestly felt it was overkill. Plus, I find Start so incredibly refreshing and the developer experience is next level.

Tailwind CSS

I've been a big fan of Tailwind CSS for a long time. The utility-first approach lets me iterate quickly on designs without switching files or managing multiple style files and classes all over the place.

I would never start a new project without it.

Radix UI

I've tried building my own dropdowns and dialogs before. It's a rabbit hole. You have to manage focusing, keyboard navigation, not to mention screen readers. You think you're done and then someone opens it on Edge or Safari.

Radix UI handles all of this. It gives you unstyled primitives that work everywhere, we just add the styling on top.

Visx

The timeseries chart on the dashboard is custom built with Visx, a collection of low-level visualization primitives from Airbnb.

I wanted full control over how the charts look and feel. A lot of other charting libraries are bloated and opinionated, but Visx gives you the primitives to build exactly what you need.

It's a bit more complex to setup, but it's worth it.

Mapbox

Mapbox GL powers the realtime globe. I tried several solutions and at one point I even had my own custom Three.js globe, but once I tried Mapbox I never went back.

It handles vector tiling, zooming, and clustering. All GPU-accelerated so it doesn't chug even with a lot of visitors.

The globe was one of those features I wanted to build from the start. There's just something about watching visitors pop up from around the world that makes the data feel real.

External services

For payments, we use Stripe. I've used it for practically everything in the past, and it's the obvious choice for SaaS not to mention the developer experience is hard to beat.

Transactional and marketing emails go through Resend. I love the simplicity of it, and pairing it with React Email means we can keep emails in the same codebase as the rest of the app.

We use Sentry for client-side error tracking. It's a great tool for discovering hidden bugs and the developer experience is excellent.

The frontend is deployed to Cloudflare Workers for edge SSR. We also use Cloudflare for DNS, DDoS protection, and Turnstile. Their free tier is hard to avoid as it's so generous.

Closing thoughts

There's no perfect tech stack, and some might think there are better choices I could have made. But in the end, you have to remember that users don't care too much about the tech stack, they just want it to work and be fast.

Every choice was made with a focus on speed, simplicity, and whether it's the right tool for the job. And honestly, if I started from scratch tomorrow, I would make the same choices.

I hope you found this insightful in some way. If reading all this has you curious to try Visitors, you can start your 14 day free trial today.

If you have any feedback or suggestions, reach out.

See what's driving your revenue

Start tracking your visitors today and see which sources bring in the most revenue.