Choosing PHP, Node or static HTML: how we decide per product
A practical framework for picking a stack per product instead of forcing one everywhere: three questions that decide between static HTML, PHP/Laravel and Node - and the real reasons each Ornova Labs product uses the stack it does.
People sometimes notice that one of our products is built in PHP, another in Node, and our website is just static HTML, and ask whether we couldn't pick one and stick to it. The honest answer is that we choose the stack per product, on purpose. A tool's job decides its technology, not the other way round, and forcing every product into one stack would mean some of them fighting their tools. This is the actual framework we use, and why each of our products ended up where it did.
None of this is about chasing the newest thing. If anything we lean the other way, towards proven, boring technology that we can still run and understand in five years. The framework is just a way of matching a small number of solid choices to the problem in front of us.
There is no default stack
The trap most teams fall into is having a favourite hammer and treating everything as a nail. Pick React for everything and your simple brochure page ships a JavaScript runtime it never needed. Pick a heavy backend for everything and your static content carries a database it never uses. The waste is invisible on a fast laptop and very visible to a user on a phone on a patchy connection. So we start every product from the same neutral question, not "which stack do we like?", but "what does this thing actually have to do?"
Three questions that decide it
Almost every decision comes down to three questions, asked in order.
The order matters, because it stops you over-building. If the honest answer to the first question is yes, you are done, and you have saved yourself an entire backend.
When static HTML wins
If a thing is essentially content, a marketing site, documentation, a blog, a product page, the same for every visitor, static HTML wins outright. It is the fastest to load, the cheapest to host, and the hardest to break, because there is no server logic or database to attack or go wrong. That is exactly why we keep this website on static HTML. "Static" does not have to mean hand-maintained either; we generate these pages from small templates, so the output is plain fast HTML without the tedium people associate with it.
When PHP and Laravel win
The moment a product needs to remember things, accounts, records, forms, files, business rules, the question becomes which mature, data-friendly backend to use, and for us that is very often PHP with Laravel. It is unglamorous and excellent: a database, authentication, file storage, validation and routing all come in the box, hosting is cheap and everywhere, and it has been quietly running serious applications for decades. For a straightforward data-driven application, the kind with projects, entries, users and reports, it lets us build the real thing fast instead of assembling a backend from scratch.
When Node wins
Node earns its place when the job is really about JavaScript: a fast custom service, a generation pipeline, real-time behaviour, or a backend that should share a language and tooling with a heavily interactive JavaScript front end. When a product's hard part lives in the JS ecosystem, fighting that with another language is just friction.
How it played out across our products
Run our catalogue through that framework and the choices explain themselves:
| Product | What it has to do | Stack |
|---|---|---|
| ornovalabs.com | Content, the same for everyone | Static HTML |
| Railway PPT | Assemble presentations through a fast generation service | Static front end + Node service |
| Railway DPR | Projects, daily entries, photos, accounts, reports | Laravel + a JS front end |
| Rail LDCE Prep | Question bank, user progress, accounts | Laravel + a JS front end |
| Railway TA | Forms, records, document and email generation | PHP |
Notice the pattern: the data-heavy applications lean on PHP and Laravel for the boring, reliable backend work; the one product whose core is a generation service uses Node; and the content site that needs none of that stays static. Each answered the three questions differently, so each landed in a different place.
Mixing stacks on purpose
Running more than one stack is sometimes treated as a failure of discipline. We see it the other way. The cost of a second stack is real, more things to know and maintain, so we do not add one lightly, but the cost of forcing the wrong tool onto a product is usually higher and lasts longer. The discipline is not "one stack forever"; it is "every choice deliberate, and every choice something we can still support". A small, well-understood set of stacks, each used where it fits, beats a single stack stretched somewhere it does not.
The rules we actually follow
- Start from the job, not the stack. Decide what the product must do before naming a technology.
- Ask the three questions in order. The first honest "yes" usually picks the tool and saves you over-building.
- Prefer boring, proven technology. Choose what you can still run and reason about years from now.
- Don't ship what you don't need. No backend for content, no framework for a brochure.
- Keep the stack list small but not forced. Add one only when a product genuinely needs it.
- Optimise for maintenance, not novelty. The exciting choice is rarely the one you thank yourself for later.
There is a well-known essay called Choose Boring Technology that argues a team has only a limited budget for novelty, and should spend it carefully. We agree. Our framework is really just a way of spending that budget where it counts, on the product, not the plumbing, and letting each tool do the job it is genuinely good at.