Technology Choices
Why We Don’t Use Node.js, React, or Heavy Frameworks
This document explains the reasoning behind our technology choices. It is not a manifesto or a critique of developers who use these tools—in many contexts, they are the right choice. But for our projects, they are consistently the wrong one, and this document explains why.
Note: This is not dogma. We use the right tool for the job. We run a proper database on our auth server, for example, because user authentication genuinely requires it. The decisions below are pragmatic, not ideological.
1. Our Users Pay for Every Megabyte
Our primary audience lives in developing countries—rural Africa, Southeast Asia, and parts of South America—where mobile data costs $1–3 per gigabyte and connections are slow and unreliable.
A standard React application ships 100–300KB of JavaScript before it renders a single pixel of useful content. For our users, that is not an inconvenience; it is a real financial cost and often means the site simply doesn’t load.
Our rule of thumb: If the total page weight (HTML + CSS + JS) exceeds 200 KB gzipped, we have already failed our users.
2. Old Devices Must Work
Budget Android phones with 512MB RAM, outdated browser engines, and slow CPUs are common among our users. Modern JavaScript frameworks assume a capable JS runtime. Our sites must not.
We build with progressive enhancement: the page works in plain HTML, CSS adds visual improvement, and JavaScript adds convenience—never core functionality. This is structurally incompatible with JS-first frameworks like React, which render nothing until the JS bundle executes.
3. Dependency Hell Is Real and Documented
We have experienced this directly. Our chat bots were originally written in Node.js and were migrated to Python specifically to eliminate dependency issues. That is not a theoretical concern — it happened on this project.
The npm ecosystem compounds this problem at scale. A “fairly simple” project can easily pull in 1,200+ packages, totaling half a gigabyte. Each of those packages is:
- A potential source of breakage when versions conflict
- A maintenance burden when security patches are needed
- A thing that can simply stop working without warning
Plain HTML, CSS, and PHP have no dependency tree. There is nothing to break.
4. Supply Chain Attacks Are a Real Threat
Every npm package you install is code written by a stranger, running with full access to your build environment — and sometimes your users’ browsers. This is not hypothetical. A well-documented example of how this attack surface works in practice: https://www.youtube.com/watch?v=r5U4S0uUIVk
Projects with hundreds of transitive dependencies have a correspondingly large attack surface. Zero dependencies means zero third-party attack surface.
5. Static Files Are Faster and Cheaper to Serve
We pre-generate HTML at build time rather than assembling it on every page request. The difference matters:
Our approach (build-time): Server receives request → sends complete HTML file → done. Zero processing, zero database queries, instant delivery.
Framework approach (runtime): Server receives request → executes framework → queries database → assembles template → sends response. Every visitor pays the processing cost individually.
For a user in rural Kenya on a 3G connection, this difference is measurable in seconds.
6. Node.js Requires Node.js
This sounds obvious, but it has real implications. Our sites run on cheap shared hosting — the kind of hosting our own users at satoshihost.com would actually use. Shared hosts almost universally support PHP. Very few support Node.js, and those that do charge more for it.
By using PHP and static files, our build process is portable to any server anywhere.
7. Long-Term Durability
A plain HTML file written in 2010 still works in every browser today. React applications from 2018 frequently cannot even be built anymore: breaking changes between major versions (class components → hooks → server components → etc.) mean the framework churn never stops.
We are a small team. We cannot afford to spend time on framework migrations. Our code should still work in five years without intervention.
8. Honest, Auditable Code
With static HTML and vanilla JS, anyone can view source and see exactly what the page does. A compiled React application shows transpiled, minified output that reveals nothing useful.
This matters for trust, for debugging, and for contributor accessibility. Plain HTML/CSS/PHP can be read and edited by almost anyone. The Node ecosystem requires substantial tooling knowledge before you can even run a project locally.
9. Security Surface Area
Fewer moving parts means fewer things to exploit. Every framework, every package, every build tool is a potential vulnerability. Our sites have no build pipeline in production — there is nothing to compromise between the editor and the served file.
When We Do Use Heavier Tools
The above reasoning applies to our public-facing sites. Where the use case genuinely justifies it, we use appropriate tools:
- Database — Our auth server uses a proper relational database because authentication state genuinely requires it. Flat files would be the wrong choice there.
- Python — Our chat bots use Python because it handles long-running processes and I/O well, and it has no dependency issues at the scale we use it.
The question we always ask is, “Does this tool solve a real problem we actually have, or does it create new problems while solving imaginary ones?”