g3ddy
Product Strategy6 min · 31 Aug 2026

React Invoice v3 upgrade

A free, self-hosted invoicing app rebuilt in hours on Backflip — and proof that a solid foundation turns AI coding tools from guesswork into genuine speed.

Geddy
Geddy
Senior Web Engineer / Lead
0:00
A clean modern sales banner centered hero object is an invoice with red accent color (pdf icon like accent color), crisp premium commercial product representation, few selling key points stating ownership of your invoicing without third parties, built on Backflip platform for building web apps
TL;DR30 sec
  • A localStorage-only side project became a full Postgres app with auth, roles, migrations, email, tests and deploys in a few hours — because the foundation was already there
  • AI coding tools are fast at features and terrible at inventing architecture; give them settled conventions and documented contracts and there's only one obvious way to build the thing
  • The real product isn't the invoicing app, it's the loop: describe a feature → let Claude Code build to spec → review the PR → deploy
  • Invoices snapshot their series prefix, currency and branding at issue time, so editing a series never rewrites a document a client already received
  • The live preview is the printed document — no template quietly drifting out of sync with what you see on screen
  • Fork it, delete the invoicing routes, keep auth/admin/migrations/tests, and start your actual project at the point most projects stall The bottleneck was never ideas — it was the cost of starting properly, and that cost is now gone.

This article contains affiliate links. If you sign up through one I may earn a commission, at no extra cost to you. It never changes what I recommend.

I built a React invoicing app in 2023. It ran locally, stored everything in browser localStorage, and that was the whole architecture. It was a spare-time project — I didn't want to spend weekends wiring up a database, sessions, roles and a deploy pipeline just to print a PDF.

I did it now (or did Claude Code do it?). And the app stayed exactly as capable as a browser tab is: one machine, one user, one clear-your-cache away from losing your ledger.

That trade-off doesn't exist anymore.

I rewrote react-invoice to v3 in a few hours on Backflip — the platform foundation I've been building for exactly this problem. Same job, real stack. Postgres, accounts, roles, an admin shell, migrations, transactional email, tests, deploys. All of it there on day one, before I wrote a single line of invoicing logic.

It's now Backflip Invoice v3.0.0, and it's free and MIT licensed.

What it actually is

Two things at once.

One: a working, self-hosted invoicing app you can run today. Invoicing behind a login, a shared ledger every signed-in user can read, each invoice recording who raised it, and a live preview that is the printed document.

Two: a worked example. Proof of how fast a new platform, internal business tool or website gets built on Backflip without trading away quality. Because auth, roles, migrations, email, integrations, tests and deploys are already solved, the feature work is the only work left.

If you've ever quoted three weeks for "a small internal tool" and known that two and a half of them were plumbing — that's the gap this closes.

The invoicing part

The stack: Next.js 16, React 19, Tailwind v4, shadcn/ui, Drizzle + Postgres, Auth.js, Turborepo, yarn 4.

Shared ledger. Per-series totals and a cumulative-sales chart for the tax year, then every invoice below it. Each one opens on its own page. The creator — or an owner/admin — can edit, lock or delete it.

Editor plus live preview. Provider and customer sit as cards that open a four-column detail dialog. Line items where qty, rate and total recompute each other. Hyphenated series numbering (RIE-0010). VAT charged only when the provider is actually registered for it.

Print and PDF. The preview is the document — no separate template drifting out of sync with what you see. Print straight from the browser, or download a server-rendered A4 PDF. Either way the file names itself properly:

2026_08_21 - RIE-0010 - EUR1031.25 VAT incl. - UAB Baltic Grid.pdf

Customers. An address book that prefills the invoice form. Invoice a company you never saved? It gets offered for one-click adoption.

Series and currency. Each series owns its numbering prefix, default currency and branding, with a platform brand as fallback. Invoices snapshot all three at issue time — so editing a series never silently rewrites a document you already sent to a client. That detail matters more than it sounds.

The old v2 CRA/MUI/localStorage app is still in .legacy-ref-project/ for reference. Not built, not deployed. Just a reminder of where it started.

The part I actually care about

Here's the loop, and it's the reason this repo exists.

Set up once: clone, provision a droplet, first deploy.

Then every feature runs the same way: describe it → let Claude Code build to spec → review the PR → deploy.

That's it. That's the whole rhythm.

The reason it works isn't the AI. AI coding tools are great at writing features and terrible at inventing architecture on the fly — ask one to "add users" to a greenfield repo and you'll get a different auth pattern every time you ask. Give it a foundation with settled conventions, real schema, existing tests and a documented contract, and it becomes genuinely fast, because there's only one obvious way to do the thing.

Backflip is that foundation. The contracts live in docs/contracts/, the workflow conventions in .[claude](https://claude.ai/referral/jQJrv36Dpw)/skills/dev-workflow. The agent reads the same rules you do.

So the work stops being "build a system" and becomes "describe a feature, review a diff." That's a much better job.

Running it locally

You need Docker Desktop running (for Postgres) and Node ≥ 20 with corepack, which pins yarn 4.17.1 — always run corepack yarn ….

First time, create your env files:

cp .env.example .env
cp .env.init.example .env.init   # one-off owner seed

Add a .env.local with your Auth.js secrets:

AUTH_SECRET=replace-with-openssl-rand-base64-33
AUTH_TRUST_HOST=true

Generate a real one with openssl rand -base64 33. Put your ADMIN_EMAIL / ADMIN_PASSWORD in .env.init — it's read only by init-owner, never by the app.

Then:

corepack yarn install
docker compose up -d       # Postgres only
corepack yarn db:migrate
corepack yarn init-owner
corepack yarn dev          # → http://localhost:3080

Sign in at /backflip/login. Google login is optional — add AUTH_GOOGLE_ID / AUTH_GOOGLE_SECRET with redirect URI http://localhost:3080/api/auth/callback/google. It only works for already-registered emails, which is the behaviour you want for a business tool.

Changed ADMIN_*? Rerun init-owner. Changed AUTH_*? Restart the dev server — env loads at startup.

Tests and deploys

corepack yarn workspace web test        # Vitest — fast, no DB
corepack yarn workspace web test:e2e    # Playwright — needs docker compose up

E2E boots its own app on port 3180 against a dedicated react_invoice_test database, so your dev data is never touched. First run on a machine needs the browser once: playwright install chromium.

Deployment goes to a DigitalOcean droplet — from your machine, GitHub Actions or Drone CI. See devops.md.

Self-hosted means your secrets

This is your deployment. The values in .env.example are local-dev defaults and nothing else. Before you put this anywhere public:

  • AUTH_SECRETopenssl rand -base64 33
  • ENCRYPTION_KEYopenssl rand -base64 32 (encrypts stored secrets like AI provider keys)
  • A strong ADMIN_PASSWORD and unique database credentials

In production, seed the owner once, then delete the file:

cp .env.init.example .env.init   # real email, strong password
corepack yarn init-owner         # idempotent
rm .env.init                     # the password lives only here

The seed script isn't imported by the app, isn't in the build, and throws without .env.init — so it's harmless to leave in place. Recreate the file whenever you need to seed another owner.

Take it and go

Use it as an invoicing app. Genuinely — it's free, it prints properly, and your ledger lives in a real database.

But the more interesting move is to fork it, delete the invoicing routes, and keep the foundation. You get auth, roles, admin shell, migrations, a shared UI system built from shadcn blocks, tests and a deploy path. Then point Claude Code at whatever you actually wanted to build.

The admin dashboard here uses login-03, dashboard-01 and sidebar-08 — lift whatever else you need.

MIT © dev-geddy. Take it.

The thing I keep coming back to: the reason my old side project stalled wasn't lack of ideas, it was the cost of starting properly. That cost is basically gone now. So what have you been not building?

Geddy
Geddy
Senior Web Engineer / Lead

Engineering leadership • AI innovation • Product thinking. 20+ years of web engineering, from independent contractor to engineering leader. Passionate about developer experience and product engineering.

Follow on LinkedIn
Keep reading