Ветклиника официальный сайт Москва: 752 услуги и 365 статей
Мой клиент likedog.ru - ветклиника в Хамовниках - пришёл ко мне с просевшим после миграции трафиком и задачей: чтобы по запросу "ветклиника официальный сайт москва" поисковики показывали именно их. Я не стал чинить баги поштучно - перепроектировал информационную архитектуру, выкатил 28 страниц специалистов, реальный прайс на 752 позиции и пропустил через автоматизированную редактуру 365 статей, причём каждая обошлась мне в $0.
Контекст: почему всё началось
Клиника находится по адресу Сивцев Вражек, 18 — это центр Москвы, Хамовники. Район дорогой и конкурентный: рядом крутятся такие гиганты как svoydoctor.ru (сеть из 20 филиалов с локальными лендингами под каждый), doctor-vet.ru (плотная воронка «дом / 24 часа / эвтаназия») и десятки агрегаторов. На старом сайте было 15 категорий услуг-зонтиков (Хирургия, Дерматология, Кардиология и т.д.), каждая с шаблонной ценой 1000 ₽ и парой абзацев общего текста. Ни подуслуг, ни локальных страниц по районам, ни специалистов с фотографиями. Параллельно случилась миграция инфраструктуры на Hetzner, и часть SSL/DNS-настроек временно сломалась — из-за этого боты несколько недель получали ошибки и трафик просел.
Когда я сел и честно посмотрел, что реально двигает SEO для ветклиники, картина оказалась такой: возврат бот-доступа после миграции уже сам по себе должен был вернуть 50–90% трафика по мере перекраулинга. Гораздо важнее было закрыть структурные пробелы — отсутствие коммерческих страниц под низкочастотные коммерческие запросы вроде «стерилизация кошки цена ЦАО» или «вызов ветеринара на дом круглосуточно».
Проблема №1: структура услуг не отражала реальный прайс
Я запросил у клиента актуальный прайс — пришёл XLS-файл на 780 строк, 28 категорий, сгруппированных не по «зонтикам», а по специалистам: Терапевт (115 позиций), Хирург (112), Офтальмолог (69), Стоматолог (48), УЗИ-специалист (28) и так далее. То есть на сайте была одна сетка («Хирургия как услуга»), а в реальной жизни клиника думает другой («Хирург как специалист с конкретными процедурами и ценами»). Это типичный кейс для медицинских клиник, и его надо было разруливать архитектурно.
Решение, которое мы выбрали: не ломать существующий раздел /services/ (там уже были метаданные, JSON-LD и какой-то крауля), а сделать параллельный раздел /uslugi/[specialist-slug]/ — 28 новых страниц, каждая под одного специалиста, с полным списком его процедур и реальными ценами. Старый каталог остаётся работать, новый растёт рядом и забирает себе низкочастотные коммерческие запросы.
Для этого пришлось завести новые таблицы в Postgres:
CREATE TABLE specialist (
id BIGSERIAL PRIMARY KEY,
slug TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
intro TEXT,
faq JSONB,
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE pricing_item (
id BIGSERIAL PRIMARY KEY,
specialist_id BIGINT REFERENCES specialist(id),
name TEXT NOT NULL,
price_rub INT,
category TEXT,
is_consultation BOOLEAN DEFAULT false
);Импорт XLS делался скриптом на xlsx — 752 валидных строки после фильтрации служебных категорий вроде «Ветеринарная клиника» (которая была пустым контейнером). Подробности про работу с самой базой описаны в документации Supabase, которая у нас селф-хостится.
Проблема №2: цены 2024 года были вдвое ниже рынка
Когда я отправил отдельного агента собирать через API выдачи Яндекса топ-3 конкурентов и парсить их прайсы (mobil-veterinar.ru, vetclinika.moscow, svoydoctor.ru), вылез сюрприз. Прайс likedog был системно ниже медианы рынка ЦАО:
| Услуга | likedog (2024) | Медиана 2026 | Разрыв |
|---|---|---|---|
| Стерилизация кошки | ~3 500 ₽ | 7 500 ₽ | ×2 ниже |
| Кастрация кота | ~2 000 ₽ | 4 000 ₽ | ×2 ниже |
| Стерилизация суки 10–25 кг | ~5 000 ₽ | 13 000 ₽ | ×2.5 ниже |
| Первичный приём терапевта | 900 ₽ | 1 800 ₽ | ×2 ниже |
Это проблема не только бизнесовая (клиника недозарабатывает), но и SEO-шная: у Яндекса есть Y.Vertical и поведенческие сигналы — если цены явно «играют в демпинг», это считывается и снижает доверие. Решили компромиссный вариант: хирургия и эвтаназия (где у клиента самый большой gap и где это бьёт по марже сильнее всего) — обновляем до медианы рынка по 12 правилам whitelist; рутинные приёмы и консультации — оставляем как есть, чтобы клиент сам потом ревизовал. Скрипт reconcile-цен прогоняет whitelist по pricing_item и обновляет только те строки, которые матчатся по фуззи-имени.
Проблема №3: 365 статей нуждались в редактуре
Параллельно с услугами решали другой вопрос: в блоге на сайте лежало 365 статей, написанных кем-то когда-то по шаблону «Сибирский хаски — это порода собак, которая была выведена в Сибири». Безликий текст без зацепок, без E-E-A-T, без ничего. Для пилота я взял claude -p (это Claude CLI в headless-режиме) и прогнал через него три статьи. Результат стал ощутимо лучше:
Было:
«Сибирский хаски – это порода собак, которая была
выведена в Сибири. Изначально их использовали
для перевозки грузов на санях...»
Стало:
«Сибирский хаски — энергичная ездовая порода,
выведенная чукчами тысячи лет назад для работы
в упряжке в суровых условиях Арктики. Сегодня это
один из самых узнаваемых компаньонов в мире...»
Пилот 3/3 успешно прошёл, дальше нужно было прокрутить тот же скрипт на все 365 статей. Один лобовой запуск по очереди занял бы ~10 часов — слишком долго и не использует параллелизм. Я добавил в scripts/edit-articles.ts пул воркеров с concurrency=8, перед запуском прогнал smoke-test на 2 статьях:
wall=218s, cpu=384s — параллелизм даёт ~1.76x на двух воркерах
Это ожидаемо: CLI-вызов держит TCP-соединение и тратит время на ожидание токенов, поэтому два воркера почти удваивают throughput. С 8 воркерами вышло ETA ~2.5 часа на 365 статей вместо 10 — приемлемо. Идемпотентность реализована через колонку seo_edited_at: если у статьи стоит таймстамп, скрипт её пропускает; оригинал сохраняется в full_description_legacy, чтобы при необходимости откатить. Из 365 запусков вышло 4 timeout-fail на самых длинных статьях — добили отдельным проходом с флагом --rerun --slug ....
Главный экономический момент: всё это считается по подписке, не по API-токенам. То есть редактура 365 статей фактически стоила $0 — просто время CPU и сетевых вызовов.
Архитектура страниц специалистов: что внутри
Каждая страница /uslugi/[slug]/ содержит:
- Intro-блок (генерится через Claude CLI на основе названия специалиста и списка его процедур) — 2–3 абзаца, отвечают на вопрос «зачем сюда идти и кто этот специалист».
- Прайс-таблица — все 50–115 позиций из
pricing_itemс реальными ценами. Это сильный коммерческий сигнал и для пользователя, и для Яндекса. - FAQ-блок (тоже генерится CLI-скриптом) — 5–7 вопросов формата «Сколько стоит первичный приём?», «Делаете ли вызов на дом?», «Можно ли без записи?».
- JSON-LD —
MedicalBusiness+Service+FAQPage. Без этого структура не считывается агрегаторами вроде Яндекс.Услуг. - Кросс-ссылки на блог — 3–5 релевантных статей из тех самых 365 отредактированных.
Все компоненты пишутся на Next.js App Router — у нас Next.js 16 на React 19, деплой через Dokploy + GHCR + GitHub Actions. Sitemap и навигация добавлены отдельным коммитом, чтобы каждый из 28 URL попал в sitemap.xml и был доступен из меню. Для типобезопасности обращений к БД использовали Supabase JS SDK с кастомной схемой через { db: { schema: 'public' } }.
Нюансы реализации, на которых я споткнулся
Не всё прошло гладко. Из неочевидного:
import { supabaseAdmin } from "@/lib/supabase"ломал env. Модуль читаетprocess.envпри загрузке, до того какdotenvуспевает прочитать.env.local. Лечится переходом на паттерн «свойcreateClientвнутри скрипта», как вedit-articles.ts.- Структура хирургического прайса оказалась не «по операциям», а «по категориям сложности» (1–4). То есть искать «стерилизация кошки» в Хирурге бесполезно — там «4-я категория сложности (ОГЭ кошки, мастэктомия и т.д.) — 8 000 ₽». Пришлось докручивать UI прайс-таблицы под этот формат и делать отдельное объяснение для пользователя.
- Триггер
set_updated_atне выполнился при миграции, потому что функция жила в другой схеме иEXISTSеё ловил, а реальный вызов внутри триггера падал поsearch_path. Не блокирующее, поправили отдельной мини-миграцией. - Параллельные агенты для разведки конкурентов — отличная штука, чтобы не загромождать основной контекст сырым HTML и сотней URL. Один агент анализировал
mobil-veterinar.ruподробно, второй парсил выдачу и сводил топ-3 в таблицу. Подробнее про подход у Anthropic в документации Agent SDK.
Результат и метрики
По итогу за неделю работы получилось:
- 365 статей отредактированы CLI-проходом за ~2.5 часа, стоимость $0.
- 752 услуги импортированы из XLS в
pricing_itemс реальными ценами. - 28 страниц специалистов на
/uslugi/<slug>/со связкой intro + прайс + FAQ + JSON-LD. - 8 карточек врачей на
/vrachi/<slug>/(B4) — отдельный спек и отдельный коммит. - 3 urgent-funnel-страницы для горячих коммерческих запросов (B3) — «вызов ветеринара на дом 24 часа», «эвтаназия на дому» и так далее.
- Корректные
sitemap.xml, навигация, кросс-ссылки между блогом и услугами. - Цены на хирургию и эвтаназию приведены к медиане рынка ЦАО (12 правил whitelist).
Трафик пока подтягивается — это инерционный процесс, Яндекс перекраулит сайт в течение 2–4 недель. Главное, что структурно мы закрыли пробелы, которые мешали клинике конкурировать: коммерческие запросы теперь имеют посадочные страницы с ценами, специалисты выделены в отдельную сетку, статьи блога перестали выглядеть как контент-фабрика 2018 года.
Выводы
Первое: разделяйте «архитектурные» SEO-задачи и «контентные». Архитектурные (правильные URL, sitemap, JSON-LD, иерархия услуг) дают эффект через 2–4 недели, но он гарантированный и накопительный. Контентные (редактура текстов, добавление картинок) — гораздо более marginal и могут вообще не сдвинуть метрики, если архитектура кривая. Если у вас есть выбор куда вкладываться первым — всегда сначала архитектура, потом контент.
Второе: реальный прайс важнее красивого описания. Когда мы развернули 28 страниц специалистов, главный эффект дали не intro-тексты и не FAQ, а сама прайс-таблица. Пользователь, попадая на страницу с 50–115 конкретными ценами, видит «здесь не врут и не торгуются» — и Яндекс тоже это видит через поведенческие. Если у вас есть шанс показать реальные цены — показывайте, даже если страница станет «менее красивой».
Третье: автоматизация редактуры через CLI окупается мгновенно. 365 статей через claude -p — это $0 и 2.5 часа против тысяч долларов копирайтерам и недель работы. Главное — заложить в скрипт идемпотентность (seo_edited_at), сохранение оригинала (full_description_legacy) и пул воркеров с разумным concurrency. Это шаблон, который дальше переиспользуется на любом проекте с большим количеством текстов.
Четвёртое: параллельные агенты экономят контекст. Когда нужно собрать данные с 5–10 внешних источников или прогнать 10 однотипных проверок, не делайте это в основном диалоге — делегируйте субагентам. Главный контекст должен быть про принятие решений, а не про сырой HTML и километровые JSON-выдачи. Это меняет масштаб задач, которые вы можете решить за одну сессию.
В следующих итерациях будем добавлять локальные лендинги по районам Москвы (по образцу svoydoctor.ru), категории сложности для хирургии в виде отдельных страниц и интеграцию с Яндекс.Услугами через структурированные данные. Но фундамент уже стоит, и теперь рост — это вопрос итерации, а не переделки всего с нуля.

AI-инженер, предприниматель, маркетолог. Основатель feberra.com и x10seo.ru. 13 лет в перфоманс-маркетинге, 3 года в системной интеграции AI в бизнес.