Building lzear.org
The stack
I rebuilt this site from scratch with a few goals: modern React full-stack without Next.js, real-time data without WebSockets, and everything deployable to a cheap VPS.
Here's what I landed on.
Remix + Bun
Remix (technically React Router v7 now) handles routing and SSR. Bun runs the server — no Node.js.
The entry point is four lines:
const handler = createRequestHandler(
() => import('./build/server/index.js'),
process.env.NODE_ENV,
)
Bun.serve({ port: 3000, fetch: handler })Electric SQL for real-time
The voting app on this site shows live results as votes come in. I'm using Electric SQL for this — it syncs Postgres state to the browser reactively.
const { data: votes } = useShape<VoteRow>({
url: `${electricUrl}/v1/shape`,
params: { table: 'votes', where: `poll_id = '${pollId}'` },
})When anyone submits a vote, every open browser tab updates without any WebSocket management code on my end.
Drizzle ORM
Type-safe DB queries that look like SQL:
const polls = await db
.select()
.from(polls)
.orderBy(desc(polls.createdAt))Schema lives in code, migrations are generated by drizzle-kit.
Better Auth
Username/password + OAuth (GitHub, Google) + anonymous guest sessions. More complex than I'd like but flexible.
Infrastructure
- Hetzner CX32 — €8/month, 4 vCPU, 8 GB RAM
- Traefik — reverse proxy, handles TLS via Cloudflare DNS-01
- GitHub Actions — builds Docker images, SSH deploys on push to main
- Branch previews — each PR gets a live URL at
<branch>.lzear.org
The whole infrastructure is in infra/ in the repo. No Terraform, no Kubernetes — just shell scripts and compose files.
What I'd change
The auth setup is more complex than it needs to be. Better Auth's username plugin piggybacks on email/password infrastructure, which means storing placeholder emails. Fine for now.
Electric SQL adds a sidecar container which means each environment (prod, each preview) runs three containers. Worth it for the DX.