Health Tourism

Esthetic Hair: a multilingual clinic site on a purpose-built CMS

A headless CMS, a multilingual site network and a queued enquiry pipeline into Bitrix24 for a hair transplant clinic.

Company
Esthetic Hair · esthetichair.com
Role
Our own company · end to end
Industry
Health Tourism
Date
Esthetic Hair case study cover

Challenge

The clinic acquired patients in several languages, and each language was a separate site maintained separately. A single price change meant repeating the same work once per language. Enquiries from the form were typed into Bitrix24 by hand, and in the process both the source information (which ad, which language, which page) and a share of the enquiries themselves were lost.

Approach

We put content in one place and separated it from presentation: a CMS on Django REST Framework where editors compose pages from blocks, with Next.js rendering the multilingual site from that API. We moved the enquiry pipeline off "fire and forget" and onto the outbox pattern — the form lands in our own database first, then goes to Bitrix24 idempotently through a Celery queue.

Starting position

Esthetic Hair is a hair transplant clinic that takes patients from abroad. In this model the site is not a brochure but the first step of the sales pipeline: a visitor arrives from a search result or an ad, reads a treatment page, sends photographs and is connected to a consultant. The rest of the process runs in Bitrix24.

When we took over, three problems were tangled together:

  • One site per language. Each language lived in its own installation, on its own theme. The only shared thing was the brand; content, components and even form fields had drifted apart.
  • The cost of a content change scaled with the number of languages. One price update was as many separate jobs as there were languages. So in practice it was not done, and the sites told different versions of the truth.
  • A human sat between the enquiry and the CRM. The form landed in an inbox and was typed into Bitrix24 from there. Campaign, language and landing page were lost in transfer — which meant there was no way to know which ad brought a patient.

Decisions

Why not an off-the-shelf CMS

The first option we weighed was WordPress: established, cheap, familiar to the team. We rejected it for two reasons.

The first is multilingualism. In health tourism, languages are not translations of one another; the same treatment page sells with a price table in one language and with patient stories in another. Plugin-based translation layers read that difference as a “missing translation” and push you into a single shape.

The second is integration. What we needed on the Bitrix24 side was not a simple webhook but a queued pipeline with retries. Building that inside a plugin ecosystem would have meant putting the critical path in code we do not control.

Why Django REST Framework

What we wrote is less an API than a management system: role-based permissions, a draft/publish flow, a media library, revision history. Django’s ORM, migration discipline and admin foundation supply a large part of that out of the box. We never considered FastAPI on scaling grounds — the bottleneck here is not framework time per request, it is how many minutes an editor needs to build a page.

DRF’s serializer layer also earned its place in the multilingual model: the same content object serialises with a different field set depending on the language parameter, so the Next.js side never has to carry language logic.

Why Next.js, and why not fully static

The site carries hundreds of pages and the content team edits them during the day. Under full static generation every small correction rebuilds the whole site. Next.js incremental regeneration strikes the balance: the visitor always gets pre-rendered HTML while the edited page refreshes in the background.

Search is the real justification for that decision. Pages are rendered on the server, so Googlebot sees the content without executing JavaScript, and hreflang tags are generated from the language mapping in the CMS rather than written by hand.

Content model: block-based

Pages are not free-form HTML but typed blocks: hero, price table, before/after gallery, FAQ, doctor card, patient testimonial. Each block maps to a component on the Next.js side.

There is a cost: the editor cannot build any layout they imagine. In return the site still looks coherent years later, and changing one block’s design fixes the corresponding pages in every language at once. We made that trade deliberately.

Delivery

The Bitrix24 pipeline

The whole integration rests on one assumption: the external system will stop responding sooner or later.

  1. When the form is submitted, the enquiry is written to our own PostgreSQL table first. The user sees the thank-you page at that point; Bitrix’s current state is not the visitor’s problem.
  2. The same transaction creates an outbox record. A Celery worker picks these up in order and writes them to the Bitrix24 REST API.
  3. Every record carries an idempotency key. If the same enquiry is retried, no second lead is opened on the Bitrix side.
  4. On failure it is retried with exponential backoff; after a set number of attempts the record moves to a dead-letter queue and becomes visible in the panel. No enquiry disappears silently.
  5. Stage changes on the Bitrix side come back by webhook, so the site also knows where a lead stands.

UTM parameters, gclid, language, landing page and device travel with the enquiry and are written into custom fields in Bitrix. The answer to “which ad brought this patient” lives in those fields.

Redis does four jobs

A single Redis deployment serves four roles: Celery’s message broker, the cache for API read responses, the rate-limit counter for form endpoints, and the lock that stops a sync job from starting twice. One dependency instead of four separately deployed components — that is where the operational load drops.

Media

Before/after galleries are the heaviest asset in this sector. Derivatives are generated on upload (AVIF and WebP, three widths), the original stays in object storage and delivery goes through Cloudflare. The editor uploads one file and never thinks about sizing.

Deployment

The Docker image is built and tested on GitHub Actions before it ships. Database migrations run as a separate, reversible step outside the deployment. Error tracking is in Sentry, and every error in the enquiry pipeline raises its own alert — because an error there is a lost patient.

Measurement

We did not put numbers on this case study; the measurement period has not closed. When it does, these are the metrics that go here:

  • Time from form submission to lead creation in Bitrix24.
  • Share of enquiries with an identifiable source — before and after the integration.
  • Time to publish a new treatment page across every language.
  • Core Web Vitals (LCP, INP, CLS) — from field data, not a lab score.

Outcome

The content team now works from one panel across every language; a new treatment page is built once and opened to the other languages through the translation layer. Every enquiry reaches Bitrix24 with its source attached and without duplicates, and an integration outage no longer turns into a lost enquiry. The measurement period for a numeric comparison is still open.

Do you have an idea, or a product that has stalled?

Let us scope it in a short call. In the first conversation we cover the technical approach and an estimated budget range.