Разработка сайта недвижимости на 1С-Битрикс
Сайт недвижимости — это каталог с одной особенностью: пользователь не листает товары, а ищет место для жизни. Средний посетитель проводит 8-12 минут на портале, применяет 4-6 фильтров, переключается между списком и картой, сохраняет 3-5 объектов в избранное. Если фильтрация тормозит, карта подгружается рывками, а карточка объекта не отвечает на вопрос «сколько до метро» — посетитель уходит на ЦИАН. Архитектура Битрикса позволяет собрать такой портал, но только если заложить правильную структуру данных до первого коммита.
Архитектура инфоблоков: как хранить объекты недвижимости
Первый вопрос — один инфоблок на все типы или отдельный инфоблок под квартиры, дома, коммерцию. Ответ зависит от количества уникальных свойств.
Один инфоблок + разделы по типам — работает, если свойства пересекаются на 70%+. Площадь, цена, адрес, координаты, фотографии — общие. Количество комнат — только для квартир, площадь участка — только для домов. Пустые свойства не занимают место в b_iblock_element_property (строка не создаётся, если значение null). Плюс: единый bitrix:catalog.smart_filter по всем типам, единая выдача в поиске, один шаблон списка.
Отдельные инфоблоки — оправдано, когда у коммерческой недвижимости 15 свойств, которых нет у жилой (класс помещения, тип аренды, высота потолков, грузовой лифт, мощность электрики). Смешивать их в одном инфоблоке — засорять фильтр. Минус: агрегированный поиск по всем типам потребует кастомный компонент с UNION-логикой или Elasticsearch.
Рекомендуемая структура для портала на 10К+ объектов:
- Инфоблок «Жилая недвижимость» — разделы: Квартиры, Дома, Таунхаусы
- Инфоблок «Коммерческая недвижимость» — разделы: Офисы, Торговые, Склады
-
Highload-блок «Жилые комплексы» — справочник ЖК с привязкой объектов через
PROPERTY_COMPLEX_ID - Highload-блок «Районы» — справочник с полигонами для поиска по карте
- Highload-блок «Застройщики» — компании с реквизитами и логотипами
Свойства объекта недвижимости — минимум 30 полей. Критически важные для фильтрации:
| Свойство | Тип | Индексация |
|---|---|---|
| PRICE | N (число) | Фасетный индекс |
| AREA | N | Фасетный индекс |
| ROOMS | L (список) | Фасетный индекс |
| FLOOR | N | Фасетный индекс |
| FLOORS_TOTAL | N | Обычный |
| DISTRICT | S:Highload | Фасетный индекс |
| COMPLEX_ID | S:Highload | Фасетный индекс |
| COORDINATES | S (строка, "lat,lng") | Нет |
| DEAL_TYPE | L (продажа/аренда) | Фасетный индекс |
Координаты хранятся как строка "55.7558,37.6173" — парсятся на фронте для отображения на карте. Хранить в двух отдельных свойствах (LATITUDE, LONGITUDE) не стоит — нет сценария, где нужна фильтрация по одной координате.
Фильтрация и карта: самая сложная часть
Штатный bitrix:catalog.smart_filter покрывает 60% задач — чекбоксы, списки, диапазоны. Но для недвижимости нужны вещи, которые он не умеет из коробки.
Ползунки диапазонов (range sliders) для цены и площади. Smart filter отдаёт MIN_VALUE и MAX_VALUE для числовых свойств через массив $arResult['ITEMS']. На фронте строим два input + slider на базе noUiSlider или rc-slider. Значения передаются через GET-параметры ?arrFilter_P1_MIN=3000000&arrFilter_P1_MAX=8000000. Проблема: при каждом изменении ползунка — перезагрузка страницы. Решение — AJAX-подгрузка результатов через bitrix:catalog.section с параметром AJAX_MODE=Y и кастомным JavaScript, который перехватывает submit формы фильтра.
Фильтрация по карте — рисование области поиска. Пользователь рисует полигон или окружность на карте, система возвращает объекты внутри области. Штатный smart filter этого не умеет. Реализация:
-
На фронте — Yandex.Maps API,
ymaps.GeoObjectсeditable: trueилиymaps.Polygonчерез инструмент рисования. Пользователь рисует область, JavaScript собирает массив координат вершин полигона. -
AJAX-запрос на бэкенд с координатами полигона. На стороне PHP — алгоритм Ray Casting (point-in-polygon): для каждого объекта проверяем, попадают ли его координаты внутрь полигона. При 50К объектов полный перебор — 20-40ms, приемлемо. При 200К+ — нужен пространственный индекс.
-
Пространственный индекс на MySQL/MariaDB: колонка
POINTтипа GEOMETRY, индекс SPATIAL, запрос черезST_Contains(polygon, point). Но Битрикс не хранит свойства инфоблоков как GEOMETRY. Решение — дополнительная таблицаproject_realty_geoс полямиELEMENT_ID,LOCATION POINT, SPATIAL INDEX. Синхронизация через обработчик событияOnAfterIBlockElementUpdate.
// Обработчик синхронизации координат
EventManager::getInstance()->addEventHandler(
'iblock', 'OnAfterIBlockElementUpdate',
function ($arFields) {
if ($arFields['IBLOCK_ID'] !== REALTY_IBLOCK_ID) return;
GeoIndexService::updatePoint(
$arFields['ID'],
$arFields['PROPERTY_VALUES']['COORDINATES']
);
}
);
- Результат AJAX-запроса — массив
ELEMENT_ID, который передаётся в основнойCIBlockElement::GetListчерез фильтр['ID' => $geoFilteredIds]. Таким образом, пространственная фильтрация комбинируется со smart filter.
Кластеризация маркеров. При 5К+ маркеров на карте Yandex.Maps начинает тормозить. ymaps.Clusterer группирует близкие маркеры автоматически. Но при зуме на район с 200 объектами в одном доме (ЖК) кластер разворачивается в кашу. Решение — кастомный ClusterPlacemark с подсказкой «14 квартир в ЖК Солнечный» и ссылкой на отфильтрованный список.
AJAX-обновление и карты, и списка синхронно. Пользователь двигает ползунок цены → обновляется список → обновляются маркеры на карте. Пользователь двигает карту → обновляется список объектов в видимой области. Это требует единого контроллера состояния на фронте. Архитектура: React/Vue-компонент (или vanilla JS с pub/sub), который держит текущие фильтры + видимую область карты (bounding box) и при изменении любого параметра делает один AJAX-запрос. Ответ содержит и HTML списка, и JSON координат для маркеров.
// Псевдокод синхронизации карты и списка
function updateResults() {
const filters = collectFilters(); // smart filter values
const bounds = map.getBounds(); // [[lat1,lng1],[lat2,lng2]]
filters.geo_bounds = bounds;
fetch('/api/realty/search/', {
method: 'POST',
body: JSON.stringify(filters)
})
.then(r => r.json())
.then(data => {
renderList(data.html);
renderMarkers(data.markers); // [{id, lat, lng, price, title}]
});
}
map.events.add('boundschange', debounce(updateResults, 300));
filterForm.addEventListener('change', updateResults);
Карточка объекта
Карточка квартиры — это 30+ полей, фотогалерея, видеотур, 3D-панорама, расположение на карте, инфраструктура рядом, ипотечный калькулятор. Всё это — один вызов bitrix:news.detail с кастомным шаблоном.
Фотогалерея — множественное свойство типа «Файл». Вывод через Swiper.js с lazy-загрузкой. Превью — CFile::ResizeImageGet() с параметром BX_RESIZE_IMAGE_PROPORTIONAL, 400x300. Полноэкранный просмотр — оригинал до 1920px.
3D-панорама и видеотур. Панорама — iframe с Matterport, Kuula или собственный viewer на Pannellum.js. Видеотур — YouTube/Vimeo embed. Оба хранятся как строковые свойства с URL. В шаблоне — условный рендер: если заполнено PROPERTY_PANORAMA_URL — показываем вкладку «3D-тур».
Ипотечный калькулятор — чистый JavaScript, без обращения к серверу. Формула аннуитетного платежа, три ползунка (стоимость, первый взнос, срок), результат — ежемесячный платёж. Ставки подтягиваются из Highload-блока «Банки-партнёры» при загрузке страницы.
XML-фиды для агрегаторов
ЦИАН, Avito, Yandex.Realty — у каждого свой формат XML. Общая логика: агент Битрикса (CAgent) запускается раз в час, выбирает активные объекты, формирует XML, кладёт в /upload/feeds/.
-
Yandex.Realty — формат
realty-feed, корневой элемент<realty-feed>, внутри<offer>с обязательными полямиtype,category,location,price,area,image -
ЦИАН — формат
cian-feed, элемент<object>, своя система категорий (flatSale,flatRent,commercialSale), обязательные поля отличаются от Yandex -
Avito — формат Avito Autoload, элемент
<Ad>, категорияНедвижимость, подкатегория зависит от типа
Каждый фид — отдельный класс-генератор, наследующий абстрактный BaseFeedGenerator. Маппинг свойств инфоблока на поля XML — в конфиге, не в коде. Это позволяет добавить новый агрегатор без разработчика.
Объём фида: 10К объектов → XML ~15MB. Генерация — 30-60 секунд. При 50К+ объектов генерация может занять 5 минут — выносим в фоновую задачу или нарезаем на части.
CRM-интеграция и агенты
Заявка с карточки объекта → лид в Битрикс24. REST API: crm.lead.add с полями TITLE, SOURCE_ID, UF_CRM_REALTY_ID (кастомное поле — ID объекта). Webhook или OAuth — зависит от того, один портал или несколько.
Риелторы — отдельный инфоблок или Highload-блок. Каждый объект привязан к агенту через PROPERTY_AGENT_ID. На странице агента — его объекты, контакты, рейтинг. Авторизованный агент может редактировать свои объекты через bitrix:iblock.element.edit.form с ограничением по CREATED_BY.
Избранное и сравнение
Избранное для неавторизованных — cookie или localStorage. Массив ID объектов, максимум 50. На сервере — middleware в init.php, который при каждом запросе проверяет cookie REALTY_FAVORITES и добавляет в $arResult признак IN_FAVORITES. Для авторизованных — Highload-блок UserFavorites с полями USER_ID, ELEMENT_ID, DATE_ADD.
Сравнение — аналогично, но с выводом таблицы свойств в два-три столбца. Компонент читает ID из cookie/Highload, делает GetList по массиву ID, рендерит таблицу с горизонтальным скроллом.
SEO для тысяч страниц
Ручной ввод meta для каждой из 10К квартир — невозможно. Шаблоны SEO через настройки инфоблока:
-
#ELEMENT_NAME#— название объекта -
UF_DISTRICT— район, подставляется через обработчикOnBeforeIBlockElementSeo - Формула:
Купить {тип} {комнаты}-комн. в {район} — {цена} ₽ | {сайт}
Микроразметка Schema.org RealEstateListing — в template.php карточки объекта:
{
"@context": "https://schema.org",
"@type": "RealEstateListing",
"name": "2-комн. квартира, 65 м², Центральный район",
"url": "https://site.ru/kvartiry/123/",
"datePosted": "2025-01-15",
"offers": {
"@type": "Offer",
"price": "8500000",
"priceCurrency": "RUB"
}
}
Canonical URL, hreflang для мультиязычности, XML sitemap через модуль seo с разбивкой по инфоблокам — до 50К URL на файл.
Производительность на 50К+ объектов
Фасетные индексы — обязательно. Без них bitrix:catalog.smart_filter на 50К элементов с 15 фильтруемыми свойствами — 3-5 секунд. С фасетным индексом — 50-150ms. Перестроение через Bitrix\Iblock\PropertyIndex\Manager::buildIndex($iblockId), запускается по крону после массового обновления.
Постраничная навигация — bitrix:system.pagenavigation с lazy-scroll (подгрузка при прокрутке). LIMIT + OFFSET на больших выборках деградирует — при OFFSET 40000 MySQL всё равно сканирует 40К строк. Альтернатива — курсорная пагинация по ID > $lastId, но штатные компоненты её не поддерживают. Для первых 200 страниц OFFSET приемлем.
Этапы и сроки
- Аналитика, прототипирование (1-2 недели) — структура данных, карта фильтров, прототипы Figma
- Дизайн (2-3 недели) — UI карточки, списка, карты, мобильная версия
- Разработка ядра (4-6 недель) — инфоблоки, фильтрация, карта, карточка объекта
- Интеграции (2-3 недели) — XML-фиды, CRM, ипотечный калькулятор
- Тестирование, оптимизация (1-2 недели) — нагрузка, SEO, кроссбраузер
- Запуск (3-5 дней) — деплой, импорт данных, мониторинг
| Масштаб | Сроки |
|---|---|
| Сайт агентства, до 500 объектов | 6-10 недель |
| Портал города, 5-10К объектов, карта + фильтры | 10-16 недель |
| Федеральный портал, 50К+ объектов, фиды, CRM | 14-24 недели |
Сроки не включают наполнение контентом и настройку рекламных кампаний — это параллельные процессы, которые стартуют на этапе тестирования.







