Back to Projects
Case Study

BillTick — Local-First Billing for Small Businesses

A zero-backend invoice and receipt generator with built-in time tracking — designed for freelancers and small businesses that want professional billing without surrendering their data to a third-party server. Everything runs in the browser; nothing leaves the device.

● Live at billtick.co 2025 Sole Engineer & Co-Founder Next.js · Vercel Local-First (IndexedDB)
Try BillTick
Live at billtick.co — no signup, no account, runs in your browser
Visit Live Site ↗

Billing tools come with strings attached

Most invoicing tools assume the same trade: you hand over your client list, line items, and billing history in exchange for the convenience of a web app. For freelancers and small operators, that’s a high-friction deal — accounts, subscriptions, cloud sync, and the ambient risk of vendor lock-in for something as simple as “send a PDF invoice”.

BillTick was built around a different premise: the user’s billing data should live on the user’s device. No account creation, no analytics, no server roundtrip. Invoices, receipts, and time entries are stored locally in the browser and exported as PDFs on demand.

No telemetry means catch it before ship

Testing is my primary discipline, and on a privacy-by-design product with no analytics coming back from production, that discipline has to be unusually tight: there is no signal to flag a regression after release, so every meaningful failure has to be caught before deployment. Vitest + Testing Library cover the persistence layer (Dexie round-trips, IndexedDB schema migrations) and the pure-function math behind totals, taxes, and time durations — small surfaces with high blast radius if they silently drift. React Hook Form + Zod schemas double as runtime test cases at the input boundary, rejecting invalid state before it ever reaches storage.

The highest-ROI surface is the PDF export path: every supported browser engine renders @react-pdf/renderer output slightly differently, so a fixed reference invoice gets rendered in each engine and diffed against a known-good baseline. In a team setting, that work is the right thing to delegate to a junior tester with a snapshot harness — repeatable, structured, and exactly the kind of regression hunting that benefits from a fresh pair of eyes — while I keep the data layer, the math, and the licensing flow that gates the paid tier.

Why local-first, and what it cost

The most consequential decision was eliminating the application backend entirely. A traditional SaaS architecture would have made billing data trivially syncable across devices, but it would also have meant standing up auth, storage, and the operational overhead of running a service for users who explicitly didn’t want a service running on their behalf. IndexedDB — accessed through Dexie — was the right primitive: durable, asynchronous, and large enough to hold years of invoice history without pressure.

PDF generation runs entirely in the browser via @react-pdf/renderer, which keeps the local-first contract intact — no document ever touches a remote server to be rendered. Browser rendering engines disagree on margins, font fallbacks, and page-break behaviour, so the export path had to be explicitly verified rather than assumed; each engine was checked against a known-good invoice layout before the feature was considered shipped.

State management is deliberately small: Zustand for in-memory app state, Dexie for persistence, Zod plus React Hook Form for input validation. No global context, no Redux ceremony, no server-state library — because there is no server state to manage.

What the platform does

🧾

Invoice & Receipt Builder

Compose invoices and receipts with line items, taxes, and configurable payment terms.

⏱️

Built-in Time Tracking

Stopwatch sessions or manual time entries — billable hours feed straight into invoices.

📄

Cross-Browser PDF Export

Generate clean, print-ready PDFs directly in the browser — validated across major engines.

🔒

Local-First Persistence

All data lives in IndexedDB on the user’s device — no servers, no accounts, no analytics.

📁

Client & Project Management

Organise recurring clients and projects with reusable profiles for faster billing cycles.

💾

Backup & Restore

Manual JSON export and import — portable backups the user owns and controls outright.

See it in action

Video coming soon
Product walkthrough — 60–90 second demo

Product views

📸 Invoice Builder
📸 Time Tracker
📸 Client & Project Manager
📸 PDF Export Preview

How it’s built

A modern client-side stack with no application server — only static hosting on Vercel’s edge, and a thin API surface for license activation and Stripe webhooks.

Next.js 16 React 19 TypeScript Tailwind CSS 4 Dexie (IndexedDB) @react-pdf/renderer React Hook Form Zod Zustand date-fns nanoid Vitest Testing Library Vercel

What I worked around

No QA handoff, no backend to lean on, and no analytics to observe usage after release — the privacy posture had to hold all the way through. That meant every bug had to be caught before deployment, since there was no telemetry coming back to flag a regression. The offline-first contract also forced careful state handling: persistence, conflict resolution, and PDF generation all had to work without a network round trip available as a fallback.

How I built it

BillTick is a solo build — I scoped the product, designed the architecture, wrote the front-end, validated the PDF export path across browsers, and shipped to Vercel without a QA handoff. As co-founder I also own the product framing: who it’s for, what it deliberately doesn’t do, and how the local-first contract is communicated to users.

Sole engineer & co-founder. Scoped, designed, coded, tested, and shipped — zero-backend architecture, cross-browser PDF accuracy validated end-to-end, deployed live to Vercel with no QA team in the loop.

What I’d do differently

I’d invest earlier in an automated visual-regression layer for the PDF output. Browser-engine differences in PDF rendering are easy to miss by eye and stable enough between releases that a scripted snapshot diff would catch drift far more reliably than manual cross-browser passes.

See BillTick in production
Open it in your browser — your data stays on your device
Visit Live Site ↗