Moving Kanvo to Cloudflare Workers, and what broke
Kanvo CRM ran on a conventional Node hosting setup for most of its life. That was the right call early — it is the path of least resistance, every library assumes it, and when you are writing software at night after running crews all day, you do not go looking for extra problems.
But the shape of the product changed. Kanvo serves field crews on phones with bad signal, scattered across job sites, hitting the app in bursts at 6am and again at 4pm. Cold starts and a single region were the wrong architecture for that. So earlier this year the platform moved onto Cloudflare Workers, on a cf-migration branch running at cf.kanvocrm.com until it was solid enough to take over.
The migration itself was not the hard part. The hard part was discovering how many dependencies quietly assume they are running inside a Node process.
The Workers runtime is not Node
Workers run on V8 isolates, not Node. There is a compatibility layer, and it covers a lot, but it does not cover everything — and the gaps do not announce themselves at install time. They announce themselves at deploy time, or worse, at runtime on a request from a real customer.
Three dependencies had to go.
PDF generation
Kanvo generates estimates, invoices, and work orders as PDFs. That was handled by @react-pdf/renderer, which is an excellent library and completely unworkable on Workers — it reaches for Node primitives around fonts and streams that simply are not there.
This was the most disruptive of the three, because PDF output is not a nice-to-have in this business. A contractor who cannot hand a client a clean estimate does not have a working CRM. Anything that replaced it had to produce byte-identical output to what customers were already receiving, because changing what an invoice looks like mid-stream is its own kind of support ticket.
Transactional email
Email was going out through a mix of Resend and SendGrid, both driven by nodemailer. Nodemailer opens SMTP connections, which means sockets, which means Node.
Rather than find a Workers-compatible SMTP client, the whole path moved to Cloudflare Email Service. That collapsed two vendors and one library into one binding on the same platform the app already runs on. Fewer moving parts, one less set of credentials to rotate, and no outbound SMTP to babysit.
The tradeoff is real and worth stating: this is more lock-in. Email now lives with the same vendor as compute and storage. That was a deliberate choice, not an accident — for a team of one, the operational simplicity is worth more than vendor optionality I would probably never exercise.
Analytics
@vercel/analytics came out. It was tied to a platform Kanvo was leaving, and its data was never load-bearing for any decision. Removing it was the easiest call of the three.
What the migration actually bought
The honest version: the app is closer to the people using it, and cold starts stopped being something I think about.
The less obvious win was consolidation. Before the migration, Kanvo's infrastructure was spread across a hosting provider, two email vendors, and separate file storage. Every one of those is an account, a bill, a status page, and a potential 2am problem. Collapsing them onto one platform means there is one place to look when something is wrong.
That matters more than it sounds like it should. I am not a team. When a crew is standing in a driveway at 7am and the app will not load, the time it takes me to figure out which vendor is broken is time nobody is working.
What I would do differently
I would have audited dependencies for runtime assumptions before starting, not during. A morning spent reading through the dependency tree asking "does this open a socket, touch the filesystem, or spawn a process" would have surfaced all three of these before any code moved.
Instead I found them one at a time, each as a surprise, each requiring a context switch away from the migration to solve an unrelated problem. The migration took longer than it needed to for entirely preventable reasons.
That is the recurring lesson of building software for your own operation: the constraint is never the interesting technical problem. It is that you are also the person who has to answer the phone.
Tell us what your operation needs.
Kanvo gets built against real jobs. If there's a workflow we haven't solved yet, that's the most useful thing you can send us.
Submit Feedback →One bucket, many businesses: how Kanvo isolates customer files
Every contractor on Kanvo uploads job photos, signed agreements, and equipment records. Here is how those files stay separated — and why one bucket beat one bucket per customer.
All entries
Everything we've written about building Kanvo.