Twenty years in business, two studios, 24 disciplines, 18 instructors and more than four thousand clients. And a website search engines never saw: the old Bitrix build was infected and, on top of that, returned a noindex directive in every response. However much content sat there, for Google and Yandex the centre simply did not exist. We rebuilt the project from scratch on Django, added our own booking system and opened 116 pages for indexing.
Client: Praktika yoga centre, Cheboksary · Category: website + SEO
The short version
- From 0 indexed pages to 116. The whole site sat under noindex; for search it did not exist.
- The infected Bitrix was replaced with clean Django, without a single line of inherited code.
- Our own booking system with an admin calendar and hourly schedule sync from Yandex Calendar.
- 1132 FAQ questions marked up across 110 pages: that much living text the centre had gathered over twenty years.
- 26 KB of HTML, a 0.4-second response, gzip and HTTPS.
- Every old URL preserved, including the .php ones, so links from old posts still land.
Tech stack
- Backend: Python, Django 5.2 with server-side rendering, Phusion Passenger.
- Database: MySQL 8, migrated from an inherited 5.7. On shared hosting the standard driver will not build, so pure PyMySQL is used.
- Infrastructure: nginx, Apache, Passenger, HTTPS via Let's Encrypt, gzip, forced HTTPS redirect.
- Booking: a custom Django application, with the admin panel serving as the staff interface.
- Integrations: schedule import from Yandex Calendar over iCal, Yandex Metrica, cron.
- SEO and GEO: JSON-LD @graph, sitemap.xml, robots.txt, llms.txt and llms-full.txt.
Server-side rendering was not a fashion choice. The HTML arrives fully assembled, so both search crawlers and AI crawlers, which barely execute JavaScript, see all the content and markup straight away.
How it started
The centre came with a complaint you hear often: “we have a website and it brings no clients”. Usually that means weak copy or slow loading. Here the cause was different and considerably worse.
First, the engine was infected. The old Bitrix build had gone years without updates and was hosting malicious code. Second, the entire site returned a noindex, nofollow directive in its header. It worked perfectly well, showed the schedule and the prices, and yet for search engines it did not exist.
Treating that makes no sense: the same holes bring back the same problem. The project was rebuilt entirely, the content moved into a single registry, and the old site was retired without any downtime for the live domain.
Full list of what was built
🌐 Site and content
- 116 pages: home, a catalogue of 24 disciplines, 18 personal instructor pages, services, prices, contacts, blog
- A three-week schedule with day-by-day scrolling and a mobile layout
- A blog of 50 yoga articles targeting search and informational queries
- A “Coming up” block on the home page, live, from the same source as the schedule
- A single content registry: pages, disciplines, instructors, copy, keywords and meta are edited in one place, and duplicates are impossible by design
🗓 Booking system
- The class calendar as the main admin screen, a class created with one click
- Studios with capacity, instructors, discipline cards, seat limits enforced without race conditions
- Schedule import from Yandex Calendar for both locations, automatically every hour
- A clear split between “the calendar failed to load” and “there are no classes”, so people see a correct status rather than an alarming error
🔍 Technical SEO and markup
- Unique titles of 30 to 60 characters and descriptions of 120 to 160 across all 116 pages, zero duplicates
- Canonical tags, correct indexing, Open Graph and Twitter Card on every page
- JSON-LD @graph: Organization, both locations as LocalBusiness with addresses and hours, WebSite, WebPage, Service for disciplines, Person for instructors, FAQPage, BreadcrumbList, Article
- Semantic breadcrumbs on 115 pages, tied into the schema
- FAQ blocks: 1132 questions across 110 pages, with markup built from the same text people read
- Sitemap, robots.txt, llms.txt and llms-full.txt as a content map for AI search
🖼 Social and accessibility
- 116 social cards at 1200×630: a photo plus the brand strip with the logo, generated by script
- Labels on form fields, accessible names for icon links and buttons, correct markup for decorative images
🚚 Migration and launch
- The infected Bitrix and its databases were removed and the old site retired with no domain downtime
- Every previous URL preserved, including the .php ones
- The site was taken through seven stages: indexing, meta, markup, breadcrumbs, page schema, accessibility, content
- The live site was checked across all 116 pages until two clean passes in a row
- Noindex was lifted, the site opened to search engines and the sitemap prepared for Webmaster and Search Console
Results
| Metric | Before | After |
|---|---|---|
| Pages open to indexing | 0, whole site under noindex | 116 |
| Engine | infected Bitrix | Django 5.2, clean code |
| Unique titles and descriptions | none | 116 of 116, 0 duplicates |
| Schema.org markup | absent | 8+ entity types, a connected @graph |
| FAQ with markup | none | 1132 questions across 110 pages |
| Social cards | placeholder logo | 116 cards at 1200×630 |
| Booking system | none | admin calendar and hourly sync |
| Technical audit | — | 0 critical findings across 116 pages |

