<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: MIKE</title>
    <description>The latest articles on DEV Community by MIKE (@mike_a6d33cdcb8d4e7c70206).</description>
    <link>https://dev.to/mike_a6d33cdcb8d4e7c70206</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3973125%2Ff62eb7b2-1e02-4872-888a-caa220590723.png</url>
      <title>DEV Community: MIKE</title>
      <link>https://dev.to/mike_a6d33cdcb8d4e7c70206</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mike_a6d33cdcb8d4e7c70206"/>
    <language>en</language>
    <item>
      <title>I built a freelance rate calculator with Next.js</title>
      <dc:creator>MIKE</dc:creator>
      <pubDate>Mon, 08 Jun 2026 00:09:28 +0000</pubDate>
      <link>https://dev.to/mike_a6d33cdcb8d4e7c70206/i-built-a-freelance-rate-calculator-with-nextjs-2pi9</link>
      <guid>https://dev.to/mike_a6d33cdcb8d4e7c70206/i-built-a-freelance-rate-calculator-with-nextjs-2pi9</guid>
      <description>&lt;p&gt;Here's the maths most calculators get wrong&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The flawed formula
&lt;/h2&gt;

&lt;p&gt;The typical calculator does this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;hourly_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;annual_income_goal&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;annual_hours_worked&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;This is wrong in three separate ways.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 1: Tax is not optional
&lt;/h2&gt;

&lt;p&gt;Your income goal is a &lt;em&gt;net&lt;/em&gt; number — what you want to keep. But you're taxed on gross. So the first correction is grossing up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;grossIncome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;netTarget&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// $80,000 / (1 - 0.28) = $111,111&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's already a $31,000 gap the naive formula ignores.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 2: You don't bill every hour you work
&lt;/h2&gt;

&lt;p&gt;This is the big one. Freelancers bill roughly 55–65% of their working hours. The rest is admin, proposals, invoicing, sales calls, and learning.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;totalHours&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;weeksWorked&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;hoursPerWeek&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// 46 * 40 = 1840&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;billableHours&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;totalHours&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;billableRatio&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// 1840 * 0.6 = 1104&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 3: Business expenses come out of gross
&lt;/h2&gt;

&lt;p&gt;Software, hardware, insurance, courses — all real costs that need covering before you pay yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;totalNeeded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;grossIncome&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;annualExpenses&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The corrected formula
&lt;/h2&gt;

&lt;p&gt;Putting it together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;minimumRate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;netTarget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expenses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;weeks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hoursPerWeek&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;billableRatio&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gross&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;netTarget&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;totalNeeded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gross&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;expenses&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;billableHours&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;weeks&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;hoursPerWeek&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;billableRatio&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;totalNeeded&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;billableHours&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;minimumRate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;netTarget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;expenses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;weeks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;46&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;hoursPerWeek&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;billableRatio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// ≈ $107/hr — not $38/hr&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  How I built it
&lt;/h2&gt;

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

&lt;p&gt;The interesting architecture decision was programmatic SEO. Rather than one page, I generate static pages for every profession × country combination using &lt;code&gt;generateStaticParams&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateStaticParams&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;COUNTRIES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;PROFESSIONS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profession&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;profession&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The live tool is at &lt;a href="https://freelancecalculator.net" rel="noopener noreferrer"&gt;freelancecalculator.net&lt;/a&gt; — 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>freelance</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
