/* Route-Critical (index, V6 §3.6.3 — erzeugt von tools/generate_critical_css.mjs) */ .collapse:not(.show){display:none}.navbar{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between}.navbar>.container-fluid{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.mydraft-svg-icon{display:inline-block;vertical-align:middle;color:currentcolor;flex-shrink:0;transition:color var(--duration-fast, 150ms) ease,stroke var(--duration-fast, 150ms) ease,fill var(--duration-fast, 150ms) ease}.mydraft-svg-icon circle,.mydraft-svg-icon line,.mydraft-svg-icon path,.mydraft-svg-icon polygon,.mydraft-svg-icon polyline,.mydraft-svg-icon rect{transition:stroke var(--duration-fast, 150ms) ease,fill var(--duration-fast, 150ms) ease}@layer components{.live-pulse-dot{width:8px;height:8px}}.min-w-0{min-width:0}@layer overrides{.live-radar-header-badge{background:rgba(239,68,68,.12);border:1px solid rgba(239,68,68,.35);color:#fca5a5;font-size:.75rem;font-weight:700;transition:.25s;cursor:pointer}}@layer overrides{.live-pulse-dot{width:8px;height:8px;background-color:var(--threat-critical,#ef4444);border-radius:50%;display:inline-block;box-shadow:rgba(239,68,68,.7) 0 0 0 0}}@layer overrides{body.light .live-radar-header-badge,html[data-bs-theme=light] .live-radar-header-badge{background:#fbd9dd;border-color:#eeb2b8;color:#8a1723!important}}.isharestuff-live-ticker{color:var(--color-text-body,#f8fafc);backdrop-filter:none;flex-wrap:wrap;row-gap:.3rem;background:var(--color-surface,rgba(15,23,42,.85))!important;border:1px solid var(--color-border,rgba(255,255,255,.12))!important;padding:.45rem .5rem!important;margin-bottom:.75rem!important}.ticker-content{mask-image:linear-gradient(to right,transparent 0px,rgb(0,0,0) 14px,rgb(0,0,0) calc(100% - 14px),transparent 100%);touch-action:pan-y;cursor:grab;user-select:none;-webkit-tap-highlight-color:transparent;order:1;width:100%;max-width:100%;flex:0 0 100%!important}.isharestuff-live-ticker .ticker-lead-group{order:2;width:100%;justify-content:space-between;margin-right:0!important}.ticker-track{display:flex;align-items:center;white-space:nowrap;will-change:transform;transform:translate3d(0,0,0);--ticker-speed:1;animation-name:isharestuffTicker;animation-timing-function:linear;animation-iteration-count:infinite;animation-duration:calc(26s / var(--ticker-speed, 1))}.ticker-link{max-width:min(78vw,340px);font-size:.82rem;display:inline-block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:baseline;transition:color .2s;color:var(--color-text-heading,#f8fafc)!important}.isharestuff-live-ticker .ticker-ctrl-btn{min-width:44px;height:44px;font-size:.78rem;-webkit-tap-highlight-color:transparent}.ticker-controls-bar{gap:.4rem}.ticker-counter{font-family:var(--bs-font-monospace, ui-monospace, monospace);font-size:.72rem;line-height:1;color:var(--color-text-secondary,#94a3b8);min-width:3.4em;text-align:center;user-select:none;white-space:nowrap;margin-left:auto}.ticker-item{font-size:.82rem}.ticker-item .ticker-date{font-size:.72rem;vertical-align:baseline}.ticker-text-group{display:inline-flex;align-items:baseline}.ticker-badge-live-btn{border:0;cursor:pointer;text-decoration:none!important}.ticker-badge{font-size:.78rem!important;min-height:44px!important;min-width:44px!important;padding:6px 12px!important;display:inline-flex!important;align-items:center!important;justify-content:center!important}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-toggler{display:none}}@media (prefers-reduced-motion:reduce){@layer overrides{#news-popup,#news-toast-banner,.animate-pulse,.live-pulse-dot,.live-pulsing-dot,.news-item-card{animation:none;transition:none}}.ticker-track{animation:none!important}.ticker-content{cursor:default}}@media (min-width:768px){.isharestuff-live-ticker{flex-wrap:nowrap;row-gap:0;backdrop-filter:blur(12px);padding:.5rem!important;margin-bottom:1rem!important}.isharestuff-live-ticker .ticker-content{order:0;width:auto;max-width:none;flex:1 1 auto!important}.isharestuff-live-ticker .ticker-lead-group{order:0;width:auto;justify-content:flex-start;margin-right:1rem!important}.ticker-controls-bar{gap:.3rem}.isharestuff-live-ticker .ticker-ctrl-btn{min-width:30px;height:auto;font-size:inherit}.ticker-counter{font-size:.66rem;margin-left:0}.ticker-track{animation-duration:calc(40s / var(--ticker-speed, 1))}.ticker-link{max-width:340px;font-size:.85rem}.ticker-item{font-size:.85rem}.ticker-badge{font-size:inherit!important;min-height:0!important;min-width:0!important;padding:.5rem 1rem!important}}@media (max-width:359.98px){.ticker-counter{display:none}.ticker-controls-bar{gap:.3rem}.ticker-badge{padding:6px 9px!important}} 🔒 I built a freelance rate calculator with Next.js | Sichere Programmierung – TSECURITY.DE Zum Hauptinhalt springen
Malware / Trojaner / VirenRansomware pressure now reaches employees’ homes and families(19.10.2026 um 06:00 Uhr)
••••••
IT NachrichtenWer in Rente ist, muss diese Handyspiele installieren(19.10.2026 um 06:20 Uhr)
••••
Malware / Trojaner / VirenRansomware pressure now reaches employees’ homes and families(19.10.2026 um 06:00 Uhr)
••••••
IT NachrichtenWer in Rente ist, muss diese Handyspiele installieren(19.10.2026 um 06:20 Uhr)
••••
Intelligence View
⚡ tsecurity.de Intelligence

I built a freelance rate calculator with Next.js

Here's the maths most calculators get wrong Most freelance rate calculators are wrong. Not buggy — conceptually wrong. They take your income goal, divide by h…

Beitrag
0
Seite
0
↗ Quelle (dev.to)
Social ReaktionenReagiere als Erste:r — dein Feedback zählt!

Here's the maths most calculators get wrong



Most freelance rate calculators are wrong. Not buggy — conceptually wrong. They take your income goal, divide by hours, and hand you a number that will quietly lose you money all year.



I got tired of explaining the correct calculation to freelancer friends, so I built a tool that does it properly. Here's the logic behind it, and a bit about how I built it.






The flawed formula



The typical calculator does this:




hourly_rate = annual_income_goal / annual_hours_worked






So if you want $80,000 and work 2,080 hours a year (40 × 52), it tells you to charge ~$38/hr.



This is wrong in three separate ways.






Fix 1: Tax is not optional



Your income goal is a net number — what you want to keep. But you're taxed on gross. So the first correction is grossing up:




const grossIncome = netTarget / (1 - taxRate);
// $80,000 / (1 - 0.28) = $111,111






That's already a $31,000 gap the naive formula ignores.






Fix 2: You don't bill every hour you work



This is the big one. Freelancers bill roughly 55–65% of their working hours. The rest is admin, proposals, invoicing, sales calls, and learning.




const totalHours = weeksWorked * hoursPerWeek;        // 46 * 40 = 1840
const billableHours = totalHours * billableRatio; // 1840 * 0.6 = 1104






So your real billable capacity isn't 2,080 hours. It's closer to 1,104. Pricing on the wrong denominator is how people end up working 50-hour weeks and still missing their income target.






Fix 3: Business expenses come out of gross



Software, hardware, insurance, courses — all real costs that need covering before you pay yourself:




const totalNeeded = grossIncome + annualExpenses;









The corrected formula



Putting it together:




function minimumRate({ netTarget, taxRate, expenses, weeks, hoursPerWeek, billableRatio }) {
const gross = netTarget / (1 - taxRate);
const totalNeeded = gross + expenses;
const billableHours = weeks * hoursPerWeek * billableRatio;
return totalNeeded / billableHours;
}

minimumRate({
netTarget: 80000,
taxRate: 0.28,
expenses: 8000,
weeks: 46,
hoursPerWeek: 40,
billableRatio: 0.6
});
// ≈ $107/hr — not $38/hr






That's a 2.8x difference from the naive calculation. On a full year, the gap between charging $38 and $107 is enormous.






How I built it



The tool is a Next.js 14 app (App Router) deployed on Vercel. The calculator itself is a single client component using useState for the inputs and useMemo for the derived rate — no backend needed, all the maths runs in the browser.



The interesting architecture decision was programmatic SEO. Rather than one page, I generate static pages for every profession × country combination using generateStaticParams:




export async function generateStaticParams() {
return COUNTRIES.flatMap(country =>
PROFESSIONS.map(profession => ({ country, profession }))
);
}






That turns 8 professions and 6 countries into 48 statically-rendered pages, each targeting a specific long-tail search like "freelance developer rate in Australia" — all built at deploy time, all served from the edge.



Market benchmark data lives in plain JSON objects keyed by profession, with a currency multiplier applied per country. No database, no CMS — just static data and static rendering, which keeps it fast and free to host.






Try it



The live tool is at freelancecalculator.net — free, no signup. Set your currency, enter your numbers, and it shows your minimum rate, a recommended rate with a 20% buffer, and how you compare to market benchmarks.



If you're a freelance dev and you've never run your rate through the corrected formula, it's worth 60 seconds. Your number might be higher than you think.

Intelligence Digest — kostenlos Täglich die wichtigsten Security-News · jederzeit abbestellbar
Community Rating & Social Proof
⭐ Leser-Wertung
–
Ø / 5.0
Noch keine Leser-Bewertungen
War diese Seite hilfreich?
0
Jetzt Bewertung abgeben
🟢 Niedrig (1-3.9) 🟡 Mittel (4.0-6.9) 🔴 Hoch (7.0-8.9) Kritisch (9.0-10.0)

Verwandte Story-Cluster & Quellen (Vektor-KI)

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten I built a freelance rate calculator with Next.js

Thematisch verwandte Begriffe: built, freelance, rate, calculator · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

💬 Kommentare werden geladen…
Zum Aktualisieren ziehen
Nächster Beitrag