惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

MyScale Blog
MyScale Blog
博客园 - 司徒正美
A
About on SuperTechFans
Vercel News
Vercel News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
爱范儿
爱范儿
I
InfoQ
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园_首页
Google DeepMind News
Google DeepMind News
T
Tailwind CSS Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
F
Fortinet All Blogs
S
SegmentFault 最新的问题
阮一峰的网络日志
阮一峰的网络日志
D
Docker
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
G
Google Developers Blog
Stack Overflow Blog
Stack Overflow Blog
M
MIT News - Artificial intelligence
Jina AI
Jina AI
H
Help Net Security
量子位
IT之家
IT之家

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Building 11 Free Finance Calculators in React: What I Lea...
Solomon Weal · 2026-05-19 · via DEV Community

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)} />

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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;
}

Enter fullscreen mode Exit fullscreen mode

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-async for 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 });
};

Enter fullscreen mode Exit fullscreen mode

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.