Back to Lab
RAXXO Studios 10 min read No time? Make it a 1 min read

Bun vs Node for Solo Developers Six Months In

Bun
10 min read
TLDR
×
  • Moved 4 scripts to Bun, kept 6 on Node after six months of real use
  • Bun startup is 4x faster, which matters for scripts I run 200 times a day
  • Native deps and FFI broke twice, so cron jobs stayed on Node
  • My rule: hot loops go Bun, anything touching weird npm packages stays Node

Six months ago I ran every script in my studio on Node. Today 4 of them run on Bun and 6 still run on Node, and I have a clear rule for which is which. Here is what actually happened, including the two times Bun broke things and I had to roll back.

Why I Tried Bun At All

I run a one person studio. I have about 30 scripts that do everything from generating product images to posting on social to building blog pages. Most of them are small. A script that resizes 40 images. A script that pings an API and writes JSON. A script that runs a template over a folder of files.

The thing about small scripts is I run them constantly. My image prep script runs maybe 200 times a day when I am working on a product drop. Each Node startup is around 120 milliseconds before my code even begins. Multiply that by 200 and I am spending 24 seconds a day just waiting for Node to wake up. That sounds tiny. Across a week of heavy work it is real friction, the kind that makes me alt tab and lose focus.

Bun cold starts in about 30 milliseconds on my machine. That is roughly 4x faster. For a script I run 200 times, the startup difference alone saves around 18 seconds a day. Again, small in isolation. But the speed shows up everywhere. `bun install` finishes in seconds where `npm install` took close to a minute on the same `package.json`. The TypeScript runs directly with no build step, no `ts-node`, no `tsx`. I just write `script.ts` and run `bun script.ts`.

That last part was the real hook. Half my friction with Node and TypeScript was the build dance. Configure `tsconfig`, pick a runner, deal with ESM versus CommonJS arguments that never end. Bun runs TypeScript and JSX out of the box and reads `.env` files without me importing dotenv. For a solo dev that wants to write a script and run it, this removes three steps.

So I picked 4 scripts to migrate as a test. Two image utilities, one API fetcher, and one file templating tool. None of them touched a database or a native binary. They were pure JavaScript and TypeScript with a couple of npm packages. If Bun was going to win anywhere, it would win here first. If you want the full picture of how I structure these tools, Claude Blueprint covers the whole studio setup.

The 4 Scripts I Moved And Why

The first script was my image prep tool. It reads a folder, resizes, renames, and writes out variants for product listings. It uses Sharp under the hood. This was the script I most wanted to be fast because of the 200 runs a day problem.

Sharp is a native dependency, which made me nervous (more on that gotcha later). But it installed clean under Bun and ran without complaint. The whole pipeline got noticeably snappier, mostly from the startup time and faster file I/O. Bun has a built in `Bun.file` API that reads and writes faster than the Node `fs` calls I was using. I rewrote the read and write parts to use it and shaved another chunk off each run.

The second script was an API fetcher that hits a few endpoints and merges the JSON. Bun ships with `fetch` built in, same as modern Node, so no axios, no node-fetch. This one moved with almost zero changes. I deleted two dependencies and it just worked.

The third was a file templating tool that takes a folder of templates and produces blog page skeletons. Pure string work and file writes. Bun's faster file operations made this one feel instant. It used to take about 3 seconds on a big batch. Now it is under one.

The fourth was a small local test runner. Bun has a built in test runner that is Jest compatible enough for my simple cases. I dropped Jest entirely. My test suite went from a 4 second startup to running in under a second. For a solo dev who avoids writing tests because the tooling is heavy, a fast built in runner actually got me to write more of them.

The pattern across all 4 is clear. They were hot loops or things I ran constantly, and they did not depend on anything weird. They used file I/O, fetch, and one or two well behaved packages. Bun replaced a pile of dependencies with built ins. I removed dotenv, node-fetch, and a TypeScript build runner across these scripts. Fewer dependencies means fewer things that break when I update them six months from now. That maintenance saving is the part I underrate at first and appreciate later. I wrote about that exact tradeoff in the Claude Blueprint overview.

The 6 Scripts That Stayed On Node

Now the part nobody puts in the breathless Bun blog posts. Six scripts stayed on Node, and two of them stayed because Bun broke them.

The first break was FFI. I had a script that calls into a native library through Node's foreign function interface to do some low level audio work. Bun has its own FFI API, `bun:ffi`, and it is genuinely fast. But porting my existing Node FFI code meant rewriting the bindings against a different API surface, and one of the type mappings did not behave the way I expected. I spent an afternoon on it, got a segfault I could not explain, and decided the script worked fine on Node already. So it stayed. Rule learned: if a script does FFI and already works, do not move it unless the speed is the whole point.

The second break was a native dependency that ships prebuilt binaries for Node's ABI but not for Bun. The package installed but threw at runtime because the binary it shipped expected a Node specific symbol. Sharp worked fine because its maintainers test against Bun. This other package did not, and I was not going to maintain a patched build. Back to Node.

The other four scripts stayed on Node for boring reasons, which are the best reasons. Two of them run as scheduled cron jobs on a server I do not want to touch. They work. They have run untouched for months. Swapping the runtime on a stable cron job to save 90 milliseconds of startup is a bad trade. The risk is real and the payoff is nothing, because nobody is waiting on a cron job at 4am.

The last two are scripts that lean heavily on packages from the deeper end of npm, the kind that do clever things with the module system or assume specific Node internals. Bun's Node compatibility is good and improving fast, but "good" is not "identical." When a package reaches into `process.binding` or some undocumented Node corner, you find out at runtime. For scripts I depend on for a product drop, I do not want surprise runtime errors. Node is the safe default for anything load bearing that uses dependencies I did not vet.

If you run a studio on Shopify like I do, the scripts that talk to the storefront API are exactly the load bearing ones I left on Node. The fetcher moved, but the publish pipeline stayed.

The Rule I Use Now

After six months I do not agonize over this anymore. I have a checklist, and it takes about ten seconds to apply.

Move it to Bun if all three are true. One, I run it often or it sits in a hot loop where startup and file I/O speed matter. Two, its dependencies are either built into Bun or well known packages that test against Bun. Three, nothing about it is load bearing in a way where a surprise runtime error would cost me a product drop.

Keep it on Node if any of these are true. It does FFI that already works. It uses a native dependency that does not ship a Bun compatible binary. It runs as a stable scheduled job nobody is waiting on. Or it leans on npm packages that poke at Node internals.

The speed only matters in two of my four moved scripts, honestly. The image prep tool and the templating tool gained real, felt speed because I run them constantly during a drop. The API fetcher and test runner are faster too, but the win there is fewer dependencies and zero build config, not raw speed. That distinction is worth keeping straight. Bun is not magic dust that makes everything faster in a way you notice. It is faster startup, faster install, faster file I/O, and a pile of useful built ins. You feel it most where those specific things are your bottleneck.

One more practical note. I do not mix runtimes inside a single project without a reason. Each of my migrated scripts is standalone, so there is no shared `node_modules` confusion. If you have a big app with one entry point, picking one runtime for the whole thing is cleaner than going halfway. I covered how I keep these tools small and standalone in the Claude Blueprint guide, and that small-scripts habit is exactly what made the Bun migration low risk in the first place.

For media work that needs voice generation I still call out to ElevenLabs over their API, and that script does not care which runtime it runs in. The runtime choice only matters when the script does real local work.

Bottom Line

Bun is worth trying if you are a solo dev with small scripts you run all day. The wins are real: 4x faster startup, near instant installs, TypeScript with no build step, and built in fetch, test, and env handling that let me delete dependencies. I moved 4 scripts and I am glad I did.

But it is not a wholesale replacement yet. FFI and odd native dependencies are the sharp edges, and stable cron jobs have no reason to change. Six of my scripts stayed on Node and that is the right call, not a failure.

The honest takeaway is to migrate the scripts where speed and simplicity actually pay off, and leave the load bearing, dependency heavy, or already stable ones alone. That split has held up for six months with zero regret.

If you want the full picture of how I run a one person studio on small, swappable scripts, the Claude Blueprint walks through the whole setup. Try Bun on one throwaway script first. You will know within an hour whether it fits your workflow.

This article contains affiliate links. If you sign up through them, I may earn a small commission at no extra cost to you. (Ad)

Stay in the loop
New tools, drops, and AI experiments. No spam. Unsubscribe anytime.
Back to all articles