I work in operations at a factory in Bangladesh. In my spare time I built LifeEasyTools, a site with 515 free calculators and everyday tools in 8 languages. It has no framework and no database for the tools: one Node.js script turns plain JavaScript objects into 2,359 static pages.

Here is how it works, what went wrong, and what I would do again.

Why static pages

Most of my users are on low-cost Android phones and slow mobile data. A calculator should show up instantly and never wait for a server to do maths that the phone can do itself.

So every page is plain HTML. Calculations run in the browser, and the PDF and image tools process files locally, so nothing is uploaded. A typical tool page is about 50 KB of HTML (13 KB gzipped), plus one shared stylesheet and one shared script.

One file per tool

Each tool is a JavaScript object:

module.exports = { slug: 'concrete-calculator', cat: 'everyday', title: 'Concrete Calculator: Volume, Bags, Cement, Sand & Stone', form: `${seg('units', 'Units', [['m', 'Metric (m)'], ['ft', 'Feet']], 'm')} ${row(num('L', 'Length', 5), num('W', 'Width', 4), num('T', 'Thickness', 0.125))}`, js: `DN.init(function (v) { var vol = v.L * v.W * v.T; return DN.result({ answer: { label: 'Concrete needed', value: DN.fmt(vol, 3) + ' m³' } }); });`, content: `<h2>How to calculate concrete volume</h2> ...`, faq: [['How many bags of cement are in 1 cubic metre?', '...']] }; 

seg, num and row are tiny helpers that return form HTML. DN is a small shared runtime (about 25 KB gzipped) that reads the form, formats numbers for the visitor's locale, and draws the result card, tables and charts.

The build script loops over every tool and writes the page with its title, description, breadcrumbs, JSON-LD (WebApplication, FAQPage, BreadcrumbList), hreflang links and a sitemap entry. A full build of all 2,359 pages takes about 35 seconds.

Translations without a framework

Each language has a translation file per tool: title, description, intro, content and a ui map of the English strings used in the form and the script.

The build translates the form HTML text nodes and every single-quoted string in the tool's script through that map. When a sentence is built from pieces ('in ' + days + ' days'), a small jsFix list replaces that expression with a properly ordered one, because word order differs between languages.

The catch: it is easy to leave English behind. So a test runs every translated tool in headless Chromium, clicks Calculate, and lists any English phrase left in the visible result. That test found things I would never have spotted by hand, such as holiday names in English on Bangla pages, or a chart legend that still said "Part / Rest".

Testing the maths

For a calculator site, a wrong answer is the worst bug. I keep a "golden" test file: 36 real cases, each with a reference value computed independently from the published formula, not copied from the tool's code.

const pmt = (P, r, n) => P * r * Math.pow(1 + r, n) / (Math.pow(1 + r, n) - 1); const CASES = [ ['emi-calculator', { p: 500000, r: 9.5, t: 5, tu: 'y' }, [pmt(500000, 0.095 / 12, 60)], 'EMI 5 lakh 9.5% 5y'], ['bmi-calculator', { units: 'metric', kg: 70, cm: 175 }, [22.9], 'BMI'], // ... net salary in France, Spain, Italy, Netherlands and Poland, due dates, unit converters ]; 

Playwright fills each form, runs it, and checks the result contains the expected number. When a test failed the first time, it was usually my test input that was wrong (a mortgage down payment is a percentage, not an amount), which is a good reason to write the reference values independently.

Layout shift on file tools

Chrome's layout-shift measurement showed 0.077 on the PDF tools on phones. The file drop zone gets extra buttons (link, camera, paste, Drive, Dropbox) from JavaScript after the first paint, and on narrow screens they wrapped onto two lines and pushed everything down.

The fix was two parts: show the extra sources as one row of fixed-height tiles on phones, and reserve their height in CSS until the script marks the zone as ready:

@media (max-width: 600px) { .fstart .drop:not(.dready) .dbtn { margin-bottom: 87px; } } 

Layout shift went to 0 on every page I measured.

SEO lessons from a site with 2,000+ pages

  • Link siblings around each page, not the first few. Pages in long families (land units, US state paycheck calculators, currency pairs) linked to the first ten items of the list, so items later in the list got almost no internal links. Linking each page to its neighbours on both sides fixed 161 weakly linked pages.
  • Keep titles under about 65 characters. Dropping the brand suffix on long titles let Google show the words that matter.
  • One canonical per page and full hreflang sets for 8 languages, generated from the same data so they never drift apart.
  • Answer first. Converter pages now start with the factor ("1 foot = 0.3048 metres") before anything else.

Numbers

  • 515 tools, 2,359 pages, 8 languages (English, Bangla, Hindi, Spanish, Arabic, Portuguese, Indonesian, French)
  • Build: about 35 seconds for all pages
  • Hosting: one small VPS behind Cloudflare; deploys upload only changed files and notify IndexNow

What I would do again

Plain objects plus a build script were enough for 500 tools. The two things that paid off most were the golden tests and the "find leftover English" test. Both caught real problems before users did.

If you want to try a tool or tell me what is missing, the site is lifeeasytools.com. I am happy to answer questions about the build in the comments.