The «Praktika» yoga center — two studios, 24 disciplines, 18 instructors and over 4,000 clients — ran on an infected Bitrix that, on top of everything, kept the whole site under a `noindex` directive: to search engines it simply did not exist. We rebuilt the project from scratch on Django 5.2 with server-side rendering, moved the database to MySQL 8 and preserved every old URL. We fitted an in-house booking system with an admin calendar and hourly schedule sync, and then opened 116 pages to indexing — with Schema.org markup, 1,132 FAQ questions and a content map for AI search.
The result in brief
- 🔓 From 0 to 116 pages open to indexing (the site was fully closed with
noindex) - 🦠 From an infected Bitrix to a clean Django, without a single line of legacy code
- 🗓 In-house booking system with an admin calendar and hourly schedule sync from an external calendar
- 🧩 1,132 FAQ questions and Schema.org markup on 110 pages
- ⚡ 26 KB HTML, 0.4 s response, gzip, HTTPS — a technical base ready to grow
The challenge
The client is a yoga center in a Russian city: two studios, more than 20 disciplines, 18 instructors and over 4,000 clients across 20 years. The old website ran on an outdated Bitrix build that, by the time work began, was infected with malicious code. On top of that, the whole site served a noindex, nofollow directive in the <head> — it was completely invisible to Google and other search engines. However much content it held, in search it simply did not exist.
The job was not to "tweak the SEO" but to rebuild the project entirely: strip out the infected engine, move the content onto a manageable platform, give the center a tool for its schedule and bookings, and stand up technical search hygiene from scratch, so the site could finally start getting indexed.
What we did
The site was rebuilt on Django 5.2 with server-side rendering: the HTML is delivered ready, not assembled in the browser, so both search robots and AI crawlers (which barely run JavaScript) see all the content and markup right away.
Content was moved into a single registry: pages, disciplines, instructors, texts, keywords and meta — all in one place, edited centrally. The 116 pages are built from that registry, and duplicates are impossible by design.
The infected Bitrix and its databases were removed, and the old site was decommissioned with no downtime on the live domain. All previous URLs (including .php) were preserved, so old links do not break.
Technology stack
- Backend: Python, Django 5.2 (SSR), Phusion Passenger
- Database: MySQL 8 (migrated from a legacy 5.7; on shared hosting where the standard driver won't compile, pure PyMySQL was wired in)
- Infrastructure: nginx → Apache → Passenger, HTTPS (Let's Encrypt), gzip, HTTP→HTTPS redirect
- Booking system: a custom Django app, with the admin panel as the working interface
- Integrations: schedule import from an external calendar (iCal), web analytics, cron
- SEO/GEO: JSON-LD @graph, sitemap.xml, robots.txt, llms.txt, llms-full.txt
Implemented features
Website and content
- 116 pages: home, a catalogue of 24 disciplines, 18 personal instructor pages, service, pricing and contact sections, a blog
- A 3-week schedule with day-by-day scrolling and a mobile-friendly layout
- A blog of 50 SEO articles about yoga, targeting search and informational queries
- An "Upcoming practices" block on the home page, live, from the same source as the schedule
Booking system (internal)
- The class calendar as the admin's main screen, creating a class with a click
- Studios with capacity, instructors, discipline cards and seat-limit control with no race conditions
- Schedule import from an external calendar for both branches, automatically once an hour
- A distinction between "the calendar failed to load" and "no classes" — the user sees a correct status, not an alarming error
Technical SEO and markup
- Individual
title(30–60 characters) anddescription(120–160) on all 116 pages, zero duplicates - Canonical, correct indexing, Open Graph and Twitter Card on every page
- JSON-LD @graph: Organization, two branches as LocalBusiness with addresses and hours, WebSite, WebPage, Service for disciplines, Person for instructors, FAQPage, BreadcrumbList, Article
- Semantic breadcrumbs on 115 pages, tied to the schema
- FAQ blocks: 1,132 questions on 110 pages, with markup built from the same text the user sees
- Sitemap, robots.txt and llms.txt / llms-full.txt — a content map for AI search engines
Social and accessibility
- 116 cards at 1200×630 for sharing (photo + a branded plate with the logo), generated by a script
- Accessibility: labels on form fields, accessible names for icon links and buttons, correct markup for decorative images
The process
- Audit and diagnosis. We dissected the infected Bitrix, confirmed the site was fully closed to indexing and mapped the project.
- Rebuild on Django. We moved the content into a manageable registry and assembled 116 server-rendered pages.
- Infrastructure migration. We deployed on production hosting, moved the DB to MySQL 8 and set up HTTPS and the old URLs.
- Booking system. We designed and built in the calendar, studios and schedule import.
- SEO / GEO / A11y. We ran the site through seven stages: indexing, meta, markup, breadcrumbs, page schema, accessibility and content.
- Auditor loop. We checked the live site across all 116 pages until two clean CLEAN runs in a row.
- Launch. We removed the
noindex, opened the site to search engines and prepared the sitemap for submission to Search Console.
Results
| Metric | Before | After |
|---|---|---|
| Pages open to indexing | 0 (whole site under noindex) | 116 |
| Engine | infected Bitrix | Django 5.2, clean code |
| Individual title/description | none | 116 of 116, 0 duplicates |
| Schema.org markup | absent | 8+ entity types, a coherent @graph |
| FAQ with markup | none | 1,132 questions, 110 pages |
| Cards for social sharing | placeholder logo | 116 cards at 1200×630 |
| Booking system | none | admin calendar + hourly sync |
| Technical audit | — | STATUS: CLEAN, 0 critical across 116 pages |

