I shipped a site called Solomon Wealth Code with 11 free finance calculators (tithe, debt snowball, compound interest, mortgage payoff, net worth, emergency fund, generosity, retirement longevity, budget). Stack: React 18 + Vite + Tailwind + TypeScript, no backend, no database, no auth.
The calculators look simple. The lessons were not. Here is what I would tell my past self.
1. Controlled numeric inputs lie to you
Naive version:
const [income, setIncome] = useState(0);
<input type="number" value={income} onChange={(e) => setIncome(+e.target.value)} />
This breaks the moment a user types 1,200 or $1200 or 1200.50 with a comma decimal (Europe). And type="number" blocks the comma on some browsers but allows it on others. Worse, on iOS Safari type="number" shows the wrong keyboard for amounts that need decimals.
What works: store the input as a string, parse on calculation.
const [incomeStr, setIncomeStr] = useState("");
const income = parseFloat(incomeStr.replace(/[^0-9.]/g, "")) || 0;
For mobile, use inputMode="decimal" instead of type="number".
2. Debt snowball is not a one-liner
The snowball method looks trivial. In code, it is a month-by-month simulation. Interest first, then minimums, then snowball extra rolls onto whichever debt is smallest after interest.
function simulateSnowball(debts: Debt[], extraMonthly: number) {
const months: MonthSnapshot[] = [];
let remaining = debts.map(d => ({ ...d }));
let month = 0;
while (remaining.some(d => d.balance > 0) && month < 600) {
month++;
remaining.forEach(d => {
d.balance += d.balance * (d.apr / 100 / 12);
});
remaining.sort((a, b) => a.balance - b.balance);
remaining.forEach(d => {
const pay = Math.min(d.balance, d.minimum);
d.balance -= pay;
});
const target = remaining.find(d => d.balance > 0);
if (target) {
target.balance -= Math.min(target.balance, extraMonthly);
}
months.push({ month, snapshot: remaining.map(d => ({ ...d })) });
}
return months;
}
Cap the loop at 600 months. There is always a user who enters $50,000 of debt and $5/month extra.
3. SEO on a SPA is a real problem
Vite + React Router = client-side rendering. Google can crawl it, but social previews (LinkedIn, Slack, Facebook, WhatsApp) cannot. They do not run JavaScript when fetching the preview card.
Two things helped:
- Pre-rendering with Puppeteer (crawl every route, write HTML snapshots into
dist/) -
react-helmet-asyncfor per-route titles, meta descriptions, canonical URLs, and JSON-LD
The JSON-LD matters more than people think. Adding Calculator, FAQPage, Article, and BreadcrumbList schema to each page got 4 calculators into Google's "People also ask" boxes within 6 weeks.
4. Calculators are content, not just tools
The instinct is to ship a clean input + output and stop. Those rank on page 5. What ranks: long-form context. 1,200 words below the fold explaining the framework, FAQ schema, footnotes. Google treats it as authoritative content with a calculator embedded, not as a calculator with thin SEO.
5. No backend = fewer bugs, faster shipping
I considered Supabase for saving results. Skipped it. Every calculation lives in useState. The whole site is a static bundle on a CDN. 0ms cold starts, $0/month hosting, no security surface.
6. Treat URLs like API endpoints
Without a database, state travels through the URL. Calculator inputs serialize to query params. Users can bookmark "my budget" or come back next month with the same numbers prefilled.
const [params, setParams] = useSearchParams();
const income = parseFloat(params.get("income") || "0");
const update = (k: string, v: string) => {
params.set(k, v);
setParams(params, { replace: true });
};
Use replace: true so the browser history does not fill up.
7. Accessibility was easier than expected
<label> + <input> + <output> plus aria-live="polite" on the result region and aria-describedby linking each input to helper text. Lighthouse 88 → 100 in an hour.
8. Things I would skip if I started over
- A monorepo. Single Vite app was fine for 11 calculators.
- A component library. Tailwind + small custom components was faster.
- Next.js. Nice tech, overkill for static calculators.
Live site
Full site: https://www.solomonwealthcode.com
Most complex calculator: https://www.solomonwealthcode.com/debt-snowball-calculator
Most popular: https://www.solomonwealthcode.com/compound-interest-calculator
Happy to answer questions about the stack in the comments.
Top comments (0)