П/ВИН

Ветклиника официальный сайт Москва: 752 услуги и 365 статей

·9 мин чтения

Мой клиент 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]/ содержит:

  1. Intro-блок (генерится через Claude CLI на основе названия специалиста и списка его процедур) — 2–3 абзаца, отвечают на вопрос «зачем сюда идти и кто этот специалист».
  2. Прайс-таблица — все 50–115 позиций из pricing_item с реальными ценами. Это сильный коммерческий сигнал и для пользователя, и для Яндекса.
  3. FAQ-блок (тоже генерится CLI-скриптом) — 5–7 вопросов формата «Сколько стоит первичный приём?», «Делаете ли вызов на дом?», «Можно ли без записи?».
  4. JSON-LDMedicalBusiness + Service + FAQPage. Без этого структура не считывается агрегаторами вроде Яндекс.Услуг.
  5. Кросс-ссылки на блог — 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 в бизнес.