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

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

На старте проект выглядел гораздо скромнее, чем сейчас. MVP можно было описать одной строкой:

регистрация => профиль продавца => создание товара => карточка => контакт с продавцом.

На разработку в итоге ушёл примерно год. За это время простая форма добавления товара превратилась в многошаговый конструктор с черновиками и предпросмотром, появилась модерация, затем повторная модерация изменений, realtime-чат, уведомления, аналитика для продавцов и отдельная инфраструктура вокруг проекта.

По дороге мы ещё и полностью поменяли первоначальную схему монетизации.

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

Почему Next.js

Frontend написан на Next.js и TypeScript. Для локального состояния используем Zustand, для серверных данных TanStack Query.

Next.js выбирали в первую очередь из-за SSR и SEO. Для нас было важно, чтобы карточки продуктов и другие публичные страницы нормально индексировались поисковиками. Для маркетплейса органический трафик потенциально играет большую роль, поэтому делать всё как обычное клиентское SPA не хотелось.

Со временем frontend довольно естественно разделился на две части.

Есть публичная зона: каталог, поиск, карточки продуктов, страницы продавцов. Здесь важны SSR, метаданные, индексация и скорость первого открытия страницы.

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

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

Авторизация и куки

С авторизацией мы провозились заметно дольше, чем предполагали.

Не с формой входа как таковой, она как раз была самой понятной частью. Основные нюансы появились из-за SSR и работы с cookies.

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

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

То есть задача из условного «сделать логин» довольно быстро становится общей частью архитектуры приложения.

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

Но гораздо больше времени у нас в итоге съела другая задача — форма создания продукта.

Форма добавления товара, которая перестала быть просто формой

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

Можно представить четыре поля: название, описание, цена, картинка.

Для готового сайта этого оказалось недостаточно почти сразу.

Если в каталоге просто написать "Корпоративный сайт, 50 000 ₽", покупателю всё равно мало что понятно. Ему нужно знать стек, наличие CMS или собственной админки, интеграции, условия передачи проекта, адаптивность, SEO, поддержку после продажи и ещё довольно много вещей.

Мы постепенно добавляли эти данные, и форма начала расти.

Сейчас она разделена на четыре этапа:

Описание, Параметры, Медиа, Цена.

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

Основной объём начинается на следующем этапе.

Параметры готового сайта

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

Поэтому появился набор структурированных характеристик.

Сначала продавец указывает, что именно он предлагает: готовый сайт, дизайн или разработку под задачу. Затем выбирает технологии, на которых построен проект.

Дальше идут интеграции и управление контентом. Можно указать CRM, платежные системы, аналитику, формы, карты, email-сервисы и другие интеграции, а также используемую CMS или тип административной панели.

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

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

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

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

Черновики и предпросмотр

Когда форма стала большой, оставлять её одной длинной страницей уже не хотелось. Поэтому мы разделили заполнение на шаги.

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

Так появились черновики.

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

Для пользователя функция довольно обычная. Для frontend она добавляет ещё одно состояние товара.

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

А после появления редактирования опубликованных товаров схема стала ещё сложнее.

Модерация и редактирование опубликованных товаров

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

Самая интересная часть появилась позже.

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

Поэтому изменения опубликованных продуктов тоже отправляются на проверку.

Получается примерно такой жизненный цикл:

черновик → модерация → публикация → редактирование → повторная модерация → обновлённая версия.

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

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

Работа с медиа

Для готового сайта одной обложки обычно недостаточно.

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

У каждого типа своя роль. Превью должно нормально выглядеть в каталоге, скриншоты собираются в галерею, видео помогает показать проект в динамике, а ссылка позволяет пользователю вообще открыть работающую демонстрацию.

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

Цена и дополнительные услуги

Первоначально стоимость товара тоже казалась очень простой частью.

Есть сайт — есть цена.

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

Поэтому продавец может добавлять к продукту дополнительные услуги.

Услугу можно создать вручную: написать название и описание, указать стоимость и дополнительный срок.

Но многие услуги повторяются. Установка на хостинг, домен и SSL, дополнительная страница, аналитика это довольно типовые вещи.

Для них мы сделали шаблоны.

Продавец выбирает готовый вариант и уже под себя меняет стоимость или срок.

Покупатель на карточке продукта может отметить нужные услуги и сразу увидеть изменившуюся итоговую цену.

Как всё это выглядит со стороны покупателя

Здесь для нас было важно не перенести огромную форму продавца один в один в интерфейс покупателя.

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

Подробные характеристики находятся ниже. Там уже можно посмотреть CMS, демо, лицензионные условия, формат передачи, SEO и остальные технические параметры.

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

И примерно в этот момент возник ещё один вопрос: как вообще должна проходить сама покупка?

Мы не хотели превращаться ещё и в финтех

Изначально модель была другой.

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

Для маркетплейса схема выглядит совершенно логично.

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

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

А каталог, модерация, чат и весь остальной продукт при этом никуда не исчезают.

В итоге мы решили, что не хотим одновременно строить площадку для готовых сайтов и ещё один финтех-проект.

Поэтому от комиссии с продажи самого сайта отказались.

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

Платформа зарабатывает на собственных услугах: публикации и продвижении товаров.

Это решение заодно довольно сильно поменяло сам продукт.

Если бы вся сделка проходила внутри площадки, главным действием была бы кнопка «Купить». У нас же ключевым действием стал контакт покупателя с продавцом.

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

Realtime-чат

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

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

Последний вариант нам особенно пригодился. Если пользователь открывает продукт и нажимает "Связаться по поводу товара", продавец сразу понимает, о каком решении идёт речь.

Не нужно начинать каждую переписку с уточнения контекста.

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

Поиск

С каталогом довольно быстро возникает проблема поиска.

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

Поэтому поиск использует не только название товара, но и другие основные данные: категории, описание и связанную с продуктом информацию.

Пользователь далеко не всегда знает точное название решения. Он скорее будет искать «сайт для школы», «готовый интернет-магазин» или что-то под конкретную задачу.

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

Что получает продавец после публикации

На старте личный кабинет легко представить как страницу "Мои продкуты".

Но продавцу мало знать, что продукт опубликован.

Хочется понимать, смотрит его кто-нибудь или нет и появляются ли после этих просмотров обращения.

Поэтому в кабинете есть сводка по продуктам, просмотрам, чатам, рейтингу и последним обращениям.

Отдельно сделали аналитику.

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

Одним из основных показателей для нас стала конверсия просмотра в контакт.

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

Поэтому гораздо интереснее смотреть, какая доля людей после просмотра карточки переходит к диалогу.

Продвижение вместо комиссии

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

Мы решили зарабатывать на тех возможностях, которые предоставляет сама площадка.

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

Нам такая схема в итоге подошла лучше первоначальной.

ComfyMarket не становится стороной сделки и не вмешивается в расчёты между продавцом и покупателем. Мы отвечаем за саму площадку и инструменты, которые она даёт продавцу.

Как вокруг сайта начинает появляться инфраструктура

Примерно за год поменялся и тип проблем, которыми приходится заниматься.

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

В production появляются уже другие вещи.

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

Сейчас релиз верхнеуровнево выглядит так:

Git → сборка → Docker → staging → production.

Вокруг проекта появились резервное копирование, firewall, HTTPS, VPN-доступ к отдельным внутренним сервисам, Fail2Ban, ограничение публичных портов, дополнительная защита административных интерфейсов и проверка загружаемых файлов.

Сам пользователь большую часть этих вещей никогда не увидит. И это, наверное, хороший признак.

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

Именно на этом этапе мы сами перестали воспринимать нашу площадку как большой пет-проект.

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

А потом в какой-то момент смотришь на проект совсем иначе. У тебя уже есть staging и production, несколько сервисов, мониторинг в Grafana, контейнеры, резервные копии, realtime-чат, пользователи, модерация. Всё это связано между собой и действительно работает.

Помню ощущение, когда я смотрел на всё это и думал: "Блин, а ведь у нас уже получился настоящий production-проект". Причём особенно приятно было понимать, что ещё год назад большей части этой системы просто не существовало.

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

Что в итоге оказалось самым сложным

Если бы в начале разработки меня спросили, какая часть frontend займёт больше всего времени, я бы вряд ли ответил «форма создания товара».

Но сейчас она точно была бы среди первых вариантов.

Не из-за количества полей. Основная сложность появилась из связей между ними и из состояний самого продукта.

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

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

Даже обычная кнопка «Редактировать» в таком проекте может означать намного больше, чем видно на экране.

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

Сейчас при проектировании новой функции я намного чаще сначала думаю именно об этом.

Что дальше

Площадка уже запущена.

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

Команда у нас небольшая: frontend-разработчик, backend-разработчик, DevOps-инженер и дизайнер.

Но теперь начинается этап, который сложно нормально смоделировать во время разработки, — работа с реальными пользователями.

До запуска почти любое продуктовое решение можно долго обсуждать внутри команды и в итоге решить, что «так будет удобнее».

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

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

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

Через год я скорее описал бы его другими словами: состояния, права, модерация, поиск, коммуникации, аналитика и инфраструктура.

Самих страниц может быть не так уж много. Основная сложность появляется между ними.

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