Skip to content
RYBKIN ALEKSEI
Back to work

Case study · JUN — AUG 2026

Roton medical supplies store

One person · three monthsroton-⁠med.ru

+12%

search traffic three months after the move

20 h

a month freed for a manager by the 1C exchange, by the client’s estimate

Problem
The site had been hacked, a page took 4 seconds to load, and a manager updated the products on it by hand.
Solution
Moved the shop to a clean build, set up the 1C exchange and ordering through a chat.
Result
A page loads in 1.1 s instead of 4.1 — from “poor” on Google’s scale to “good”. The 1,100 products update from 1C on their own, orders come in through the chat, and search positions were back within three weeks.
01

A clean site that opens in a second

The old Bitrix site had been hacked — 435 malicious files — and a page took 4 seconds to load. I moved the shop to a clean build without the infected code: a page opens in 1.1 seconds, and every Google speed metric is in the green. A slow page loses shoppers before they see the product.

How it was donecollapse

Moved a medical supplies shop off a hacked Bitrix site into a clean build on Next.js and WordPress/WooCommerce. Page load time (LCP) fell from 4.1 to 1.1 seconds.

WordPress with WooCommerce stayed a sealed-⁠off admin; the public side runs on Next.js, with list pages built ahead of time and cards built on demand and held in a response cache that the exchange invalidates by webhook, only for the products it touched. Served from our own server behind Caddy with automatic TLS, and the WordPress object cache in Redis. Every Core Web Vital is in the green, and the server returns a product card in 0.3s.

The old site had been compromised — 435 malicious files, web shells among them, in randomly named folders. Pulled the data into a clean build and carried none of the infected code across. Plus key-⁠only SSH, fail2ban and automatic HTTPS.

Roton home page — open full size
The home page opens in a second: every Google speed metric in the green.
02

Products update from 1C on their own

A manager used to update the products on the site by hand. Now every weeknight 1,100 products with stock and photos arrive from 1C by themselves, and sizes and colours come together in one card. 96% of products go live without anyone touching them. By the client’s estimate, that freed 20 hours of a manager’s time a month.

How it was donecollapse

Set up the automatic 1C exchange and wrote a Python parser for product attributes: around 1,100 products with stock and photos now update on the site by themselves. By the client’s estimate that freed 20 hours of a manager’s time a month.

The exchange runs over CommerceML every weeknight: products, categories, sizes, colours, stock and images — around 1,100 items and 2,300 offers in a quarter of an hour. The site and 1C can differ only until the next run. The exchange plugin needed fixing for PHP 8: 11 patches are applied by a script when the container starts, so reinstalling the plugin does not lose them.

The parser reads the size reference. The client’s Size field packs up to five things into one string: size, side, girth in centimetres, compression class and garment length, with Cyrillic and Latin mixed in. Of 233 distinct values it reads 227 — 97%, and 99.7% of products by how often a value occurs; six non-⁠standard ones go to manual review. The rules are covered by 47 unit tests, written before the code. The storefront never parses: it looks values up in a prepared table, and an unknown one holds the product in quarantine until a person rules on it. Forty of 1,070 items sit there now, so 96% publish with no manual step. Sizes and colours became a choice inside the card, and 2,300 offers collapsed into 940 cards.

Roton product card on a phone: colour and size choice — open full size
Size and colour are a choice inside the card: 2,300 variants from 1C fold into 940 cards.
03

Search traffic survived the move and grew

Changing the engine puts a shop’s search positions at risk: old links start leading nowhere. All 1,192 old addresses lead to the right pages; positions dipped by no more than 5% and were back within three weeks, and by the end of the third month search traffic was up 12%.

How it was donecollapse

Kept the search traffic through the change of engine: every old address leads to the right page, and search positions were back where they had been within three weeks.

A product is tied to its 1C row by that row’s identifier, so a repeat export updates the same page, and the address survives both a rename in 1C and the change of engine. Took an inventory of 1,192 addresses from the old site and covered them with 33 redirect rules, each landing on a live page that makes sense. Alongside that: structured data, a sitemap and readable transliterated URLs. Positions dropped no more than 5% in the first fortnight; organic traffic was up 12% by the end of the third month.

04

Shoppers find the product and order in a chat

Search with filters by brand, size and product type answers at once and never offers an option that finds nothing. Ordering needs no form or sign-⁠up: the shopper writes in a chat on the site, and a manager answers from Telegram. The old button sent people off to a messenger, where they got lost; now the chat converts at 4.2%. When the link to Telegram once broke, I restored the whole conversation history, and not a single enquiry was lost.

How it was donecollapse

Built search with filters and ordering through a chat on the site: the message goes to the managers in Telegram, the reply comes back to the visitor in the same chat.

Search runs on Meilisearch: instant results and filters by brand, size, section and garment type across a catalogue of a thousand items. The search engine computes the filter values and their counts itself, so the list never offers an option that finds nothing.

Ordering needs no form and no account: the button used to hand the visitor off to a messenger and lose them. Chat conversion is 4.2%. The bridge later died quietly: the server lost its route to Telegram, and the managers went weeks without seeing enquiries. Found it, stood up our own relay on Cloudflare Workers and recovered the whole correspondence — not one enquiry was lost.

Roton catalogue: compression hosiery filtered to class 2 — open full size
The filters never offer an option that finds nothing, so shoppers don’t hit an empty page.
Roton product page with the chat open — open full size
Ordering without a form or sign-⁠up: the shopper writes in the chat on the site, a manager answers from Telegram.
05

Failures show up at once, and the project isn’t tied to me

Three watchers keep an eye on the 1C exchange and report a failure to Telegram and by email, so stale stock doesn’t sit on the site unnoticed. Every night an encrypted copy of the database goes to the cloud, and restoring from it is tested: the last run brought back all 60 tables. With documentation, automated tests and one-⁠command deployment, another developer can take the project over.

How it was donecollapse

Set up monitoring of the exchange and backups with restore checks. Prepared the project for handover to another developer: documentation, automated tests, one-⁠command deployment.

Three independent watchdogs: a night watcher holds the export window and reports the catalogue diff, a morning check catches stale data, and a third starts watching the moment 1C makes contact, so an export outside the schedule cannot pass unnoticed. Success is judged by a mark the exchange stamps on every parsed product — not by what the exchange replied, since the standard has it answer 200 OK to an error as well. The delivery channel is doubled: Telegram through our own relay, plus email.

Encrypted nightly copies go to the cloud in chunks over four streams — a way around the provider’s 16 KB/⁠s per-⁠connection cap, which had been stopping them from arriving whole. A separate verify mode downloads a copy, decrypts it and loads the dump; the last run restored all 60 tables.

Failures were taken down to their cause. The 1C import kept dying on a cache flush: a third-⁠party plugin called one every thousand XML elements, which with an external cache means wiping the whole Redis database — 20,290 times in a month. Once the flush missed its timeout and rolled the entire export back. Replaced it with an in-⁠process flush, moved the full one to the end of the exchange, and made a cache failure harmless to the page. Another: a night report showed 73 items fewer in stock and the client was about to audit the warehouse, but the reading had been taken in the middle of a background recount. Fixed the completion flag and told the client myself that the alarm was false.

A reproducible Docker environment, one-⁠command deployment that rolls itself back on a failed build, 6,700 lines of documentation on deployment, recovery and operations, 16 operations scripts, 16 catalogue checks, 106 storefront tests. And a separate file of 157 diagnosed failures: what broke, why, and which signal catches it.

How it was built

I worked with Claude Code: I kept the architecture and sign-⁠off, the agent wrote the routine code, and every result went through automated tests and review. That is how one person moved the whole shop without a team, and the client had one person responsible from the first call to the handover.

Next.js React TypeScript WordPress/WooCommerce PHP Python 1C (CommerceML) MySQL Redis Docker

Got a similar problem? Describe it and I’ll suggest where to start.

Message me on Telegram