javascript

Веб-приложение без фронтенд-фреймворков в эпоху AI-поиска: личный опыт

  • понедельник, 29 декабря 2025 г. в 00:00:07
https://habr.com/ru/articles/981284/

Всем привет! За последние годы фронтенд-разработка прочно ассоциируется с фреймворками, сборщиками и всё более тяжёлыми клиентскими приложениями. Параллельно с этим поисковые системы начали активно внедрять AI-ответы, меняя саму модель потребления контента. В этой статье я хочу рассказать о своём опыте разработки веб-приложения без фронтенд-фреймворков - зачем я сознательно пошёл против тренда, какие технические решения это за собой потянуло и как всё это неожиданно пересеклось с эпохой AI-поиска.

Почему я не понимаю React, Angular и прочие Vue.JS?

С JS-фреймворками я плотно поработал примерно пять лет назад, после чего сформировал для себя определённое мнение и к этой теме больше не возвращался. Разумеется, за это время многое могло измениться — я это осознаю и не претендую на универсальность выводов.

Речь не о том, что я не понимаю, как фреймворки устроены или как ими пользоваться. Скорее, это то самое интуитивное ощущение, знакомое многим разработчикам: когда код формально работает, но ты понимаешь, что при определённых условиях он может сломаться. Возможно, это редкий race condition - например, при одновременной записи в таблицу с уникальными индексами. Возможно, проблема проявится только под нагрузкой, до которой проект ещё даже не дорос. И ты оставляешь всё как есть - просто потому, что сейчас «достаточно хорошо». Но при этом понимаешь, что решение получилось хрупким и рано или поздно даст о себе знать. Примерно такое же чувство у меня возникает при использовании JS-фреймворков.

Если упростить, сама концепция выглядит несколько костыльной: в итоге мы получаем JS-код, который после загрузки вручную рендерит HTML (если не рассматривать серверный рендеринг). Возникает ощущение, что эта идея противоречит базовым принципам веб-страниц. Как будто что-то фундаментально не так. При этом на старте всё выглядит очень эффектно: реактивность, декларативное описание интерфейсов, удобная логика построения view - всё это действительно впечатляет. Но, по моему опыту, лишь до определённого момента.

Единственное, что лично мне не очень нравится со старта, так это практика написания view и логики в одном файле. Как по мне - не очень удобно.

При этом я хорошо понимаю, что SPA - оптимальное решение для целого класса задач. Если веб-приложение не реализует ключевые свойства классического сайта - связную структуру документов и навигацию, а выступает именно как инструмент (графический редактор, музыкальный секвенсор, сложная интерактивная панель), то JS-фреймворки, на мой взгляд, являются лучшим и самым логичным выбором.

Я по образованию и профессии врач, но увлекаюсь программированием и веб-разработкой уже почти 20 лет. Я застал эпоху, когда многое, что мы решаем сейчас при помощи:

element {
  transition: opacity .2s ease-in-out;
  ....
}

Решалось исключительно в подавляющем большинстве случаев так:

<embed src="awesome_effect.swf"  height="800px" width="600px">

Понимаете, о чём я? SPA мне напоминает FLASH-приложения в браузере. Кто пробовал делать при помощи этой технологии сайты, тот помнит, как неудобно было, например, реализовывать историю просмотра - нужно было самостоятельно с ней работать, просто чтобы реализовать адекватное поведение при нажатии на кнопки "назад" и "вперёд" в браузере. Но браузеры уже умеют это из коробки, ведь так? Они умеют кэшировать страницы и куски страницы, при переходе между ними, берут одинаковые куски из кэша сами по себе, не нужно об этом беспокоиться. SPA частично возвращают эту головную боль, к сожалению.

Представьте, вы создаёте пустой проект на Vue.js и собираете его - и что же? Файл app.js весит почти мегабайт! Да, есть gzip, оптимизация зависимостей и прочие вещи, но для пустого проекта это выглядит чрезмерно, согласитесь.

Вспомните времена FLASH, когда текстовый контент сайта показывался поисковикам, а пользователи видели только embed SWF. Сейчас вопрос актуален для SPA, по моим наблюдениям, поисковики их любят. Конечно, серверный рендеринг помогает, но это снова дополнительная работа над тем, с чем обычные веб-страницы справляются сами по себе. А игнорирование этого на этапе проектирования может обернуться полной переделкой приложения.

В результате мы безусловно получаем ускорение разработки, но платим за это чрезмерно раздутым, на мой взгляд, билдом и возможными проблемами с SEO. Похоже на некий компромисс с самим собой - когда написал не очень, но оно работает, и ты оставляешь как есть, чтобы не тратить время в текущем моменте.

Совместимость из коробки - о дивный новый мир настал

Помните jQuery? Он был хорош в плане тотальной совместимости - с ним нам не нужно было заморачиваться с отдельной поддержкой InternetExplorer 8, например! И это было удобно. Но сейчас выглядит излишним.

К тому же сейчас у нас есть возможность писать на JS, используя "классы". Это не требует фреймворка, хотя требует сборщика. Но он собирает исключительно то, что вы сами попросите. Кстати, на мой взгляд, сама идея кажется странной. Мы пишем на современном JS, чтобы потом при помощи того же JS превратить код в один файл старого образца, который понимают браузеры. Но это работает - прощайте заморочки с совместимостью между браузерами (почти)! Тоже самое применимо и к стилям - ведь CSS мы тоже собираем таким же образом. И это позволяет отказаться от CSS-фреймворков (не уверен, вено ли называть фреймворком по сути просто набор стилей), поэтому прощай Bootstrap!

Раньше Bootstrap действительно быстро и эффективно решал задачи совместимости и компоновки интерфейса: одна и та же страница начинала адекватно выглядеть на устройствах с разными экранами.

Фактически, современные CSS-препроцессоры и инструменты сборки почти полностью снимают необходимость в использовании CSS-библиотек. Сейчас стили писать легко: есть богатый набор свойств и функций для алгоритмизации, свойства и функции прямо в CSS,flex, grid, calc, clamp и другие крутые штуки. Всё это собирается в компактный, совместимый с браузерами минифицированный билд. Вопрос компоновки интерфейса решается значительно проще, чем раньше.

Разумеется, при желании можно подключать тот же Bootstrap очень точечно, вытаскивая только нужные части и избегая раздутия билда. Я же решил отказаться от CSS-фреймворков скорее из «спортивного интереса». При этом стоит отметить, что они создают куда меньше проблем, чем JS-фреймворки. Просто если есть лишние 5-7 дней на аккуратную проработку стилей, то в большинстве случаев CSS-фреймворки становятся не обязательны.

Пара слов о проекте - истоки и история

Изначально это было приложение для подготовки к тестам, которое я делал исключительно для себя, ещё будучи студентом. В медицинском вузе на каждой сессии сначала нужно сдать тестирование, и только потом допускают к устному экзамену. При этом сами тесты часто были, мягко говоря, своеобразные: неоднозначные формулировки, большое количество вопросов и необходимость буквально заучивать ответы. Списки вопросов с правильными вариантами обычно удавалось достать, но читать и запоминать их было утомительно и малоэффективно.

Особенно запомнился тест по психиатрии на пятом курсе. В некоторых вопросах было по 7–9 вариантов ответов, из которых верными могли быть от одного до восьми. Даже понимая тему, легко было ошибиться: не заметить вариант, не дочитать его в спешке или просто промахнуться. Ситуацию усугубляло и то, что тест сдавался на компах под MS-dos в программе АИСТ (или АИССТ - сейчас уже не вспомню, а гуглёж не помог). Ответы нужно было вводить вручную, через пробел, и подтверждать нажатием на Enter. Банальный миссклик вполне мог стоить правильного ответа.

Я сделал небольшой эмулятор: простой сайт, который показывает вопрос, варианты ответов и ждёт точного ввода. На реализацию ушёл буквально один вечер.

Я добавил жёсткое правило: следующий вопрос не появляется, пока текущий не будет отвечен правильно. Сначала это выглядело сомнительно, но после нескольких прогонов эффект оказался неожиданно сильным - уже со второго прохода ответы вспоминались почти автоматически, без долгих раздумий.

С педагогической точки зрения подход, возможно, спорный, но как инструмент быстрого закрепления материала он сработал удивительно эффективно.

Скинул ссылку в учебную группу — все без проблем сдали тест. Потом появились тесты по акушерству, офтальмологии и другим предметам. Я просто добавлял их по мере необходимости, и проект начал жить своей жизнью.

Через пару лет я расширил сайт за пределы медицины, слегка приукрасил внешний вид и оставил его в свободном плавании, оплачивая лишь домен и хостинг. Со временем выяснилось, что для поделки, собранной за несколько вечеров, трафик у сайта неожиданно высокий: стали присылать тесты из других вузов, пришлось переделывать всё «по-взрослому». Проект рос, стабильно работал, приносил небольшой доход с рекламы (которую, к слову, легко заблокировать).

Позже были апдейты с поддержкой изображений в тестах и новые переделки. 4 ноября этого года случился очередной ремейк: он занял больше года неспешной работы.

Сейчас реализованы следующие основные функции:

  • Каталог тестов по различным дисциплинам с иерархией и поиском по тестам, вопросам и статьям.

  • Несколько режимов прохождения тестов (обучающий, контрольный).

  • Поддержка вопросов разных типов - выбор верных, последовательность, соответствие и открытый вопрос.

  • Вопросы и ответы с разметкой и изображениями.

  • Сохранение прогресса прохождения.

  • Работа без авторизации (для быстрого старта) и с аккаунтами.

  • Редактор тестов и статей.

  • Всякие социальные штуки - лайк/дизлайк, комменты, лидерство в тесте и т.д.

Проект продолжает развиваться, в основном тогда, когда у меня самого появляется новая практическая потребность или желание что-то переделать, как давно хотелось.

Интересное при разработке - мои велосипеды

Здесь я фокусируюсь именно на фронте - на серверной стороне у меня нет ничего экзотического. Обычный Laravel, который крутится под Apache на Ubuntu. Как раз на бэкенде я активно использую готовые решения: где-то библиотеки, где-то целиком стороннее бесплатное ПО. Например, для конвертации электронных книг, обработки и ресайза изображений и других утилитарных задач.

Изначально на фронте я ставил себе ограничение: никаких фреймворков и сторонних библиотек. На практике оно выполнено частично - сейчас в проекте используются axios и pusher.js.

Лично я считаю axios избыточным: для большинства задач достаточно простой обёртки над fetch. Но пока оставил как есть - возможно, переделаю в будущем. От pusher.js отказаться сложнее, но тоже возможно.

Сборку проекта я делаю через Laravel Vite. Ниже показано, как подключение этих библиотек влияет на размер JS-билда (с gzip):

Сценарий сборки

Размер JS-билда

axios

pusher.js

Прирост к базовому, %

Без сторонних библиотек

37.61 kB

-

-

-

Только axios

51.06 kB

+ 13.46 kB

-

+35.8%

Только pusher.js

56.40 kB

-

+ 18.8 kB

+49.9%

axios + pusher.js

69.83 kB

+ 13.46 kB

+ 18.8 kB

+85.7%

Мы видим, что даже небольшие библиотеки значительно увеличивают размер билда - при том, что они решают простейшие задачи, вроде отправки запроса или приёма уведомлений. В нашем случае их добавление увеличивает билд почти на 85%.

А что будет, если понадобится реализовать что-то более «экзотическое»? Цифры только вырастут.

Я не хочу сказать, что всё всегда нужно писать самому. Речь о том, что многие привычные задачи, для которых мы обычно тянем библиотеки, уже доступны из коробки в браузере. Например, слайдер или карусель можно сделать полностью нативно, без сторонних зависимостей.

Велосипед #1 - карусель на скролле

HTML5 не предоставляет готового элемента для карусели, но всё необходимое уже есть: overflow-x. Идея проста: контейнер слайдов может располагаться где угодно, браузер сам вычисляет позиционирование элементов.

Для прокрутки используем scrollTo() — нативная анимация делает движение плавным и привычным, как обычный скролл.

Разметка проста:

<div class="slider">
  <div class="slider-inner">
    <div/>
    <div/>
    <div/>
  </div>
</div>

Минимальные стили:

.slider{//помещаем контейнер карусели в любое место
    overflow: hidden;
}
.slider-inner{//этот контейнер нужен для скролла
    overflow:scroll;
    display: flex;
    width: 100%;
    max-width: 100%;
    scroll-behavior: smooth;
    position: relative;
    gap: .5rem;
    scroll-snap-type: x mandatory;//нужно для ручного управления об этом чуть позже
}
.slider-inner > *{
    //любой прямой потомок будет занимать всю ширину .slider-inner
    min-width: 100%!important;
    height: min-content;
    scroll-snap-align: center;//нужно для ручного управления об этом чуть позже
}

достаточно написать минимальную навигацию

next(slider){
  //текущая позиция хранится, например, в dataset
  const next = Number.parseInt(slider.dataset.current)+1;
  Slider.to(slider,next);
}
prev(slider){
  //текущая позиция хранится, например, в dataset
  const prev = Number.parseInt(slider.dataset.current)-1;
  Slider.to(slider,prev);
}
to(slider,to){
  //получаем слайды - то есть дочерние элементы контейнера
  const slides = slider.children;
  /*
  вычисляем позицию, если to пришло из next/prev
  проводится проверка на выход to за возможные границы
  конкретно в данном случае выполняется переход от первого слайда к последнему
  при prev на первом слайде и наоборот
  */
  to = (to + slides.length) % slides.length;
  //И вот она магия - переходим на нужный слайд быстро и изящно
  slider.scrollTo(slides[to].offsetLeft,0);
}

Всё. Навигация сводится к функциям next, prev и to, где передаём ссылку на элемент и номер слайда.

Из коробки доступно управление жестами: браузеры корректно обрабатывают свайпы и прокрутку с трекпада. scroll-snap-type у контейнера и scroll-snap-align у детей обеспечивают остановку слайда по центру и плавное поведение на всех устройствах.

На моём сайте подобный слайдер используется во время тестирования - вопросы помещаются в slider-inner и скроллятся при вводе ответа.

пример нативного скролла
пример нативного скролла

Можно расширять - поддержка разных высот слайдов, смена current при ручном скролле и т.д. - но суть остаётся той же: простой, нативный и полностью рабочий слайдер без сторонних библиотек.

Велосипед #2 - динамическая загрузка изображения оптимального размера

Все помнят, как в 2018–2020-х годах Google начал развивать систему оценки удобства веб-страниц - хотя сама она существовала и раньше. С 2021 года эти показатели, фактически, стали влиять на выдачу.

Но речь не только о ранжировании. Метрики Core Web Vitals несут в себе полезный смысл: как пользователю, мне гораздо комфортнее использовать сайты, где они соблюдаются. Поэтому, как разработчик, я стараюсь их учитывать, особенно CLS (Cumulative Layout Shift) — то самое смещение макета о котором у гугла есть очень наглядное видео.

Основной источник смещения контента - это картинки. Они загружаются сверху вниз и во время загрузки смещают элементы под собой.

Изображения также критичны для оценки скорости загрузки: важно, чтобы фактический размер картинки совпадал с областью, которую она занимает на странице. Если картинка больше области, возникает излишняя нагрузка на сеть. Если меньше и её растягивают - изображение становится размытым.

Ситуацию усложняет большой разброс размеров экранов современных устройств. Простое и эффективное решение - использовать <picture> с несколькими вариантами изображения разных размеров. Браузер автоматически выберет подходящий файл в зависимости от размера экрана или контейнера.

Проверить можно - хром, например, показывает размеры оригинала, отрендеренные размеры и выбранный файл:

Просмотр характеристик выбранного изображения в Chromе

Разметка для показа этого изображения:

<picture style="aspect-ratio: 1.5;
                background: #688399;
                max-width: 1200px;">
  <source srcset="/xs_6938018200ac7.jpg" media="(max-width: 260px)">
  <source srcset="/sm_6938018200ac7.jpg" media="(max-width: 500px)">
  <source srcset="/md_6938018200ac7.jpg" media="(max-width: 740px)">
  <source srcset="/lg_6938018200ac7.jpg" media="(max-width: 900px)">
  <source srcset="/xl_6938018200ac7.jpg" media="(max-width: 1200px)">
  <img 
       class="image"
       style="aspect-ratio:1.5;"
       src="/xl_6938018200ac7.jpg"
       alt="awesome pic"
       fetchpriority="high">
</picture>

Сложность в том, что для реализации такого поведения нужно очень точно определять поведение в момент загрузки изображения на сервер - правильно нарезать и обрабатывать. Считаю строгую обработку картинок при загрузке на сервер всегда обязательной. Для себя я определил примерно следующий алгоритм:

  1. Определяем сущности, у которых могут быть изображения - статья, тест, вопрос, юзер и т.д. Для MVC-паттерна сущность - это модель.

  2. Измеряем места отображения — в интерфейсе определяем ширину блоков, где будут показываться изображения, и формируем массив значений ширины для каждой сущности.
    Например, аватар на экране никогда не превышает 500px, значит, нет смысла загружать изображение больше этого размера. А на мобильных экранах ава размером 200px - есть смысл хранить уменьшенный вариант.

  3. Определяем поведение при несоответствии пропорций — для каждой сущности оставлять как есть, подгонять под нужные размеры или кадрировать.

  4. Обработка при загрузке — нарезаем изображение под нужные размеры, сохраняем получившееся соотношение сторон (оно понадобится для предотвращения смещений) и усреднённый цвет картинки, чтобы показывать заглушку похожего оттенка до полной загрузки изображения.

  5. Сохраняем максимальную ширину картинки - если исходник меньше, чем запланированный максимальный размер в интерфейсе - нам нужно будет точно указать значение ширины, чтобы браузер смог посчитать высоту на основании aspect-ratio.

После того, как все эти данные подсчитаны и записаны, на фронте не составит труда красиво показать картинку. Используя aspect-ratio, браузер автоматически резервирует место под неё, исключая смещение, а srcset обеспечивает выбор изображения оптимального размера. На сервере определяем первую/важную/самую большую картинку и добавляем ей атрибут fetchpriority="high" , для всех остальных - loading="lazy" - они будут загружены, только если до них доскроллят.

Обрабатываем картинки при загрузке страницы:

image.classList.add('loading');//либо добавляем класс ещё на сервере
image.addEventListener('load',()=>{
    image.classList.remove('loading');
});

И для классов image и loading пишем стили типа таких:

.image{
    filter: none;
    width: 100%;
    margin: 0 auto;
    display: block;
    transition: opacity .2s ease-in-out,filter .2s ease-in-out;
}
.image.loading, .image.error{
    opacity: 0;//фон под картинкой будет совпадать с её усреднённым цветом, который мы вычислили на этапе загрузки изображения
    filter: blur(3);//как один из вариантов превью - размытие самой маленькой версии изображения
}

Минимальные трудозатраты, но по моим субъективным наблюдениям, они позволяют избежать более половины потерь баллов при оценке Core Web Vitals. И ещё раз: основная работа проводится на сервере, на фронте всё работает практически из коробки.

Велосипед #3 - редактор статей

Вот это, пожалуй, самая сложная часть всего проекта. Реализация WYSIWYG-редактора контента заняла больше половины времени разработки и я до сих пор сомневаюсь в том, надо ли оно было. Дело в том, что когда у нас есть contenteditable="true" , задача кажется простой на первый взгляд. И, когда я начал гуглить, как что лучше реализовать, то гугл кричал мне, что следует использовать готовое решение. Думаю, это был правильный совет... Тем не менее, что-то получилось.

Осложняется всё тем, что в разных браузерах поведение внутри элемента с contenteditable="true" сильно отличается. Например при нажатии на Enter хром создаёт новый div, а firefox вроде бы <br> - я уже даже не помню. Несмотря на кажущуюся простоту и богатый выбор инструментов, многое следует делать вручную - начиная от обработки нажатия на ввод. Например:

  • при нажатии Enter внутри блока следует взять всю часть контента блока справа от каретки, перенести её в новый блок и поместить его после текущего

  • если блок это список, добавление нового элемента списка; если последний элемент пуст, создание стандартного блока с кареткой;

  • если выделен текст, его нужно удалить и корректно обработать вставку;

  • отдельная логика для Backspace и Delete в зависимости от контекста;

  • и множество других условий и комбинаций.

Чтобы упростить задачу, я отказался от одного большого contenteditable и сделал множество маленьких элементов, внутри которых происходит редактирование. Это позволило избежать ситуаций, когда выделение охватывает блоки разных типов, и сильно облегчило обработку событий.

Range и Selection в JavaScript - это отдельный вид боли... По идее, они должны облегчить задачу форматирования, например, обёртку в <b> или <i> - но как я ни пытался это сделать, у меня ничего не получилось. То есть, в простой ситуации это просто - взял, обернул в теги. А вот если выделение начинается снаружи тега, а заканчивается внутри, например, так:

слово "означают" обёрнуто в <b>, выделение начинается снаружи <b> и заканчивается внутри
слово "означают" обёрнуто в <b>, выделение начинается снаружи <b> и заканчивается внутри

Короче, я использовал для форматирования execCommand - хоть везде пишут, что это deprecated, оно работает лучше, чем любая моя ручная реализация. Да что говорить, на хабре, если глянуть в билд, есть document.execCommand("copy") , например.

Есть забавная статья про contenteditable.

При этом реализацию создания ссылок и обёртки в <code> я сделал вручную - там не так много вариантов.

В итоге инструмент получился в целом рабочим, но работает он всё-таки с ошибками - время от времени неправильно обрабатывается нажатие на Backspace, да можно особо не напрягаясь, просто сломать редактирование так, что ничего нельзя будет ввести в редактор. Причём даже не специально. Нет обработки вставки текста с форматированием и вставки изображений из буфера обмена. Криво работает undo/redo. И так далее...

Разумеется, следует очень внимательно обрабатывать то, что введено пользователем - и на этапе подготовки к отправке на сервер, и на сервере. Там можно всё причесать и структурировать.

Как итог - опыт реализации редактора скорее отрицательный, и если вам следует это сделать, я бы рекомендовал готовое решение. Хотя, при наличии времени и желания, это тоже не обязательно.

Тем не менее, я использовал свой редактор статей и в редакторе тестов - в итоге вопросы и варианты ответов могут содержать в себе такой же контент, как и статьи: форматированный текст, изображения, код, списки и прочее. Раньше я этого почему-то не делал.

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

Я из тех людей, кто до последнего не признавал нейронки. Изменил всё случай - мне понадобилось нарисовать картинку по моему сюжету быстро и недорого в качестве эскиза для татухи, и художники при заданных ТЗ задачу (качесвенно, быстро, недорого) выполнить не смогли. И я подумал - почему бы и не поробовать. В итоге, всё получилось успешно. Так я осознал, что нейронки являются вполне удобным и эффективным инструментом.

Я не говорю сейчас про вайбкодинг, если честно, слабо себе это представляю. Но точечные задачи нейронки выполняют вполне хорошо - достаточно, например, адекватно назвать метод, чтобы автокомплит предложил уже готовый код. Правда, не всегда правильный, но это всё равно интересно. В общем, при написании кода мне не так часто пригождаются нейронки. Но есть ряд задач, с которыми они справляются на отлично!

Например, пишут точные SQL запросы - достаточно просто показать структуру, индексы и описать в двух словах, что требуется. Это работает в случае, если поля названы адекватно. Использовал при реализации вложенных комментариев и постраничной выборке для lazyloading. Сам тоже, написал бы конечно, но ушло бы явно больше времени.

Быстро делают картинки для обложек. Суть обложки в том, чтобы цеплять глаз и более или менее отражать суть того, что она оборачивает. Не столь важно, чтобы картинка теста, например, была художественно качественной. На неё никто не смотрит детально - она нужна по сути только для того, чтобы тест выделялся в выдаче. Нейрокартинок для этих целей более чем достаточно на мой взгляд.

Нейронки быстро делают описания, тексты и ключевые слова - в общем, те части страницы, которые никто не читает и которые опять же нужны только роботам. Роботы пишут для роботов.

Нейронки быстро и вполне приемлемо делают парсинг сырых тестов в нужный формат для автоматического добавления на сайт, но при большом объёме теста проще и надёжнее использовать поиск и замену по регуляркам, например, в notepad++ или вашей IDE.

Обратная сторона нейросетей

В этом году поисковики внедрили нейронки в поиск... И для моего сайта это колоссальный удар. И причём это произошло именно в тот год, когда я решил обновить сайт после 5-летнего простоя. Это интересное совпадение. Так или иначе - посетителей из поиска за текущую осень стало в 10 раз меньше, чем за осень прошлого года. Это при том, что функционал сайта подразумевает не просто просмотр и чтение, а непосредственную работу с сайтом. Вот сравнение количества прохождений тестов за 2024 и 2025 годы:

на момент 13 декабря 2025 года
на момент 13 декабря 2025 года

То есть мы видим, что традиционный подъём тестирований к концу года в связи с сессиями и экзаменами превратился в спуск. Хотя ещё летом наблюдался традиционный подъём, связанный с летней сессией. Одно совпало точно - в августе все отдыхают. И как хорошо, думаю я, что эта тенденция почти совпала с тем моментом, когда я сделал обновление - отличный шанс подстроиться к новым реалиям, остаётся добавлять контент и функционал. В том числе отказ от фреймворков на фронте - это шаг в сторону оптимизации.

Кстати сейчас, на момент написания статьи, зарегистрировано 815 057 прохождений тестов - статистику я начал собирать в марте 2015-го года. Когда я только запустил сайт, на нормальную индексацию ушло два года. Без рекламы, без постов, без продвижения. Но этого было достаточно - я был на 1-м месте по ключевым запросам. Сейчас само обновление пошатнуло позиции в связи с незначительным изменением структуры ссылок и удаления из индекса тысяч дублей и копий. Это отлично иллюстрируется скрином из Google Search Console:

Падение трафика из поисковой выдачи в 80 раз!
Падение трафика из поисковой выдачи в 80 раз!

Как меняется роль классических сайтов в эпоху AI-поиска на собственном опыте

Фактически мы наблюдаем сдвиг парадигмы. Раньше поисковик был посредником: он приводил пользователя на сайт, а дальше пользователь сам читал, сравнивал и делал выводы. Сейчас всё чаще ответ формируется прямо в выдаче - в виде краткой выжимки, сгенерированной нейросетью. Пользователь получает результат, даже не переходя на источник.

Для контентных сайтов это означает прямую потерю трафика. Особенно для тех, где основная ценность - текстовое объяснение, определения или списки фактов и прочие, как Хабр или StackOverflow. Посудите сами - давно ли вы реально заглядывали на StackOverflow?

Но в моём случае ситуация чуть сложнее. Проект изначально не был читалкой - он предполагает активное взаимодействие. Прохождение тестов, ввод ответов, работу с интерфейсом и прочие клики. Это не тот формат, который можно полностью заменить кратким ответом в поиске. Тем не менее, падение трафика показывает, что даже такие сайты серьёзно зависят от входящего поискового потока.

По сути, поисковики начинают забирать верхний слой ценности, оставляя сайтам только сложные или интерактивные сценарии.

Я уже принял тот факт, что органический поиск больше не является стабильным источником трафика, как раньше.

Вместо вывода

Этой статьёй мне хотелось сделать сразу несколько вещей.

Во-первых, поделиться личным опытом разработки достаточно крупного веб-приложения в одиночку, без фронтенд-фреймворков. Не как универсальным рецептом, а как примером альтернативного пути - с его плюсами, минусами и странными решениями.

Во-вторых, честно рассказать о проекте, который живёт уже больше десяти лет. Не как о стартапе мечты, а как о долгоживущем pet-проекте, который периодически переписывается, ломается, чинится и подстраивается под новые реалии - в том числе под изменения в поиске и экосистеме веба в целом.

И, наконец, мне действительно интересно услышать обратную связь.
Что в описанном подходе выглядит наивно или устаревше?
Где я, возможно, усложнил себе жизнь без веской причины?
Какие архитектурные или продуктовые решения стоило бы принять иначе, если смотреть на проект со стороны?

Я не претендую на истину и не пытаюсь доказать, что фреймворки - зло или что мой путь единственно верный. Скорее наоборот - это попытка зафиксировать опыт, который складывался годами, и понять, что в нём было удачным, а что - нет.

Если этот текст заставил кого-то задуматься, подискутировать или просто вспомнить собственные старые проекты - значит, он был написан не зря.

С уважением,
Сергей