Back to blog

Frontend deploys in 3 minutes, backend takes 40: managing deploy velocity in headless Bitrix

We went headless. The frontend started deploying in 3 minutes. The backend didn't.

For the first two months, we lived with this: a feature was ready in Next.js, but the Bitrix API wasn't there yet. Or the opposite — the backend updated, the frontend had no idea. Production showed green, the site misbehaved. This isn't a flaw in headless architecture. It's an operational problem nobody talks about in headless tutorials.

In headless, "deploy" isn't one operation. It's two.

In a monolith, there's one repo, one deploy. Frontend and backend ship together. Annoying, but at least predictable.

In headless you have two independent stacks with different delivery speeds. On paper, that's freedom. In practice, it's asymmetry you need to manage consciously.

Next.js in 3 minutes, Bitrix in 40: what's actually happening

Our setup: IvanPin/ivanpin.com runs Next.js + PM2 on the same server. Deploy is git pull, npm run build, pm2 restart. Usually 2–3 minutes. If something breaks, rolling back to the previous PM2 snapshot takes seconds.

The backend, IvanPin/wgp_backend, is Bitrix. Deploying PHP endpoints goes through FTP via our deploy_ivanpin_api.py script. The sequence: upload files, reset OPcache via HTTP call on prod, verify the endpoints are responding. Realistic time with context switching and manual checks: 30–40 minutes.

The deploy velocity gap between Next.js and Bitrix is 10–13x. Not a catastrophe — if you manage it deliberately.

Three desync patterns that kill your iteration speed

  1. A feature is ready in Next.js, tests pass, you deploy the frontend — and users see empty content because the backend doesn't know about the new API field yet. If you don't maintain backward compatibility by default, this pattern repeats.
  1. You update the response format in a Bitrix endpoint. Everything's fine in the test environment. On prod, Next.js starts receiving different JSON and breaks silently — no 500 error, just data rendering wrong. You find out a few hours later.
  1. The most painful for our specific setup: wgp_backend serves both ivanpin.com and webgoodpeople.com simultaneously. A change in the API for one site can break the other. A backend deploy isn't an isolated operation — it's a risk for multiple consumers at once.

API contracts as the way to decouple stacks

What actually helped was making the contract explicit. We have X-WGP-Token and a fixed set of endpoints under /api/ivanpin/services/. That's already a contract: Next.js knows what the response will contain, Bitrix knows which format it can't change.

The practical rule we introduced: no breaking API changes deploy without a simultaneous frontend update. If a change isn't backward compatible, we either add versioning or write an adapter layer on the Next.js side.

This isn't sophisticated architecture. It's an agreement that needed to be written down.

Feature flags: who owns the state

The next question that comes up: you're shipping a new feature. When do you "enable" it — on the frontend or backend?

We split it like this: if a feature depends on backend data, the flag lives on the backend. That might be a field in the API response (feature_enabled: true/false), or simply the presence or absence of an endpoint under /api/. Next.js checks before rendering. If the backend isn't ready, the feature isn't shown.

If a feature is purely frontend — UI toggle, component switch — the flag lives in Next.js, in environment variables or config. Deploys without touching Bitrix.

That split eliminated most of our desync issues.

What we changed in the deploy process

Before: "I'll just update it now." After: a 4-step checklist.

First, I check whether there are frontend changes waiting for this backend update. If there are, I ship them together.

Second, for any API change I verify backward compatibility. If I'm breaking the format, the frontend updates first.

Third, after deploying Bitrix I run a smoke test on both sites. One HTTP request to /api/ivanpin/services/blog/ and one to /api/services/wgp/ — I confirm both respond correctly.

Fourth, I log in deploy_ivanpin_api.py exactly which files changed. That's what lets me roll back to the previous version in 5 minutes if something goes wrong.

What I'm not automating

I haven't automated the Bitrix deploy and I'm not planning to anytime soon. The backend lives on a shared server with multiple projects. An error in an automated deploy can touch a client's production environment. A manual deploy with a checklist costs more time, but less risk.

That's not an excuse. It's a deliberate tradeoff.

Takeaway

Going headless doesn't solve the deploy problem — it reformulates it. Instead of "we ship together," you get "we ship independently but need to coordinate."

If your Next.js goes to prod in 3 minutes and Bitrix takes 40, that's not a reason to abandon headless. It's a reason to define rules: what ships first, where flags live, how you verify compatibility.

Managed asymmetry beats the illusion of synchrony.

→ Why I chose this headless architecture in the first place: Headless isn't about speed. It's about deploy independence.

→ How I decided not to rewrite Bitrix: I turned down a $40K rewrite contract. The logs told me why.