Кейс · Веб и SaaS, Скорость и инфраструктура
Сайт психолога блог для родителей, платный клуб и вебинары
Сайт психолога психоаналитического направления: статьи для родителей, тесты, вебинары и закрытый клуб по подписке. Мы починили кэш и поиск, подключили оплату через ЮKassa с проверкой каждого платежа и сделали вход без пароля.
Паспорт проекта
- Отрасль
- Психология
- Срок
- 8 недель
- Команда
- Команда E404 и ИИ-агент
Стек

Задача
Клиент — психолог психоаналитического направления: статьи для родителей о детях, тесты к семинарам, вебинары, консультации онлайн и очно. Аудитория — родители: читают статьи, проходят тесты, записываются на приём и оформляют подписку на закрытые статьи.
Сайт достался нам работающим, но с поломками:
- раздел «Семинары» оказался чужим шаблоном из поставки темы — отсюда адреса вида
/seminars/creator/, а плагин вебинаров не использовался; - поиск не работал вовсе: тема отдаёт его сервису Google, который никто не настроил, и
/?s=…отвечал 404; - на каждой странице уходили пять запросов в никуда — к файлам, которых на сайте нет;
- кэш страниц был установлен, но не собирался: 59 задач висели в очереди, в кэше — ни одного файла.

Владелица просила заменить раздел «Семинары» вебинарами — с обложкой у каждого видео, одной ценой за курс и статусом «Скоро». А ещё — реакции и просмотры под статьями, страницу «Обо мне», которую можно заполнять самой, а затем — продажу вебинаров и подписки на закрытые статьи.
Ограничения. Тема сторонняя и обновляется, поэтому её файлы мы не правим: всё своё живёт слоями поверх неё.
Стенда у сайта нет: каждая правка выкладывается сразу на живой сайт — с копией прежних файлов и проверкой перед заменой.
Что сделали
Блог, в котором можно найти нужное
Статьи, материалы клуба и тесты идут одной лентой, у платного — пометка «Клуб». Над лентой три фильтра: доступ, тема и возраст ребёнка. Родитель выбирает «2–3» — и из 43 материалов остаются 18. Темы и возрасты владелица заводит сама, в админке. Поиску вернули штатный движок WordPress — теперь он находит статьи по запросу, а не отвечает 404.

Статья, после которой есть куда идти
Оформление мы собрали отдельным слоем по UI-kit поверх темы: шапка, лента, карточки, кнопки. У статьи — время чтения, просмотры и «Поделиться». После текста — событие или вебинар по теме: его выбирают в редакторе или сайт подбирает сам по справочнику тем. Если событие уже прошло, карточка не прячется, а получает пометку «Прошедшее событие». Ниже — «Разобрать вашу ситуацию отдельно» с кнопкой записи.

Закрытый клуб и вход без пароля
Путь читателя к закрытой статье — три шага:
- Читает начало статьи — оно открыто всем — и видит предложение подписки за 199 ₽ в месяц.
- Входит через VK ID, Яндекс или по коду из письма — паролей на сайте нет.
- Платит через ЮKassa — разово, без автопродления.

Отрывок режется по целым абзацам и никогда не больше половины статьи: аудит показал, что без этих ограничений «отрывком» могла уйти вся статья. Лента RSS тоже отдаёт только отрывок.
Вебинары с видео, которое нельзя переслать ссылкой
Вебинары стали своим типом записи: уроки с обложками, одна цена за курс, статус «Скоро» с датой открытия, PDF-материалы для купивших. Видео уроков лежат вне публичной части сайта и открываются только тому, у кого есть доступ. Старые адреса /seminars/… ведут на новые постоянной переадресацией.

В редакторе вебинара файл урока выбирается из закрытого каталога или загружается прямо в редакторе, там же — обложка и описание урока.

PDF к статьям и клубу
К статьям и материалам клуба можно прикреплять PDF — тем же блоком, что у вебинаров. PDF статьи скачивает любой читатель, PDF клуба — только участники; остальные видят название и обложку.


Тесты с разбором
Тест — вопросы по одному со счётчиком «1 / 20». Результат — не балл, а разбор: что получается хорошо, что мешает и что сделать уже сейчас. Каждым результатом можно поделиться — у него своя ссылка.

Консультации и запись
Цена, длительность, форматы и свободные часы владелица меняет на экране «Консультация» в админке — без правки кода. Форма записи своя: свободные часы в ней берутся из тех же настроек, а заявки приходят в раздел «Заявки» и на почту. Страница «Обо мне» тоже собирается из полей в админке: фото, текст, дипломы, события.




Приложение для iPhone и Android
Приложение собрано на Flutter и ждёт публикации в магазинах: та же лента, клуб, вебинары и тесты, учётки общие с сайтом. Оплата — только на сайте: на iPhone в приложении нет ни цен, ни оплаты, поэтому не нарушает правил App Store о продаже цифрового контента.



Как устроено

Сайт — четыре слоя. Внизу сторонняя тема, её не трогаем. Над ней — модули оформления отдельных страниц из поставки темы. Дальше наши mu-plugins: дизайн-слой, вход по почте, консультации, фильтр блога, блоки в статьях, поиск, заслоны REST и сброс кэша. Сверху — семь своих плагинов: вебинары, клуб, формы, SEO, просмотры, «Обо мне» и API для мобильного приложения.
Страницы отдаёт кэш прямо из файла, WordPress запускается только там, где без него никак: вход, оплата, новая страница. Расписание задач перенесли на системный cron.
Оплату подключили к API ЮKassa напрямую, без плагинов-посредников. Видео и PDF отдаёт nginx из закрытого каталога — только после того, как PHP проверит доступ.
От чего отказались. От переезда на другую тему: замер показал, что тема не тормозит сайт: без кэша страница собиралась за 72–81 мс, а переезд — большая и несрочная задача. От WooCommerce: ради подписки и вебинаров магазин не нужен. От сторонних плагинов SEO, форм и почты — заменили своими: формы теперь описаны данными, а настройки почты лежат в конфигурации сайта, а не в базе.
Инженерные решения
Кэш, который не собирался
Плагин кэша страниц был установлен, но в кэше не было ни одного файла, а в очереди висели 59 задач — каждая страница собиралась заново. Мы проследили путь задачи в очереди. Очередь плагин разбирает запросом к собственному маршруту REST, и куки в такой запрос не передаются — он приходит как гость. А тема закрывает REST для гостей, поэтому очередь не двигалась.
Открыть REST гостям целиком нельзя. Мы открыли ровно два маршрута очереди, которые и так защищены подписью плагина, и сверяем именно маршрут, а не подстроку адреса: подстроку можно дописать к любому запросу.
Заработавший кэш сломал бы расписание: штатный cron WordPress запускается попутно с визитами, а страница из кэша обходится без WordPress. Поэтому в тот же день расписание переехало на системный cron, раз в минуту. Сейчас страница из кэша отвечает примерно за 40 мс от установки соединения до первого байта, вместе с сетью.
Оплата, которой сайт не верит на слово
Старый плагин вебинаров выдавал доступ по параметру в адресе — такой адрес мог собрать кто угодно. Теперь доступ открывается только после оплаты, подтверждённой ЮKassa. Телу уведомления сайт не верит: по номеру платежа он сам запрашивает платёж у API и сверяет сумму с заказом. Так же сайт проверяет платёж, когда покупатель возвращается со страницы банка раньше уведомления.
// Платёж уже запрошен у API ЮKassa. Сверяем сумму: подменённый в браузере
// запрос на создание платежа не должен открыть доступ за меньшие деньги.
$paid = isset( $payment['amount']['value'] ) ? (float) $payment['amount']['value'] : 0.0;
$expected = (float) $purchase->amount;
if ( abs( $paid - $expected ) > 0.01 ) {
self::log( sprintf(
'Сумма не сошлась по покупке %d: ожидали %.2f, оплачено %.2f',
$purchase->id,
$expected,
$paid
) );
return false;
}
// Уведомление и возврат из банка могут прийти одновременно: строку сменит
// только один из них, и письмо о покупке уйдёт одно.
$done = $wpdb->query( $wpdb->prepare(
"UPDATE $table SET status = 'completed', payment_id = %s, payment_method = 'yookassa'
WHERE id = %d AND status <> 'completed'",
$payment_id !== '' ? $payment_id : (string) $purchase->payment_id,
(int) $purchase->id
) );
У клуба и вебинаров один магазин в ЮKassa и один адрес уведомлений, поэтому каждый раздел передаёт чужой платёж соседу. Приём оплаты проверили настоящим платежом и уведомлениями об оплате и отмене.
Видео, которое не перешлёшь ссылкой
Видео уроков из медиатеки открывались бы любому, у кого есть ссылка. Мы вынесли файлы за пределы публичной части сайта. Плеер берёт видео с адреса, где PHP на каждый запрос проверяет вход и доступ к вебинару и отвечает заголовком X-Accel-Redirect. Сам файл отдаёт nginx — с перемоткой и не занимая PHP на весь просмотр. Тот, кто купил, урок посмотрит; тот, кому переслали ссылку, — нет.

Результат
Замер в октябре 2026 — Lighthouse 12.8.2, с компьютера команды:
| Замер | Компьютер | Телефон |
|---|---|---|
| Оценка Lighthouse, главная | 100 | 99 |
| Оценка Lighthouse, статья | 100 | 97 |
| Первая отрисовка (FCP), главная | 0,4 с | 1,1 с |
| Сдвиг вёрстки (CLS) | 0 | 0 |

- Безопасность. Аудит безопасности и кода нашёл 56 проблем: одна критичная, шесть высоких, 19 средних, 30 низких. На следующий день закрыты все: 55 исправлены, одну оставили сознательно.
- Поиск в Яндексе и Google. У 42 страниц из 50 было одинаковое описание — теперь у каждой своё; у каждой страницы — один H1 и canonical. Адреса из карт сайта отправлены в IndexNow одним пакетом.
- Оплата. Приём платежей работает, подписку на клуб уже можно оформить, первый вебинар откроется в декабре 2026.
- Порядок в коде. Сторонние плагины SEO, форм и почты заменены своими, из базы вычищены 19 таблиц и 38 опций удалённых плагинов, в теме нет ни строчки нашего кода.
Поделиться записью



Комментарии
Писать можно, войдя через VK ID, через Яндекс ID или по коду из письма. У комментария — имя и фото из профиля VK или Яндекса либо имя, которое вы укажете при входе по почте. Как мы обращаемся с данными — в политике конфиденциальности.