From 89d095f4860ebf73d4032811fea5a6a01e34c54a Mon Sep 17 00:00:00 2001 From: bot-backend Date: Sun, 13 Sep 2026 14:41:10 +0300 Subject: [PATCH] =?UTF-8?q?feat(mera):=20=D0=B4=D0=B2=D0=B5=20=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D1=82=D1=8C=D0=B8=20=D0=BD=D0=B0=20=D1=81=D0=BE=D0=B1?= =?UTF-8?q?=D1=81=D1=82=D0=B2=D0=B5=D0=BD=D0=BD=D0=BE=D0=BC=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=BC=D0=B5=D1=80=D0=B5=20=D0=B2=D0=BC=D0=B5=D1=81=D1=82=D0=BE?= =?UTF-8?q?=20=D0=BA=D0=B0=D1=80=D1=82=D0=BE=D1=87=D0=B5=D0=BA=20=C2=AB?= =?UTF-8?q?=D0=A1=D0=9A=D0=9E=D0=A0=D0=9E=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Раздел /articles с 29.08 стоял с одной живой статьёй и двумя карточками без ссылок. Обе дописаны — но не по темам из макета: те темы данными не обеспечены. «Сколько стоит метр в Екатеринбурге: цены сделок, а не объявлений». 24 535 договоров Росреестра за июль 2025 — июнь 2026, регион 66. Медиана 123 626 ₽/м². Несущий факт статьи контринтуитивен и проверен: кривая цены метра по комнатности не падает, а прогибается — дешевле всего метр в двухкомнатной (116 910 ₽), дороже всего в студии (134 507 ₽). Плюс поквартальная динамика за все десять доступных кварталов (+20,6%) и восемь городов области с порогом публикации в сто сделок. «Почему квартира не продаётся: что происходит с ценой, пока вы ждёте». 49 620 объявлений области с историей цены площадок. Цена двигалась у 43,5%, из них вниз у 60,2%, вверх у 38,0%. Медиана снижения −3,85% и −291 000 ₽. ПОЧЕМУ НЕ ТЕ ТЕМЫ, ЧТО В МАКЕТЕ. Карточка «Сколько продаётся квартира» требовала срока экспозиции, а он не измеряется: `days_on_market` пуст у всех 435 тысяч сделок Росреестра, а снятие объявления и продажа в наших данных неразличимы — 41% «снятий» приходится на шесть дат, совпадающих с провалами собственного обхода. Мерили бы мы свой сборщик, а не рынок. Вторая карточка называлась «Завысить и торговаться»; запрос «завысить цену на квартиру» уведён юридическим смыслом (завышение суммы в ДКП ради ипотеки), и статья попала бы в чужую выдачу. ДИСЦИПЛИНА ЧИСЕЛ. Замер прошёл адверсариальную проверку: из десяти первоначальных величин воспроизвелись две, остальные отброшены как непригодные к печати. Обе статьи пересчитаны заново с нуля. У каждого блока стоит своё поле source/footnote с датой, выборкой и границей — чем это число НЕ является. Неизмеримое названо неизмеримым прямо в тексте, а не обойдено молчанием: первый раздел второй статьи объясняет читателю, почему средний срок продажи не назовёт никто честный. Обвязка: два ключа в PUBLIC_ROUTES, две страницы, sitemap, три матчера в apps.caddy на каждый адрес, PUBLIC_SHORT_PATHS у RouteGuard, PUBLIC_INDEXED_FILES в гейте noindex. Блок «Читайте дальше» перестал быть статичным «СКОРО» и делает карточку ссылкой, как только у неё появился route. Витрина и лэндинг обновились сами — обе перебирают ARTICLE_CARDS. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VQ8jqr4SFirX5tFLwdSrXh --- caddy/sites/apps.caddy | 6 +- .../__tests__/landing-numbers-gate.test.ts | 2 + .../mera-public/articles/articles-content.ts | 277 +++++++++++++++++- .../articles/kak-ocenit-kvartiru/page.tsx | 15 +- .../pochemu-kvartira-ne-prodaetsya/page.tsx | 231 +++++++++++++++ .../page.tsx | 231 +++++++++++++++ .../frontend/src/app/mera-public/content.ts | 2 + .../frontend/src/app/mera-public/sitemap.ts | 14 +- .../src/components/auth/RouteGuard.tsx | 2 + 9 files changed, 765 insertions(+), 15 deletions(-) create mode 100644 tradein-mvp/frontend/src/app/mera-public/articles/pochemu-kvartira-ne-prodaetsya/page.tsx create mode 100644 tradein-mvp/frontend/src/app/mera-public/articles/skolko-stoit-metr-v-ekaterinburge/page.tsx diff --git a/caddy/sites/apps.caddy b/caddy/sites/apps.caddy index ca670588..574eba98 100644 --- a/caddy/sites/apps.caddy +++ b/caddy/sites/apps.caddy @@ -518,7 +518,7 @@ meraocenka.ru { # Next отдаёт его КОРНЕВЫМ /trade-in/robots.txt (конвенция Next — # robots.txt существует только в корне app/, не под mera-public/), эта # форма под `{path}` не попадает — отсюда отдельный handle у него. - @meraPages path /estimate /oferta /refund /privacy /articles /articles/kak-ocenit-kvartiru /docs /business /sitemap.xml + @meraPages path /estimate /oferta /refund /privacy /articles /articles/kak-ocenit-kvartiru /articles/skolko-stoit-metr-v-ekaterinburge /articles/pochemu-kvartira-ne-prodaetsya /docs /business /sitemap.xml handle @meraPages { rewrite * /trade-in/mera-public{path} reverse_proxy tradein-frontend:3000 { @@ -549,7 +549,7 @@ meraocenka.ru { # переносит query как есть. Обёртка `route` обязательна по той же # причине, что у соседей — внутри `handle` порядок директив определяет # Caddy, и без неё `redir` выполнился бы раньше `uri`. - @meraShortSlash path_regexp shortslash ^/(estimate|oferta|refund|privacy|articles|articles/kak-ocenit-kvartiru|docs|business)/$ + @meraShortSlash path_regexp shortslash ^/(estimate|oferta|refund|privacy|articles|articles/kak-ocenit-kvartiru|articles/skolko-stoit-metr-v-ekaterinburge|articles/pochemu-kvartira-ne-prodaetsya|docs|business)/$ handle @meraShortSlash { route { uri strip_suffix / @@ -637,7 +637,7 @@ meraocenka.ru { # порядок директив внутри `handle` определяет Caddy, и без неё `redir` # выполняется РАНЬШЕ `uri`, отдавая Location, равный исходному адресу # (бесконечный цикл; поймано на локальном стенде). - @meraLongPages path /trade-in/mera-public/estimate /trade-in/mera-public/estimate/ /trade-in/mera-public/oferta /trade-in/mera-public/oferta/ /trade-in/mera-public/refund /trade-in/mera-public/refund/ /trade-in/mera-public/privacy /trade-in/mera-public/privacy/ /trade-in/mera-public/articles /trade-in/mera-public/articles/ /trade-in/mera-public/articles/kak-ocenit-kvartiru /trade-in/mera-public/articles/kak-ocenit-kvartiru/ /trade-in/mera-public/docs /trade-in/mera-public/docs/ /trade-in/mera-public/business /trade-in/mera-public/business/ + @meraLongPages path /trade-in/mera-public/estimate /trade-in/mera-public/estimate/ /trade-in/mera-public/oferta /trade-in/mera-public/oferta/ /trade-in/mera-public/refund /trade-in/mera-public/refund/ /trade-in/mera-public/privacy /trade-in/mera-public/privacy/ /trade-in/mera-public/articles /trade-in/mera-public/articles/ /trade-in/mera-public/articles/kak-ocenit-kvartiru /trade-in/mera-public/articles/kak-ocenit-kvartiru/ /trade-in/mera-public/articles/skolko-stoit-metr-v-ekaterinburge /trade-in/mera-public/articles/skolko-stoit-metr-v-ekaterinburge/ /trade-in/mera-public/articles/pochemu-kvartira-ne-prodaetsya /trade-in/mera-public/articles/pochemu-kvartira-ne-prodaetsya/ /trade-in/mera-public/docs /trade-in/mera-public/docs/ /trade-in/mera-public/business /trade-in/mera-public/business/ handle @meraLongPages { route { uri strip_prefix /trade-in/mera-public diff --git a/tradein-mvp/frontend/src/app/mera-public/__tests__/landing-numbers-gate.test.ts b/tradein-mvp/frontend/src/app/mera-public/__tests__/landing-numbers-gate.test.ts index 0a5362ab..979a3e65 100644 --- a/tradein-mvp/frontend/src/app/mera-public/__tests__/landing-numbers-gate.test.ts +++ b/tradein-mvp/frontend/src/app/mera-public/__tests__/landing-numbers-gate.test.ts @@ -330,6 +330,8 @@ describe("гейт витринных чисел (#2904)", () => { "estimate/page.tsx", "articles/page.tsx", "articles/kak-ocenit-kvartiru/page.tsx", + "articles/skolko-stoit-metr-v-ekaterinburge/page.tsx", + "articles/pochemu-kvartira-ne-prodaetsya/page.tsx", "docs/page.tsx", "business/page.tsx", ]; diff --git a/tradein-mvp/frontend/src/app/mera-public/articles/articles-content.ts b/tradein-mvp/frontend/src/app/mera-public/articles/articles-content.ts index 6e995f6c..772e83a9 100644 --- a/tradein-mvp/frontend/src/app/mera-public/articles/articles-content.ts +++ b/tradein-mvp/frontend/src/app/mera-public/articles/articles-content.ts @@ -45,7 +45,7 @@ export interface ArticleCardMeta { * второй статьи не может увести её на первую (так и было, пока * `to="articleHowTo"` стоял константой в JSX). */ - readonly route: "articleHowTo" | null; + readonly route: "articleHowTo" | "articlePrices" | "articleWaiting" | null; readonly rubric: string; readonly title: string; /** Подводка карточки — дословно из макета. */ @@ -61,16 +61,16 @@ export const ARTICLE_CARDS: readonly ArticleCardMeta[] = [ lead: "Почему цена соседа — не ориентир, и как считать по аналогам.", }, { - route: null, - rubric: "СРОКИ · 5 МИН", - title: "Сколько продаётся квартира в 2026 году и что «подвешивает» объявление", - lead: "Средние сроки по типам квартир и главные причины простоя.", + route: "articlePrices", + rubric: "ЦЕНЫ СДЕЛОК · 5 МИН", + title: "Сколько стоит метр в Екатеринбурге: цены сделок, а не объявлений", + lead: "Медиана по 24 535 сделкам Росреестра и почему метр в двушке самый дешёвый.", }, { - route: null, - rubric: "ТОРГ · 7 МИН", - title: "Завысить и торговаться или поставить в рынок: что выгоднее", - lead: "Считаем на данных, сколько теряет «висящая» квартира каждый месяц.", + route: "articleWaiting", + rubric: "ЦЕНА И ОЖИДАНИЕ · 5 МИН", + title: "Почему квартира не продаётся: что происходит с ценой, пока вы ждёте", + lead: "Кто снижает цену, на сколько и за какой срок — по 49 620 объявлениям.", }, ]; @@ -181,3 +181,262 @@ export const HOWTO = { summary: "Смотрите на цены сделок, а не объявлений. Берите 5–14 действительно взаимозаменяемых аналогов, считайте медиану цены за метр, применяйте поправки и обязательно проверяйте цену сроком продажи. Если считать некогда — это ровно то, что МЕРА делает автоматически.", } as const; + +/** + * Статья «Сколько стоит метр в Екатеринбурге» (13.09.2026). + * + * ЗАМЕР, НА КОТОРОМ СТОИТ ТЕКСТ. Таблица `deals` — договоры купли-продажи + * Росреестра, регион 66. Взяты четыре последних доступных квартала + * (`deal_date` 2025-07-01, 2025-10-01, 2026-01-01, 2026-04-01 — дата в данных + * штампуется началом квартала, отдельного дня сделки в выгрузке нет), то есть + * июль 2025 — июнь 2026. Отсечены цены за метр вне коридора 20 000–500 000 ₽ + * как заведомый мусор выгрузки. По Екатеринбургу осталось 24 535 сделок. + * + * ГРАНИЦА, КОТОРУЮ НАЗЫВАЕМ В ТЕКСТЕ: Росреестр публикует цену договора. Она + * не всегда равна сумме, которую покупатель отдал на руки, и связки со + * сделавшим её объявлением в данных нет — `cadastral_number` пуст и у сделок, + * и у объявлений. Поэтому «разрыв между ценой объявления и ценой сделки» в + * статье не считается: считать его нечем. + * + * ПОЧЕМУ ДИНАМИКА ДАНА ПОКВАРТАЛЬНО: в выгрузке ровно десять различных + * значений `deal_date`, все на первое число квартала. Месячного разрешения в + * данных нет вовсе. + */ +export const PRICES = { + rubric: "ЦЕНЫ СДЕЛОК · 5 МИН ЧТЕНИЯ", + title: "Сколько стоит метр в Екатеринбурге: цены сделок, а не объявлений", + lead: [ + "Когда вы смотрите объявления, вы видите, сколько продавцы хотят получить. Сколько покупатели заплатили на самом деле, видно в другом месте — в договорах купли-продажи, которые проходят через Росреестр. Эти два числа не совпадают, и ориентироваться стоит на второе.", + "Ниже — медианы по реальным сделкам Екатеринбурга и области за последние четыре квартала. Без округлений до красивого и без слова «примерно».", + ], + toc: [ + { id: "s1", title: "Сколько стоит метр по сделкам" }, + { id: "s2", title: "Почему метр в двушке самый дешёвый" }, + { id: "s3", title: "Что делала цена последние два с половиной года" }, + { id: "s4", title: "Города области: где метр дешевле" }, + { id: "s5", title: "Пять способов неправильно прочитать эти цифры" }, + ], + s1: { + id: "s1", + heading: "1. Сколько стоит метр по сделкам", + body: "Медианная цена квадратного метра в сделках по Екатеринбургу — 123 626 ₽. Медиана, а не среднее: среднее в недвижимости всегда тянут вверх несколько дорогих объектов, и к типичной квартире оно отношения не имеет. Четверть сделок прошла дешевле 100 000 ₽ за метр, четверть дороже 151 288 ₽ — вот настоящая ширина рынка, и она больше, чем кажется по объявлениям. Если ваша цена попадает в эту вилку, вы в рынке.", + source: + "ЗАМЕР 13.09.2026 · 24 535 ДОГОВОРОВ КУПЛИ-ПРОДАЖИ РОСРЕЕСТРА ПО ЕКАТЕРИНБУРГУ ЗА ИЮЛЬ 2025 — ИЮНЬ 2026 · ЭТО ЦЕНА ДОГОВОРА, А НЕ ЦЕНА ОБЪЯВЛЕНИЯ", + memo: "Цена объявления и цена договора — разные величины. Насколько именно разные, не скажет никто: Росреестр публикует сделки без привязки к объявлению, сопоставить одно с другим нечем.", + }, + s2: { + id: "s2", + heading: "2. Почему метр в двушке самый дешёвый", + body: "«Чем больше квартира, тем дешевле метр» — самое живучее заблуждение о ценах. По сделкам кривая не падает, а прогибается: дешевле всего метр в двухкомнатной, а дальше он снова дорожает.", + table: { + head: ["КОМНАТНОСТЬ", "МЕДИАНА ЗА МЕТР"], + rows: [ + { name: "Студия · медиана 25 м² · 2 967 сделок", value: "134 507 ₽" }, + { name: "Однокомнатная · 38 м² · 8 342 сделки", value: "124 256 ₽" }, + { name: "Двухкомнатная · 51 м² · 6 548 сделок", value: "116 910 ₽" }, + { name: "Трёхкомнатная · 70 м² · 3 861 сделка", value: "121 835 ₽" }, + { + name: "Четыре комнаты и больше · 108 м² · 2 817 сделок", + value: "132 122 ₽", + }, + ], + }, + footnote: + "ТОТ ЖЕ ЗАМЕР · РЯДОМ С КАЖДОЙ СТРОКОЙ — МЕДИАННАЯ ПЛОЩАДЬ И ЧИСЛО СДЕЛОК, НА КОТОРЫХ ПОСЧИТАНА МЕДИАНА", + after: + "Объяснение простое: у студий и у больших квартир метр продаётся вместе с чем-то ещё. У студии — с низким порогом входа, её берут те, кому важна сумма целиком, а не цена метра. У четырёхкомнатной — с домом и районом, потому что такие квартиры почти не строят в дешёвых сериях. Двушка же самый массовый и самый конкурентный товар на рынке, и цена метра в ней прижата плотнее всего.", + }, + s3: { + id: "s3", + heading: "3. Что делала цена последние два с половиной года", + body: "Рынок Екатеринбурга не падал ни одного квартала из десяти доступных. Медиана росла непрерывно, но по-разному: в 2024 году прибавка держалась около полутора процентов за квартал, в 2025-м и 2026-м ускорилась. Для продавца отсюда следует практический вывод: цена, посчитанная по сделкам полугодовой давности, сегодня занижена.", + tiles: [ + { value: "106 727 ₽", label: "метр в первом квартале 2024 года" }, + { value: "128 756 ₽", label: "метр во втором квартале 2026 года" }, + { value: "+20,6%", label: "рост медианы за два с половиной года" }, + ], + source: + "ТОТ ЖЕ ИСТОЧНИК, ВСЕ ДЕСЯТЬ ДОСТУПНЫХ КВАРТАЛОВ · ДАТА СДЕЛКИ В ВЫГРУЗКЕ РОСРЕЕСТРА ШТАМПУЕТСЯ НАЧАЛОМ КВАРТАЛА, МЕСЯЧНОГО РАЗРЕШЕНИЯ В ДАННЫХ НЕТ", + }, + s4: { + id: "s4", + heading: "4. Города области: где метр дешевле", + body: "Внутри Свердловской области разброс больше, чем между комнатностями. Города-спутники Екатеринбурга идут почти по областной столице, дальние промышленные города — втрое дешевле.", + kv: [ + { k: "ВЕРХНЯЯ ПЫШМА", v: "102 888 ₽ за метр · 1 193 сделки" }, + { k: "ЗАРЕЧНЫЙ", v: "95 865 ₽ · 393 сделки" }, + { k: "БЕРЁЗОВСКИЙ", v: "95 745 ₽ · 858 сделок" }, + { k: "ПЕРВОУРАЛЬСК", v: "66 384 ₽ · 1 458 сделок" }, + { k: "КАМЕНСК-УРАЛЬСКИЙ", v: "63 763 ₽ · 1 994 сделки" }, + { k: "НИЖНИЙ ТАГИЛ", v: "62 637 ₽ · 3 799 сделок" }, + { k: "АСБЕСТ", v: "46 429 ₽ · 642 сделки" }, + { k: "НОВОУРАЛЬСК", v: "40 527 ₽ · 816 сделок" }, + ], + footnote: + "ТОТ ЖЕ ПЕРИОД · ПОКАЗАНЫ ГОРОДА, ГДЕ ЗА ГОД НАБРАЛОСЬ НЕ МЕНЬШЕ СТА СДЕЛОК · ГДЕ ИХ МЕНЬШЕ, МЕДИАНА НЕУСТОЙЧИВА И МЫ ЕЁ НЕ ПУБЛИКУЕМ", + }, + cta: { + eyebrow: "МЕДИАНА ПО ГОРОДУ — ЭТО ЕЩЁ НЕ ВАША ЦЕНА", + title: "МЕРА считает по вашему дому, а не по городу", + body: `Между двумя домами в одном районе разница бывает больше, чем между районами. Мы находим аналоги вокруг вашего адреса, считаем медиану по ним и показываем, на чём она построена. Рынок вокруг — бесплатно, ваша цена и список аналогов — за ${SERVICE_PRICE_RUB} ₽.`, + }, + s5: { + id: "s5", + heading: "5. Пять способов неправильно прочитать эти цифры", + mistakes: [ + { + title: "Умножить медиану города на свою площадь", + body: "Получится цена абстрактной квартиры в абстрактном районе. Медиана по городу — ориентир порядка величины, а не оценка объекта.", + }, + { + title: "Решить, что раз метр в двушке дешевле, двушку продавать невыгодно", + body: "Дешевле метр, а не квартира. Двушка всё равно стоит дороже однушки: 6 015 000 ₽ против 4 650 000 ₽ по медиане сделок.", + }, + { + title: "Считать цену договора суммой на руки", + body: "Росреестр публикует то, что написано в договоре. Совпадает ли это с фактическим расчётом между сторонами, из данных не видно.", + }, + { + title: "Брать медиану полугодовой давности", + body: "За последние четыре квартала медиана прибавляла около двух процентов за квартал. Полгода отставания — это несколько процентов мимо вашей цены.", + }, + { + title: "Сравнивать свою квартиру с городом, а не с соседями", + body: "Между квартилями Екатеринбурга разброс от 100 000 до 151 288 ₽ за метр. Где внутри этой вилки стоит ваш дом, медиана города не скажет.", + }, + ], + }, + summary: + "Медианный метр в Екатеринбурге стоит 123 626 ₽ по 24 535 сделкам за год. Дешевле всего метр в двухкомнатной, дороже всего в студии. Рынок рос все десять доступных кварталов и прибавил 20,6% с начала 2024 года. Города области идут от 40 527 ₽ в Новоуральске до 102 888 ₽ в Верхней Пышме. Всё это ориентиры порядка, а не оценка вашей квартиры: её считают по соседним домам.", +} as const; + +/** + * Статья «Почему квартира не продаётся» (13.09.2026). + * + * ЗАМЕР, НА КОТОРОМ СТОИТ ТЕКСТ. Таблица `offer_price_history` — история цены + * объявления, которую ведут сами площадки (cian, yandex, domklik, avito), + * склеенная с `listings` по региону 66. Срез 13.09.2026: + * + * 49 620 объявлений области, у которых есть хоть одна запись о цене + * 21 608 из них цена менялась хотя бы раз (43,5%) + * 13 009 итоговая цена ниже стартовой (60,2% от менявших) + * 8 212 итоговая цена ВЫШЕ стартовой (38,0% от менявших) + * + * Медиана по снизившим: −3,85%, −291 000 ₽, 30 дней между первой и последней + * записью о цене. + * + * ГРАНИЦЫ, КОТОРЫЕ НАЗЫВАЕМ В ТЕКСТЕ. (1) Это движение ЗАПРАШИВАЕМОЙ цены, а + * не скидка на сделке: связки объявления со сделкой в данных нет. (2) Знаменатель + * 21 608 — объявления, где цена двигалась; те, у кого запись одна, в долю не + * входят, потому что у них неподвижная цена и отсутствие наблюдения + * неразличимы. (3) «30 дней» — срок между первой и последней записью о цене, а + * не срок продажи. Срока продажи в данных нет вовсе: поле `days_on_market` в + * сделках Росреестра пусто у всех 435 тысяч записей, а исчезновение объявления + * означает только исчезновение из выдачи, но не продажу. + * + * ПОЧЕМУ В СТАТЬЕ НЕТ СРЕДНЕГО СРОКА ПРОДАЖИ. Он не измеряется. Снятие + * объявления и продажа в наших данных неразличимы, а даты «снятий» частично + * совпадают с провалами собственного обхода — то есть измеряли бы мы свой + * сборщик, а не рынок. + */ +export const WAITING = { + rubric: "ЦЕНА И ОЖИДАНИЕ · 5 МИН ЧТЕНИЯ", + title: "Почему квартира не продаётся: что происходит с ценой, пока вы ждёте", + lead: [ + "Квартира висит третий месяц, звонков нет, и первый совет от всех вокруг — «подожди, твой покупатель найдётся». Иногда это правда. Чаще ожидание заканчивается тем же, чем закончилось у большинства: продавец снижает цену, только на несколько месяцев позже и на большую сумму.", + "Мы посмотрели, что на самом деле происходит с ценой в объявлениях Свердловской области, пока они висят. Ниже — что видно в данных и, не менее важно, чего в них не видно.", + ], + toc: [ + { id: "s1", title: "Что вообще можно измерить, а что нельзя" }, + { id: "s2", title: "Кто двигает цену и в какую сторону" }, + { id: "s3", title: "На сколько снижают те, кто снижает" }, + { id: "s4", title: "Что делать, если звонков нет" }, + { id: "s5", title: "Пять причин простоя, которые не в цене" }, + ], + s1: { + id: "s1", + heading: "1. Что вообще можно измерить, а что нельзя", + body: "Честно о границах, прежде чем показывать числа. Среднего срока продажи мы вам не назовём, и никто честный не назовёт: чтобы его посчитать, нужно знать день, когда квартиру выставили, и день, когда её купили. Второго нет ни у кого, кроме сторон сделки. Росреестр публикует договоры без срока экспозиции, а исчезновение объявления с площадки означает только исчезновение с площадки — продал, передумал или переподал заново, из данных не отличить. Зато прекрасно видно другое: как продавец сам двигает цену, пока ждёт.", + source: + "ЗАМЕР 13.09.2026 · 49 620 ОБЪЯВЛЕНИЙ СВЕРДЛОВСКОЙ ОБЛАСТИ, У КОТОРЫХ ПЛОЩАДКА ВЕДЁТ ИСТОРИЮ ЦЕНЫ · ЭТО ЗАПРАШИВАЕМАЯ ЦЕНА, А НЕ ЦЕНА СДЕЛКИ", + memo: "Любая статья, которая называет вам «средний срок продажи квартиры», считает его по снятиям объявлений. Снятие — это не продажа. Спросите, как отличали одно от другого.", + }, + s2: { + id: "s2", + heading: "2. Кто двигает цену и в какую сторону", + body: "Из 49 620 объявлений цена хотя бы раз сдвинулась у 21 608. Дальше начинается неожиданное: вниз ходят не все. Почти четыре из десяти движений — вверх.", + tiles: [ + { value: "43,5%", label: "объявлений, где цена вообще двигалась" }, + { value: "60,2%", label: "из двигавших — снизили" }, + { value: "38,0%", label: "из двигавших — подняли" }, + ], + source: + "ТОТ ЖЕ ЗАМЕР · ЗНАМЕНАТЕЛЬ ДОЛЕЙ — 21 608 ОБЪЯВЛЕНИЙ, У КОТОРЫХ ЦЕНА МЕНЯЛАСЬ · У ОСТАЛЬНЫХ ЗАПИСЬ ОДНА, И НЕПОДВИЖНУЮ ЦЕНУ ОТ ОТСУТСТВИЯ НАБЛЮДЕНИЯ НЕ ОТЛИЧИТЬ", + after: + "Подъём цены обычно означает не жадность, а переоценку: продавец начал слишком низко, получил шквал звонков в первую неделю и понял это. Ошибка в меньшую сторону чинится легко и быстро. Ошибка в большую сторону чинится месяцами — и это ровно та асимметрия, ради которой стоит потратить вечер на расчёт до публикации.", + }, + s3: { + id: "s3", + heading: "3. На сколько снижают те, кто снижает", + body: "Медианное снижение выглядит скромно в процентах и совсем не скромно в рублях. И это не одна большая уступка в конце, а обычно две-три ступеньки подряд.", + table: { + head: ["ЧТО ИЗМЕРЕНО", "МЕДИАНА ПО 13 009 ОБЪЯВЛЕНИЯМ"], + rows: [ + { name: "Снижение от стартовой цены", value: "−3,85%" }, + { name: "Снижение в рублях", value: "−291 000 ₽" }, + { name: "Срок между первой и последней ценой", value: "30 дней" }, + { name: "Нижний квартиль снижения", value: "−7,7%" }, + { name: "Верхний квартиль снижения", value: "−2,4%" }, + ], + }, + footnote: + "ТОТ ЖЕ ЗАМЕР · «30 ДНЕЙ» — СРОК МЕЖДУ ПЕРВОЙ И ПОСЛЕДНЕЙ ЗАПИСЬЮ О ЦЕНЕ, А НЕ СРОК ПРОДАЖИ · СРОК ПРОДАЖИ ИЗМЕРИТЬ НЕЧЕМ", + }, + s4: { + id: "s4", + heading: "4. Что делать, если звонков нет", + body: "Ожидание само по себе не аргумент. Рынок отвечает на цену быстро, и молчание — тоже ответ. Порядок действий такой:", + kv: [ + { k: "ПЕРВАЯ НЕДЕЛЯ", v: "считаем показы и звонки, а не дни. Ноль звонков за неделю при живом объявлении — цена выше рынка" }, + { k: "ВТОРАЯ НЕДЕЛЯ", v: "пересчитываем по аналогам заново: что сейчас стоит рядом и что из этого реально ушло" }, + { k: "ПЕРЕД СНИЖЕНИЕМ", v: "проверяем фото, описание и этаж — часть простоя не в цене вовсе, см. раздел 5" }, + { k: "ЕСЛИ СНИЖАЕМ", v: "один заметный шаг лучше пяти незаметных: снижение на 1% рынок не видит" }, + { k: "ЧЕГО НЕ ДЕЛАТЬ", v: "снимать и публиковать заново ради «свежести» — история цены на площадках всё равно сохраняется" }, + ], + footnote: + "ЭТО ПОРЯДОК ДЕЙСТВИЙ, А НЕ ЗАМЕР · ИЗ ДАННЫХ ЗДЕСЬ ТОЛЬКО ОДНО: СНИЖАЮЩИЕ ДЕЛАЮТ ЭТО В МЕДИАНЕ ЗА ТРИ СТУПЕНЬКИ ЦЕНЫ", + }, + cta: { + eyebrow: "ПЕРЕД ТЕМ КАК СНИЖАТЬ", + title: "Проверьте, действительно ли цена выше рынка", + body: `Снижать вслепую — это отдавать сотни тысяч на всякий случай. МЕРА найдёт аналоги вокруг вашего адреса и покажет, где ваша цена относительно них. Рынок вокруг — бесплатно, разбор вашей цены — за ${SERVICE_PRICE_RUB} ₽.`, + }, + s5: { + id: "s5", + heading: "5. Пять причин простоя, которые не в цене", + mistakes: [ + { + title: "Четыре фотографии, снятые вечером", + body: "Объявление конкурирует в ленте миниатюр, а не в тексте. Если карточку не открыли, цена значения не имела.", + }, + { + title: "Описание из характеристик", + body: "«2/5, панель, 44 м²» покупатель уже видит в фильтре. В описании должно быть то, чего в фильтре нет.", + }, + { + title: "Показы только по будням до шести", + body: "Ваш покупатель в это время на работе. Часть простоя — это не отказ рынка, а несовпадение расписаний.", + }, + { + title: "Обременение, о котором вы скажете позже", + body: "Ипотека, доли, несовершеннолетний собственник. Всплывает на этапе аванса и отправляет вас в начало.", + }, + { + title: "Продажа «в никуда»", + body: "Если вы не знаете, куда переезжаете, вы неосознанно тормозите сделку сами. Покупатель это чувствует раньше вас.", + }, + ], + }, + summary: + "Срок продажи никто честно не измерит: снятие объявления и продажа в данных неразличимы. Зато видно движение цены. Она двигалась у 43,5% объявлений, и в 60,2% случаев вниз, в медиане на 3,85% и 291 000 ₽. Прежде чем снижать, проверьте цену по аналогам и проверьте само объявление: часть простоя не в цене.", +} as const; diff --git a/tradein-mvp/frontend/src/app/mera-public/articles/kak-ocenit-kvartiru/page.tsx b/tradein-mvp/frontend/src/app/mera-public/articles/kak-ocenit-kvartiru/page.tsx index fcdd7c94..b6bdbebd 100644 --- a/tradein-mvp/frontend/src/app/mera-public/articles/kak-ocenit-kvartiru/page.tsx +++ b/tradein-mvp/frontend/src/app/mera-public/articles/kak-ocenit-kvartiru/page.tsx @@ -202,8 +202,19 @@ export default function HowToArticlePage() {
- {ARTICLE_CARDS.filter((c) => c.route !== "articleHowTo").map( - (c) => ( + {ARTICLE_CARDS.filter((c) => c.route !== "articleHowTo").map((c) => + c.route ? ( + +
{c.rubric}
+
{c.title}
+
{c.lead}
+
Читать →
+
+ ) : (
{c.rubric}
{c.title}
diff --git a/tradein-mvp/frontend/src/app/mera-public/articles/pochemu-kvartira-ne-prodaetsya/page.tsx b/tradein-mvp/frontend/src/app/mera-public/articles/pochemu-kvartira-ne-prodaetsya/page.tsx new file mode 100644 index 00000000..9ab9cfd3 --- /dev/null +++ b/tradein-mvp/frontend/src/app/mera-public/articles/pochemu-kvartira-ne-prodaetsya/page.tsx @@ -0,0 +1,231 @@ +import type { Metadata } from "next"; + +import { InnerHeader } from "../../_components/InnerHeader"; +import { JsonLd } from "../../_components/JsonLd"; +import { PublicLink } from "../../_components/PublicLink"; +import { FooterV3 } from "../../_components/v3/FooterV3"; +import { breadcrumbNode, ORGANIZATION_ID } from "../../_lib/schema"; +import { PUBLIC_ORIGIN, PUBLIC_ROUTES } from "../../content"; +import styles from "../../landing-v3.module.css"; +import { ARTICLE_CARDS, WAITING } from "../articles-content"; + +/** + * Статья «Почему квартира не продаётся: что происходит с ценой, пока вы ждёте». + * Текст и все величины — в `articles-content.ts` (там же замер, на котором они + * стоят, и его границы). В этом файле не должно быть ни одного числа: папка + * попадает в `SUBPAGES` гейта чисел. + * + * Серверный компонент без клиентского JS: содержание — якорные ссылки, + * секции — обычные заголовки с id. + * + * ИНДЕКСАЦИЯ ОТКРЫТА (10.09.2026, см. layout.tsx). + */ + +const PUBLIC_WAITING_URL = `${PUBLIC_ORIGIN}${PUBLIC_ROUTES.articleWaiting}`; +const DESCRIPTION = WAITING.lead[0]; + +export const metadata: Metadata = { + title: `${WAITING.title} — МЕРА`, + description: DESCRIPTION, + alternates: { canonical: PUBLIC_WAITING_URL }, + openGraph: { + type: "article", + title: `${WAITING.title} — МЕРА`, + description: DESCRIPTION, + url: PUBLIC_WAITING_URL, + }, +}; + +/** + * JSON-LD `Article` для сниппетов поисковика. Без `datePublished`/ + * `dateModified`: достоверной даты публикации в коде нет, а выдуманная дата — + * то самое нечестное утверждение, которое запрещает шапка content.ts. + */ +const ARTICLE_JSON_LD = { + "@context": "https://schema.org", + "@type": "Article", + headline: WAITING.title, + description: DESCRIPTION, + author: { "@type": "Organization", name: "МЕРА" }, + publisher: { "@id": ORGANIZATION_ID }, + mainEntityOfPage: { "@type": "WebPage", "@id": PUBLIC_WAITING_URL }, +}; + +const BREADCRUMB_JSON_LD = { + "@context": "https://schema.org", + ...breadcrumbNode([ + { name: "Главная", url: `${PUBLIC_ORIGIN}${PUBLIC_ROUTES.home}` }, + { name: "Статьи", url: `${PUBLIC_ORIGIN}${PUBLIC_ROUTES.articles}` }, + { name: WAITING.title, url: PUBLIC_WAITING_URL }, + ]), +}; + +export default function WaitingArticlePage() { + return ( + <> + + + +
+ + +
+
{WAITING.rubric}
+

{WAITING.title}

+
+ + + +
+ {WAITING.lead.map((p) => ( +

+ {p} +

+ ))} + +

+ {WAITING.s1.heading} +

+

{WAITING.s1.body}

+
{WAITING.s1.source}
+ + +

+ {WAITING.s2.heading} +

+

{WAITING.s2.body}

+
+ {WAITING.s2.tiles.map((t) => ( +
+
{t.value}
+
{t.label}
+
+ ))} +
+
{WAITING.s2.source}
+

{WAITING.s2.after}

+ +

+ {WAITING.s3.heading} +

+

{WAITING.s3.body}

+
+
+ {WAITING.s3.table.head[0]} + + {WAITING.s3.table.head[1]} + +
+ {WAITING.s3.table.rows.map((r) => ( +
+ {r.name} + + {r.value} + +
+ ))} +
+
{WAITING.s3.footnote}
+ +

+ {WAITING.s4.heading} +

+

{WAITING.s4.body}

+
+ {WAITING.s4.kv.map((row) => ( +
+ {row.k} + {row.v} +
+ ))} +
+
{WAITING.s4.footnote}
+ +
+
{WAITING.cta.eyebrow}
+
{WAITING.cta.title}
+

{WAITING.cta.body}

+ + Проверить квартиру + +
+ +

+ {WAITING.s5.heading} +

+
    + {WAITING.s5.mistakes.map((m, i) => ( +
  1. + + {String(i + 1).padStart(2, "0")} + +
    +
    {m.title}
    +
    {m.body}
    +
    +
  2. + ))} +
+ +
+
КОРОТКО
+

{WAITING.summary}

+
+
+ +
+
+

Читайте дальше

+ + Все статьи → + +
+
+ {ARTICLE_CARDS.filter((c) => c.route !== "articleWaiting").map((c) => + c.route ? ( + +
{c.rubric}
+
{c.title}
+
{c.lead}
+
Читать →
+
+ ) : ( +
+
{c.rubric}
+
{c.title}
+
{c.lead}
+
СКОРО
+
+ ), + )} +
+
+
+ + + ); +} diff --git a/tradein-mvp/frontend/src/app/mera-public/articles/skolko-stoit-metr-v-ekaterinburge/page.tsx b/tradein-mvp/frontend/src/app/mera-public/articles/skolko-stoit-metr-v-ekaterinburge/page.tsx new file mode 100644 index 00000000..461cca2b --- /dev/null +++ b/tradein-mvp/frontend/src/app/mera-public/articles/skolko-stoit-metr-v-ekaterinburge/page.tsx @@ -0,0 +1,231 @@ +import type { Metadata } from "next"; + +import { InnerHeader } from "../../_components/InnerHeader"; +import { JsonLd } from "../../_components/JsonLd"; +import { PublicLink } from "../../_components/PublicLink"; +import { FooterV3 } from "../../_components/v3/FooterV3"; +import { breadcrumbNode, ORGANIZATION_ID } from "../../_lib/schema"; +import { PUBLIC_ORIGIN, PUBLIC_ROUTES } from "../../content"; +import styles from "../../landing-v3.module.css"; +import { ARTICLE_CARDS, PRICES } from "../articles-content"; + +/** + * Статья «Сколько стоит метр в Екатеринбурге: цены сделок, а не объявлений». + * Текст и все величины — в `articles-content.ts` (там же замер, на котором они + * стоят, и его границы). В этом файле не должно быть ни одного числа: папка + * попадает в `SUBPAGES` гейта чисел. + * + * Серверный компонент без клиентского JS: содержание — якорные ссылки, + * секции — обычные заголовки с id. + * + * ИНДЕКСАЦИЯ ОТКРЫТА (10.09.2026, см. layout.tsx). + */ + +const PUBLIC_PRICES_URL = `${PUBLIC_ORIGIN}${PUBLIC_ROUTES.articlePrices}`; +const DESCRIPTION = PRICES.lead[0]; + +export const metadata: Metadata = { + title: `${PRICES.title} — МЕРА`, + description: DESCRIPTION, + alternates: { canonical: PUBLIC_PRICES_URL }, + openGraph: { + type: "article", + title: `${PRICES.title} — МЕРА`, + description: DESCRIPTION, + url: PUBLIC_PRICES_URL, + }, +}; + +/** + * JSON-LD `Article` для сниппетов поисковика. Без `datePublished`/ + * `dateModified`: достоверной даты публикации в коде нет, а выдуманная дата — + * то самое нечестное утверждение, которое запрещает шапка content.ts. + */ +const ARTICLE_JSON_LD = { + "@context": "https://schema.org", + "@type": "Article", + headline: PRICES.title, + description: DESCRIPTION, + author: { "@type": "Organization", name: "МЕРА" }, + publisher: { "@id": ORGANIZATION_ID }, + mainEntityOfPage: { "@type": "WebPage", "@id": PUBLIC_PRICES_URL }, +}; + +const BREADCRUMB_JSON_LD = { + "@context": "https://schema.org", + ...breadcrumbNode([ + { name: "Главная", url: `${PUBLIC_ORIGIN}${PUBLIC_ROUTES.home}` }, + { name: "Статьи", url: `${PUBLIC_ORIGIN}${PUBLIC_ROUTES.articles}` }, + { name: PRICES.title, url: PUBLIC_PRICES_URL }, + ]), +}; + +export default function PricesArticlePage() { + return ( + <> + + + +
+ + +
+
{PRICES.rubric}
+

{PRICES.title}

+
+ + + +
+ {PRICES.lead.map((p) => ( +

+ {p} +

+ ))} + +

+ {PRICES.s1.heading} +

+

{PRICES.s1.body}

+
{PRICES.s1.source}
+ + +

+ {PRICES.s2.heading} +

+

{PRICES.s2.body}

+
+
+ {PRICES.s2.table.head[0]} + + {PRICES.s2.table.head[1]} + +
+ {PRICES.s2.table.rows.map((r) => ( +
+ {r.name} + + {r.value} + +
+ ))} +
+
{PRICES.s2.footnote}
+

{PRICES.s2.after}

+ +

+ {PRICES.s3.heading} +

+

{PRICES.s3.body}

+
+ {PRICES.s3.tiles.map((t) => ( +
+
{t.value}
+
{t.label}
+
+ ))} +
+
{PRICES.s3.source}
+ +

+ {PRICES.s4.heading} +

+

{PRICES.s4.body}

+
+ {PRICES.s4.kv.map((row) => ( +
+ {row.k} + {row.v} +
+ ))} +
+
{PRICES.s4.footnote}
+ +
+
{PRICES.cta.eyebrow}
+
{PRICES.cta.title}
+

{PRICES.cta.body}

+ + Проверить квартиру + +
+ +

+ {PRICES.s5.heading} +

+
    + {PRICES.s5.mistakes.map((m, i) => ( +
  1. + + {String(i + 1).padStart(2, "0")} + +
    +
    {m.title}
    +
    {m.body}
    +
    +
  2. + ))} +
+ +
+
КОРОТКО
+

{PRICES.summary}

+
+
+ +
+
+

Читайте дальше

+ + Все статьи → + +
+
+ {ARTICLE_CARDS.filter((c) => c.route !== "articlePrices").map((c) => + c.route ? ( + +
{c.rubric}
+
{c.title}
+
{c.lead}
+
Читать →
+
+ ) : ( +
+
{c.rubric}
+
{c.title}
+
{c.lead}
+
СКОРО
+
+ ), + )} +
+
+
+ + + ); +} diff --git a/tradein-mvp/frontend/src/app/mera-public/content.ts b/tradein-mvp/frontend/src/app/mera-public/content.ts index 92fc154b..586e0116 100644 --- a/tradein-mvp/frontend/src/app/mera-public/content.ts +++ b/tradein-mvp/frontend/src/app/mera-public/content.ts @@ -195,6 +195,8 @@ export const PUBLIC_ROUTES = { // Макет 29.08: раздел статей и хаб документов. articles: "/articles", articleHowTo: "/articles/kak-ocenit-kvartiru", + articlePrices: "/articles/skolko-stoit-metr-v-ekaterinburge", + articleWaiting: "/articles/pochemu-kvartira-ne-prodaetsya", docs: "/docs", // Страница B2B-контура: связаться и оставить заявку. Заведена 31.08.2026 // взамен ссылки на `B2B_URL` в шапке и подвале — та вела в закрытый контур diff --git a/tradein-mvp/frontend/src/app/mera-public/sitemap.ts b/tradein-mvp/frontend/src/app/mera-public/sitemap.ts index 8d90f386..4ff12f89 100644 --- a/tradein-mvp/frontend/src/app/mera-public/sitemap.ts +++ b/tradein-mvp/frontend/src/app/mera-public/sitemap.ts @@ -22,7 +22,7 @@ import { PUBLIC_ORIGIN, PUBLIC_ROUTES } from "./content"; * актуальным целиком. Двигать дату вперёд нужно вручную, вместе с реальным * изменением состава/контента страниц. */ -const LAST_MODIFIED = new Date("2026-09-10T00:00:00Z"); +const LAST_MODIFIED = new Date("2026-09-13T00:00:00Z"); export default function sitemap(): MetadataRoute.Sitemap { const url = (path: string) => `${PUBLIC_ORIGIN}${path}`; @@ -52,6 +52,18 @@ export default function sitemap(): MetadataRoute.Sitemap { changeFrequency: "monthly", priority: 0.6, }, + { + url: url(PUBLIC_ROUTES.articlePrices), + lastModified: LAST_MODIFIED, + changeFrequency: "monthly", + priority: 0.6, + }, + { + url: url(PUBLIC_ROUTES.articleWaiting), + lastModified: LAST_MODIFIED, + changeFrequency: "monthly", + priority: 0.6, + }, { url: url(PUBLIC_ROUTES.business), lastModified: LAST_MODIFIED, diff --git a/tradein-mvp/frontend/src/components/auth/RouteGuard.tsx b/tradein-mvp/frontend/src/components/auth/RouteGuard.tsx index 6769b645..bec56070 100644 --- a/tradein-mvp/frontend/src/components/auth/RouteGuard.tsx +++ b/tradein-mvp/frontend/src/components/auth/RouteGuard.tsx @@ -102,6 +102,8 @@ const PUBLIC_SHORT_PATHS = [ "/privacy", "/articles", "/articles/kak-ocenit-kvartiru", + "/articles/skolko-stoit-metr-v-ekaterinburge", + "/articles/pochemu-kvartira-ne-prodaetsya", "/docs", "/business", // Машинные артефакты для поисковых роботов, а не страницы. React на них не