DEV Community

Cover image for Writing C for the Web in 2026 Isn't Insane. It Might Be the Future

Writing C for the Web in 2026 Isn't Insane. It Might Be the Future

James Anderson on October 19, 2026

Someone shipped a modern, commercial web product — user accounts, Stripe payments, a search dashboard, transactional emails — written entirely in C...
Collapse
 
pengeszikra profile image
Peter Vivo • • Edited

I am resonating with your post. Even I know the ultimate dependency limitation is when we don't using computer. I have practical advice in that question too:

That why I buy this MazdaB2200/1993 pickup, no computer, no overengineered technical solution, even no 4x4. Just a pure functionality, a perfekt workhore for everything.

But back to the coding I think some of my previous post was step on this road:

JSDoc Evangelism - I am arguing why we don't need TypeScript for safety
Javascript great again! - JS game with reactive "framework" ( 3 simple function : proxy ) + CSS based 3D
Rustroke - WASM mini draw - Competition winner vector fill method written in Rust with zero dependency.
JS/TS/TSX terminal with |> - helpfull terminal for coding practice.

Your suggestion is in my head, so I will make a markdown first easy than vim terminal based editor powered by C! Without C dependency! We will see what will happen.

Collapse
 
james_anderson_h profile image
James Anderson •

Ha, the 1993 Mazda B2200 is the perfect physical version of the whole argument — no computer, no overengineering, no 4x4 it doesn’t need, just pure functionality that fits in your head and keeps running because there’s barely anything to break. That truck is the minimal-stack philosophy with a tailgate. The best workhorses, in trucks and in code, are the ones simple enough that you can fix them yourself.

And your posts are genuinely walking the same road — JSDoc instead of TypeScript (type safety without the build step and the dependency), a reactive “framework” that’s three functions and a Proxy, zero-dependency Rust/WASM. There’s a clear through-line: get the benefit, refuse the ceremony. That’s exactly the “every layer is a cost to justify, not a feature to collect” instinct, applied for real rather than just admired.

And now a C-powered, Vim-style terminal editor with no C dependencies — starting with markdown because it’s the easier on-ramp before the full editor? That’s a great way to do it, and honestly I’d read the writeup of that build more than almost anything on my feed right now. Build the small thing you can hold entirely in your head, see what it teaches you. Please post it when it exists — and keep the Mazda running. 🙂

Collapse
 
csm18 profile image
csm •

When Neovim started trending, I honestly hadn’t even tried Vim. And every tutorial said to start with Vim, so I decided to give Vim a try instead.

Turns out, the latest version of Vim is much closer to a basic code editor than I expected. You get syntax highlighting, the ability to open and edit files, and even a terminal that you can launch from inside Vim!

Collapse
 
james_anderson_h profile image
James Anderson •

That’s a great example of the exact thing the terminal/fundamentals conversation keeps circling — reaching for the newer, trendier thing (Neovim) and discovering the “old” tool it’s built on was already most of what you needed. Plain Vim being closer to a real editor than people assume is a nice reminder that a lot of the complexity we pile on is solving problems the base tool had quietly already handled — syntax highlighting, a built-in terminal, split windows, all there without a single plugin. And starting with the thing the newer tool is based on is underrated advice generally, because you learn what’s fundamental versus what’s a convenience layer on top — which means when something breaks, you actually know which layer to look at. Enjoy the Vim rabbit hole; it’s one of the good ones.

Collapse
 
build996 profile image
build996 •

The 'deploy by replacing the binary' detail is compelling, but I'd want the schema story beside it before calling deployment simple. Once a release changes the SQLite schema, swapping back the executable may no longer restore a working version. Keeping old and new binaries compatible with the database during an upgrade seems like part of that simplicity budget too. Does the original build describe how it handles migrations, or where it draws the line between a binary replacement and a deployment that needs coordination?

Collapse
 
james_anderson_h profile image
James Anderson •

That’s exactly the right pressure to put on “just replace the binary,” and you’ve found the seam: binary replacement is atomic and trivial until the schema moves. At that point the binary and the database are coupled, and swapping the executable back no longer restores a working version, because the data underneath changed shape. The rollback story quietly stops being “copy the old file back” the moment a migration has run.

So the real simplicity budget isn’t “one binary to deploy” — it’s “one binary plus a schema both the old and new binary can tolerate during the window where both might run.” That’s the genuinely hard part of any zero-downtime deploy, and it doesn’t get easier just because the stack is minimal.

To your actual question: the original doesn’t cover migrations — it frames deployment as binary replacement and leaves the schema lifecycle out of scope. Where I’d draw the line: a pure binary swap is safe only when the change is schema-compatible both ways (additive columns, nothing renamed or dropped). Anything destructive needs coordination — expand/contract migrations, a both-binaries-work window, a real rollback plan. SQLite makes it a file-level concern, not a server to coordinate with; it doesn’t repeal the discipline. Worth a paragraph. Going in the revision.

Collapse
 
build996 profile image
build996 •

The both-binaries-work window would make that revision much more useful. I'd qualify 'additive columns' a little: an old writer can still fail if a new column is required without a suitable default, and positional reads can make extra columns surprising too. A concrete compatibility check could run the old binary against a copy of the migrated database and exercise writes as well as reads. That would give the simple deployment claim a boundary readers can actually test before replacing the executable.

Thread Thread
 
james_anderson_h profile image
James Anderson •

The qualification is exactly right — “additive” isn’t automatically safe. A NOT NULL column with no default breaks the old writer, and positional reads (SELECT *, column-index access) make even harmless-looking extra columns surprising. “Additive” has to mean backward-tolerant, which is a stricter claim than just “I only added things.”

And the concrete test is what turns the boundary from assertion into something checkable: run the old binary against a copy of the migrated database and exercise writes as well as reads, not just startup. That’s the step that catches the NOT-NULL and positional cases before they hit production, and it gives “just replace the binary” an actual precondition readers can verify instead of trust. Going in with credit — the old-binary-against-migrated-copy check is the receipt the simplicity claim needed.

Collapse
 
slabb profile image
Sam LABBE •

I made the same bet one layer up the stack: the browser.

My agent-browser (navette) is a ~660 KB Rust binary driving the WebView your OS already ships — no Chromium download, no 200 MB of Puppeteer luggage per CI run. Same math as Prophitt's C app, same three forces you name, compounding one layer higher: agents installing npm packages at machine speed, AI-generated test code nobody can hold in their head, and a complexity tax measured in gigabytes.

The part nobody believes until they measure it: opening a page in the OS WebView costs ~8 ms warm, and full rendering comes free because the engine shipped with the OS. The "insane" option turns out to be the one downloading 218 MB.

One force I'd add to your list, though — and it's the one your stack answered with C and mine answered with Rust: memory safety. Radical simplicity cuts the dependency graph to zero; it doesn't cut the CVE classes in the language you kept. OpenBSD's mitigations are doing real work in that deployment — worth a paragraph in the next revision.

The comprehensibility point is the one I'll steal, though. In a flood of generated code, "I can read every file" is the new moat. Verified on my own stack: the whole thing fits in your head too.

Collapse
 
james_anderson_h profile image
James Anderson •

Navette is the same bet one layer up, and the numbers are the argument: a 660 KB Rust binary driving the OS WebView instead of 218 MB of downloaded Chromium per CI run. Same three forces, compounding higher — agents installing packages at machine speed, AI-generated test code nobody can hold in their head, a complexity tax now measured in gigabytes. And the ~8ms warm open with rendering free because the engine shipped with the OS is exactly the “nobody believes it until they measure it” detail that makes the whole case land — the “insane” option was the one downloading a quarter-gig.

Your memory-safety addition is the right correction, and sharper than my footnote: radical simplicity cuts the dependency graph to zero, but it doesn’t cut the CVE classes in the language you kept — which is the whole reason the language choice (your Rust, Prophitt’s C-plus-OpenBSD-mitigations) is load-bearing, not incidental. Zero dependencies and a memory-unsafe core is a smaller attack surface with the same sharp edges. That’s a paragraph in the revision, credited. And “I can read every file is the new moat,” verified on your own stack, is the line I’ll be stealing back.

Collapse
 
slabb profile image
Sam LABBE •

Take the paragraph 😉 — the revision reads better with the correction inside it than beside it.

The symmetry between the two bets is my favorite part: yours deletes the runtime, writing to the web's metal; mine deletes the bundle, driving the engine the OS already ships. Same instinct from two directions — remove the layer that grew past what anyone can hold. And your CVE-class point is the honest half of the C case: zero dependencies doesn't remove the language's sharp edges, it makes them countable — and countable is exactly what an audit needs. The moat line comes with the same duty on my side: "I can read every file" is only a moat while the reading actually happens, which is why navette ships with a bench anyone can rerun. Curious how the mitigation paragraph lands in the revision — OpenBSD's hardening story plus C deserves the same receipts treatment as the dependency graph.

Thread Thread
 
james_anderson_h profile image
James Anderson •

Taking the paragraph, with thanks. 😉 “Same instinct from two directions — delete the runtime, delete the bundle” is the cleanest framing of both bets. And “countable is what an audit needs” is the real point — zero dependencies doesn’t remove C’s sharp edges, it makes them few enough to actually check. The moat’s duty cuts the same way: readable only counts while someone reads, which is why the rerunnable bench matters. The mitigation paragraph gets the receipts treatment — OpenBSD hardening deserves the same proof as the dependency graph.

Collapse
 
dhruonweb profile image
Dhruv Malaviya •

The supply-chain argument is the strongest here and deserves to lead. Three libraries you can name beats three hundred transitive dependencies you can't, and that's a real security property rather than an aesthetic preference.

The honest counterpoint is that the dependency count didn't go to zero, it moved. SQLite, a TLS implementation, the OS itself — you still don't audit those. What changed is the number of parties between you and them, which is the part that actually matters.

The thing that doesn't transfer is team size. One person can hold a C codebase in their head, and that's a large part of why this works. At six engineers the calculus flips — not because C got worse, but because the shared understanding did.

Worth separating too: replacing one binary is a bigger win than the throughput argument, which was always going to be fine.

Collapse
 
james_anderson_h profile image
James Anderson •

The point that the dependency count didn’t go to zero but moved is the sharpest correction the piece could get, and you’ve named exactly where it went: SQLite, a TLS implementation, the OS. You still don’t audit those — nobody reads libcrypto line by line. What changed isn’t that trust disappeared; it’s the number of parties between you and the thing you’re trusting, and the removal of the anonymous long tail. Three named libraries maintained by projects under global scrutiny is a different risk profile than three hundred transitive packages from strangers — not zero trust, just trust concentrated in fewer, more-watched hands. That’s the real security property, stated precisely.

And the team-size limit is the one I most under-weighted. “Fits in your head” is load-bearing, and a head is singular. One person holds a C codebase; at six engineers the shared mental model fragments, and total comprehension — the thing that made the minimal stack safe — is exactly what you lose first. Not because C got worse, but because comprehensibility doesn’t scale with headcount the way framework conventions do. That’s the honest boundary on the whole argument.

And you’re right to separate the wins: replacing one binary is the real prize; throughput was always going to be fine. Conflating them weakens the strong claim with the trivial one. All three going in the revision.

Collapse
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

.