Перейти к содержанию

Кейс · Веб и SaaS, Скорость и инфраструктура

Сайт психолога блог для родителей, платный клуб и вебинары

Сайт психолога психоаналитического направления: статьи для родителей, тесты, вебинары и закрытый клуб по подписке. Мы починили кэш и поиск, подключили оплату через ЮKassa с проверкой каждого платежа и сделали вход без пароля.

100баллов Lighthouse на компьютере — у главной и у статьи
0,4 сдо первой отрисовки на компьютере
CLS 0вёрстка не сдвигается при загрузке — на компьютере и телефоне

Паспорт проекта

Отрасль
Психология
Срок
8 недель
Команда
Команда E404 и ИИ-агент

Стек

WordPressPHP 8.4MariaDBnginxЮKassaFlutter
Проект вживую ↗

Задача

Клиент — психолог психоаналитического направления: статьи для родителей о детях, тесты к семинарам, вебинары, консультации онлайн и очно. Аудитория — родители: читают статьи, проходят тесты, записываются на приём и оформляют подписку на закрытые статьи.

Сайт достался нам работающим, но с поломками:

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

Владелица просила заменить раздел «Семинары» вебинарами — с обложкой у каждого видео, одной ценой за курс и статусом «Скоро». А ещё — реакции и просмотры под статьями, страницу «Обо мне», которую можно заполнять самой, а затем — продажу вебинаров и подписки на закрытые статьи.

Ограничения. Тема сторонняя и обновляется, поэтому её файлы мы не правим: всё своё живёт слоями поверх неё.

Стенда у сайта нет: каждая правка выкладывается сразу на живой сайт — с копией прежних файлов и проверкой перед заменой.

Что сделали

Блог, в котором можно найти нужное

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

Страница «Блог»: строки фильтра «Доступ», «Тема», «Возраст», счётчик «43 материала», первая карточка с пометкой «Клуб»
Фильтр по доступу, теме и возрасту ребёнка — над лентой
Фильтр в работе: выбрали возраст «2–3» — из 43 материалов осталось 18

Статья, после которой есть куда идти

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

Конец статьи: карточка события «Язык прикосновений» с пометкой «Прошедшее событие», плашка «Разобрать вашу ситуацию отдельно» с кнопкой «Записаться», блок «Оцените материал»
После текста — событие по теме и предложение разобрать свою ситуацию

Закрытый клуб и вход без пароля

Путь читателя к закрытой статье — три шага:

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

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

Вход без пароля: из закрытой статьи — к коду на почту

Вебинары с видео, которое нельзя переслать ссылкой

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

Вебинар «Основы психического развития младенца»: обложка, плашки «Откроется 1 декабря 2026», «2 часа», «2 урока», «3 790 ₽», ниже описание
Дата открытия, длительность, число уроков и цена — над описанием

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

Блок «Уроки» в редакторе вебинара: название урока, выбор файла из закрытого каталога и кнопка «Загрузить видео», обложка урока и описание
Урок в редакторе: файл — из закрытого каталога, пересланная ссылка ничего не откроет

PDF к статьям и клубу

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

Блок «Материалы для скачивания» в редакторе статьи: название «Памятка: правила в семье», файл PDF, обложка; справа — метка доступа «Бесплатно»
Блок PDF в редакторе статьи — тот же, что у вебинара
Конец статьи: карточка «Материал для скачивания. Памятка: правила в семье. PDF, 147 КБ» с обложкой и кнопкой «Скачать», ниже плашка «Разобрать вашу ситуацию отдельно»
После текста — карточка материала с обложкой и кнопкой «Скачать»

Тесты с разбором

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

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

Консультации и запись

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

Страница «Консультации»: «3 000 ₽ за встречу», «50 минут · онлайн или очно», раскрывающиеся описания двух форматов, блок «Записаться на консультацию»
Цена, длительность и форматы — из админки, без правки кода
Страница «Блог» на телефоне с выбранным фильтром «2–3» и счётчиком «18 материалов»Блок подписки на телефоне: замок, «Дальше — для подписчиков», «199 ₽ за месяц», кнопки входаРезультат теста на телефоне: иллюстрация, кнопки соцсетей, заголовок стиля и начало разбора
На телефоне: фильтр блога, подписка клуба и результат теста

Приложение для iPhone и Android

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

Экран «Блог» в приложении: фильтры «Все», «Статьи», «Клуб», «Тесты», карточка материала клуба с замкомМатериал клуба в приложении: конец отрывка и блок «Дальше — для участников клуба» с кнопкой «Войти»Окно «Вход» в приложении: «Регистрация не нужна», кнопки «Вход с Apple» и «Войти по почте»
Приложение: лента блога, закрытый материал клуба без цен, вход через Apple или по почте

Как устроено

Схема: читатель, приложение и владелица обращаются к веб-серверу nginx. Готовую страницу он берёт из кэша, вход, оплату и новые страницы передаёт WordPress, видео и PDF отдаёт из закрытого хранилища после проверки доступа. WordPress работает с базой MariaDB, ЮKassa, IndexNow и входом через VK ID, Яндекс ID или код из письма.
Как устроен сайт: что отдаёт кэш, что — WordPress и куда уходят данные

Сайт — четыре слоя. Внизу сторонняя тема, её не трогаем. Над ней — модули оформления отдельных страниц из поставки темы. Дальше наши 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 на весь просмотр. Тот, кто купил, урок посмотрит; тот, кому переслали ссылку, — нет.

Страница «Файлы уроков и PDF» в админке: пояснение о закрытом хранилище, загрузка файла, таблица из двух видео и двух PDF с размерами и датами
Закрытое хранилище в админке: видео уроков и PDF одним списком

Результат

Замер в октябре 2026 — Lighthouse 12.8.2, с компьютера команды:

Замер Компьютер Телефон
Оценка Lighthouse, главная 100 99
Оценка Lighthouse, статья 100 97
Первая отрисовка (FCP), главная 0,4 с 1,1 с
Сдвиг вёрстки (CLS) 0 0
Отчёт Lighthouse для главной на компьютере: оценка производительности 100, First Contentful Paint 0,4 с, Largest Contentful Paint 0,5 с, Total Blocking Time 0 мс, CLS 0
Lighthouse, главная на компьютере: 100 из 100, первая отрисовка за 0,4 с

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

Открыть сайт

Комментарии

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

Поделиться записью

TGVK