how-to · создание сайтов в сочи
Создание сайта в Сочи без конструктора
Создание сайтов в Сочи в выдаче выглядит как конвейер: поддомен сети, Битрикс или Тильда, цена «от 45 000». Это статья не про поддомен. Night Sniper — студия в Сочи, ул. Несербская, 6. Сайты собираю на Next.js, код ваш, конструктор не навязываю.
Я Дима. Гео-ключ маленький и чистый: 22 августа 2026 Wordstat по региону Сочи (239) дал «создание сайтов в сочи» около 47 показов, «разработка сайтов в сочи» — 16. Запросы вроде «цена сайта сочи» часто ведут не к студии, а к отелям и аптекам. Посадочная под чистый ключ — /sochi/sajty. Ниже — как устроен сам сайт, если собирать его как продукт, а не как набор блоков конструктора.
Срок лендинга — от 5 дней после фиксации объёма. Цену на сайте не рисую: лендинг на 4 экрана и портал с кабинетом не стоят одинаково.
Живые примеры, не «500 проектов сети»: wow-event.ru, yalta-tavrida.com, royal-coast.ru, доставка Souvlak, RICH GROUP. Отель и события — курортная специфика, не «ещё один корпоратив на Бутстрапе».
Это не флагман блога «студия Сочи». Сочи здесь — офис и локальные посадки /sochi, /sochi/sajty, /sochi/boty. Как собираем сайт как продукт — кейс сайт под ключ. Лендинг как объём работ — лендинг под ключ.
Что внутри: конструктор vs код; title ≠ H1; canonical и дубли; форма в CRM; антиспам; LocalBusiness без выдуманных координат; FAQ schema; карта; скорость; картинки; Метрика; sitemap; robots и Clean-param; nginx; IndexNow; чего не плодить; этапы работы.
Как это выглядит у нас
Сочи здесь — офис и локальные посадки, не флагман бренда. Живые сайты, которые можно открыть, те же, что на главной.



На главной: лендинг от 5 дней после фиксации объёма, правки до результата, код ваш навсегда. Кейс сайта как продукта — сайт под ключ. Состав лендинга — лендинг под ключ. Ниже — каркас страницы, форма, локальное SEO. Это спутник /sochi/sajty, не замена посадочной.
Чем студия отличается от конструктора
Тильда и аналоги хороши, пока хватает готовых секций. Как появляются каталог, своя CRM, форма с антиспамом, скорость на 4G в Хосте в августе — вы упираетесь в платформу и ежегодную подписку.
Кастомный Next.js:
- Исходники можно унести на любой сервер.
- Title, canonical, sitemap, IndexNow — в коде, не «где-то в меню SEO».
- Форма бьёт в вашу CRM, а не в общий ящик менеджера конструктора.
- Нет абонплаты за кнопки, когда закончится «тариф бизнеса».
Не делаю вид, что конструктор — зло. Если нужен лендинг «проверить нишу за неделю на шаблоне» — я не тот подрядчик. Если нужен сайт, который живёт три сезона — тогда да.
Битрикс в Сочи в выдаче стоит часто. Это нормальный выбор, если у вас уже 1С и отдел, который его знает. Я его не навязываю и не делаю вид, что «Битрикс всегда плох». Мой контур — Next.js + SQLite/Postgres по задаче + свои формы.
Каркас страницы: title не равен H1
Яндекс и люди читают разные строки. Title — сниппет в выдаче. H1 — заголовок на странице. Их нельзя копировать один в один «для SEO». На /sochi/sajty сейчас так: title про Next.js и конструктор, H1 — «Создание сайтов в Сочи».
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Сайт под ключ в Сочи на Next.js, без конструктора",
description:
"Создание сайтов в Сочи: лендинг и сайт под ключ. Офис на Несербской, 6. Смета после брифа.",
alternates: { canonical: "https://night-sniper.ru/sochi/sajty" },
openGraph: {
title: "Сайт под ключ в Сочи на Next.js — Night Sniper",
description: "Не Тильда. Дизайн, код, форма заявки, Метрика. Лендинг от 5 дней.",
url: "https://night-sniper.ru/sochi/sajty",
type: "website",
},
};
export default function Page() {
return <h1>Создание сайтов в Сочи</h1>;
}Description — до ~160–200 символов для сниппета, не простыня. Ключ в первом предложении, не пять раз.
Canonical обязателен, если страницу можно открыть с www, без www, с utm и через внутреннее зеркало. Иначе ключ размажется по дублям.
Не делайте отдельный URL «создание-сайтов-сочи» и «razrabotka-sajtov-sochi» с одним текстом. Это не семантика, это дубль.
App Router: страница как серверный компонент
Посадочная Сочи у нас — серверный компонент, форма — клиентский остров. Не тащите всю страницу в "use client" ради одной кнопки.
import Ns2Shell from "../../components/ns2/Ns2Shell";
import SochiLanding from "../../components/ns2/SochiLanding";
export default function SochiSitesPage() {
return (
<Ns2Shell leadSource="night-sniper:sochi-sajty">
<SochiLanding
crumbs={[
{ href: "/", label: "Главная" },
{ href: "/sochi", label: "Сочи" },
{ href: "/sochi/sajty", label: "Сайты" },
]}
kicker="Сочи · сайты"
h1="Создание сайтов в Сочи"
lede={<p>Лендинг и сайт под ключ. Офис на Несербской, 6.</p>}
faq={[]}
related={[]}
/>
</Ns2Shell>
);
}leadSource попадает в форму. На хабе студии — night-sniper:sochi, на ботах — night-sniper:sochi-boty. Если везде одно и то же, через месяц не поймёте, что сработало.
Форма заявки, которая попадает в CRM
Форма без CRM — почта, которую не читают в сезон. На night-sniper.ru заявка уходит в POST /api/leads/ingest.
Контракт из кода, не из фантазии:
sourceдля формы сайта —site(илиlanding/quiz, если это они). Неsochi.source_detail— до 64 символов, уникален на посадочной.- Пустая заявка без контакта и текста — 400.
- Если на сервере задан секрет хаба — HMAC в
x-lead-signature. Браузерная форма обычно бьёт в тот же origin, секрет проверяется на серверном route, который принимает форму, а не из клиента.
Клиентский вызов:
await fetch("/api/leads/ingest", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
source: "site",
source_detail: "night-sniper:sochi-sajty",
name: name || null,
phone,
tg: telegram,
email: email || null,
message: "Нужен сайт. Кратко: лендинг отеля, 8 экранов.",
}),
});На клиенте: согласие на ПДн и хотя бы один контакт. На сервере — лимит частоты, иначе бот набьёт воронку.
Не обещаю «заявка за 30 секунд попадёт в Битрикс24», если Битрикс24 нет в контуре. У нас: ingest → карточка → уведомление в мессенджер.
Простейший антиспам на route — не секрет из статьи про «honeypot на CSS», а серверный лимит:
const hits = new Map<string, number[]>();
function allow(ip: string, windowMs = 10 * 60 * 1000, max = 5) {
const now = Date.now();
const arr = (hits.get(ip) || []).filter((t) => now - t < windowMs);
if (arr.length >= max) return false;
arr.push(now);
hits.set(ip, arr);
return true;
}Для боя лучше таблица или существующий лимит в ingest (отпечаток телефона + день). Map в памяти сбросится при рестарте — это нормально как первый слой.
Honeypot-поле company_url, скрытое CSS: если заполнено — отвечайте 200 без записи в CRM. Бот доволен, воронка чистая.
Локальное SEO: адрес, не «Сочи» в каждом абзаце
Коммерческие факторы Яндекса для города: адрес, карта, телефон или мессенджер, сроки, живые кейсы. Не плотность слова «Сочи». Переспам в каждом абзаце — путь к Баден-Бадену. H1, один абзац введения, title, FAQ — достаточно. Дальше пишите продукт.
Регион в Яндекс.Вебмастере для домена — Сочи, если это ваш город. Мультигород из шаблонов «услуга × 80 городов» Яндекс уже сжигал на других проектах: тонкие страницы уезжают в 410. Поэтому нет /adler/sajty, нет /sochi/crm на нулевых показах.
JSON-LD на хабе студии — ProfessionalService с улицей. Координаты не выдумываю, если их нет в кадастре на руках. Карта — виджет поиска по адресу, не случайная точка на набережной.
{
"@context": "https://schema.org",
"@type": "ProfessionalService",
"name": "Night Sniper",
"url": "https://night-sniper.ru/sochi",
"email": "hi@night-sniper.ru",
"address": {
"@type": "PostalAddress",
"streetAddress": "ул. Несербская, д. 6",
"addressLocality": "Сочи",
"addressRegion": "Краснодарский край",
"addressCountry": "RU"
},
"areaServed": { "@type": "City", "name": "Сочи" },
"sameAs": ["https://t.me/nightsniper01"],
"priceRange": "По запросу"
}FAQ на странице должен совпадать с FAQPage в разметке. Не кладите в JSON-LD вопросы, которых нет в HTML. Отзывы в schema — только если они реально есть на странице. Звёзды «5.0 из 47 отзывов», которых нет — это ложь, не SEO.
Хлебные крошки:
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Главная", "item": "https://night-sniper.ru/" },
{ "@type": "ListItem", "position": 2, "name": "Сочи", "item": "https://night-sniper.ru/sochi" },
{ "@type": "ListItem", "position": 3, "name": "Сайты", "item": "https://night-sniper.ru/sochi/sajty" }
]
}В коде это собирает хелпер, чтобы крошки в HTML и в JSON-LD не разъехались.
Карта:
<iframe
title="Офис Night Sniper на карте"
src="https://yandex.ru/map-widget/v1/?text=Сочи, ул. Несербская, 6&z=16"
loading="lazy"
/>Не ставьте lat=43.58&lon=39.72 «на глаз». Виджет поиска по адресу врёт меньше.
Скорость: курортный трафик — мобильный
LCP с прелоадером и тяжёлым шоурилом на главной — отдельный разговор. Внутренние посадочные должны быть легче главной: без видео на hero, без трёх каруселей.
Картинки кейсов — уже сжатый webp из портфолио, не полноразмерный JPEG с дрона. Шрифты — preload тех, что реально рисуют H1.
export default function CaseImage({ src, alt }: { src: string; alt: string }) {
return (
<img
src={src}
alt={alt}
width={1280}
height={720}
loading="lazy"
decoding="async"
/>
);
}width и height нужны, чтобы не прыгал layout. decoding="sync" на hero главной мы уже ставили отдельно, когда мерили PSI. На внутренней посадочной обычно хватает lazy.
Не выключайте Метрику «чтобы ускорить». На RU-сайте поведенческие Яндекс читает всерьёз. Цель — отправка формы, не «просмотр 15 секунд». Цель должна совпадать с тем, что пишет CRM: иначе реклама оптимизируется в воздух.
Не накручивайте отказы. Фильтр за ПФ дороже любой статьи.
Яндекс читает отказы серьёзнее Google. Обманный H1 «создание сайтов 5000 рублей» при смете в 15 раз выше убьёт поведенческие быстрее, чем недохват ключа.
Что входит в лендинг под ключ
- Прототип блоков: hero, оффер, доказательства, форма, FAQ.
- Дизайн в той же системе, что и этот сайт — не «шаблон 14 из маркетплейса».
- Вёрстка, адаптив, форма, SSL, Метрика.
- Тексты: ваши факты. Я не выдумываю «№1 в Сочи» и 500 проектов, которых нет.
Домены, почта, редирект www — по брифу. SEO-блог и геохаб — отдельные страницы, их не прячут в подвал конструктора.
Оплата и личный кабинет — если нужны, закладываю отдельно в смету. Не прячу это в «сайт под ключ за одну цифру».
Sitemap: обещать только живые URL
Новый URL должен появиться в sitemap.xml в ту же выкладку, не «добавим потом». Черновики блога со статусом review в sitemap не попадают — так и задумано. Яндекс не любит обещать URL, который отдаёт заглушку или noindex.
export default function sitemap() {
return [
{ url: "https://night-sniper.ru/sochi", changeFrequency: "weekly", priority: 0.85 },
{ url: "https://night-sniper.ru/sochi/sajty", changeFrequency: "monthly", priority: 0.8 },
{ url: "https://night-sniper.ru/sochi/boty", changeFrequency: "monthly", priority: 0.8 },
];
}Публичные статьи блога попадают в карту только со статусом published/dzen. Пока вы читаете эту страницу как черновик — в sitemap её нет.
robots.txt и мусорные параметры
Для Яндекса дубли с utm и yclid режутся Clean-param, не директивой Host: (она давно мертва). В Next часто отдают robots.ts, а Clean-param дописывают статикой.
User-agent: Yandex
Clean-param: utm_source&utm_medium&utm_campaign&yclid&from
Sitemap: https://night-sniper.ru/sitemap.xmlНе закрывайте /sochi и /blog в Disallow «на всякий случай». Не открывайте /admin, /crm, /api.
import type { MetadataRoute } from "next";
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{ userAgent: "*", allow: "/", disallow: ["/admin", "/crm", "/api"] },
],
sitemap: "https://night-sniper.ru/sitemap.xml",
};
}Clean-param в MetadataRoute.Robots Next может не пропустить. Тогда дописывайте отдельной строкой в статическом файле, который отдаёт nginx, и не держите два противоречивых robots.
IndexNow — после вашего «ок», не до
Индексация черновика вреднее, чем день задержки. Статьи в статусе review отдают robots: noindex. Посадочные /sochi* — живые страницы продукта.
Когда текст согласован: статус published, снять noindex, URL уже в sitemap, пинг IndexNow. Не наоборот.
Черновик блога читаете по прямой ссылке. Поиск его не должен забирать.
nginx перед Node
Публичный 443 обслуживает nginx, Next слушает localhost. Новый URL не требует нового сервера — location уже проксирует приложение. Не поднимайте второй nginx на 443 «для лендинга Сочи».
location / {
proxy_pass http://127.0.0.1:3004;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}Перед правкой конфига — копия *.bak-YYYYmmdd-HHMMSS, sudo nginx -t, потом reload.
Next.js плохо переносит pm2 reload. Цикл выкладки: npm run build → адресный pm2 restart night-sniper-app → смотреть логи. Не pm2 restart all.
После правки компонентов посадочной сборка обязательна. PM2 держит старую .next в памяти и маскирует дыру до следующего рестарта.
Контент, который не стыдно отдать в Вебмастер
- Факты из портфолио: живые URL, не «500 проектов сети».
- Сроки как на главной: лендинг от 5 дней, бот от 3.
- Адрес как в футере и оферте, не «Сочи, центр» без улицы.
- Никакого «гарантия топ-1 за 30 дней».
- Телефон или мессенджер, на который реально отвечают. У нас публичный контур — форма, квиз, Telegram @nightsniper01, почта hi@night-sniper.ru.
How-to в блоге и посадочная — разные задачи. Посадочная продаёт работу. Статья учит, как устроен сайт, с кодом. Их не склеивают в один URL «чтобы вес».
Статьи про ботов рядом: MAX, Telegram. Гео-боты: /sochi/boty. Хаб студии: /sochi.
Чего не плодить
CRM, парсеры, AI-чатбот с хвостом «в Сочи» на 0–4 показа в месяц — тонкий city-pSEO. Их не плодим.
Не клонировать эту статью под Адлер, Красную Поляну и «Москва-Сити» с заменой города. Если появится отдельный спрос и отдельный офис/партнёр — отдельная страница с уникальным текстом. Не шаблон.
Национальный ключ «лендинг под ключ» толще гео. Если вы не в Сочи и ищете просто разработку — геостраница необязательна. Квиз всё равно один: /quiz.
Этапы работы со мной
- Бриф: цель, примеры, материалы, срок «к сезону», если он есть.
- Прототип: какие экраны, какая форма, какой
source_detail. - Сборка и выкладка на staging или сразу на прод — как договоримся.
- Правки до состояния, когда страница вам нравится и форма доезжает.
- После вашего «ок» по тексту — индекс: sitemap уже содержит URL, пингуем IndexNow.
До пункта 5 черновики блога висят с noindex. Посадочные /sochi* можно смотреть и править дизайн.
Встретиться можно на Несербской, 6. Большую часть проектов веду удалённо.
Каркас формы на клиенте
Минимум, который я ставлю, чтобы заявка не улетала пустой и чтобы согласие было явным.
"use client";
import { useState } from "react";
export function LeadBox({ sourceDetail }: { sourceDetail: string }) {
const [phone, setPhone] = useState("");
const [ok, setOk] = useState(false);
const [err, setErr] = useState("");
async function onSubmit(e: React.FormEvent) {
e.preventDefault();
setErr("");
if (!phone.trim()) {
setErr("Нужен телефон или Telegram");
return;
}
const res = await fetch("/api/leads/ingest", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
source: "site",
source_detail: sourceDetail,
phone,
message: "Заявка со страницы сайтов в Сочи",
}),
});
if (!res.ok) {
setErr("Не отправилось. Напишите в Telegram.");
return;
}
setOk(true);
}
if (ok) return <p>Заявку получил. Отвечу сам.</p>;
return (
<form onSubmit={onSubmit}>
<input value={phone} onChange={(e) => setPhone(e.target.value)} placeholder="Телефон или @username" />
<label>
<input type="checkbox" required /> Согласен на обработку персональных данных
</label>
<button type="submit">Отправить</button>
{err ? <p>{err}</p> : null}
</form>
);
}Цель Метрики вешайте на успешный ответ, не на клик по кнопке. Клик без 200 — не заявка.
Реальный sitemap, не «пример из гайда»
Карта night-sniper.ru собирается в app/sitemap.ts на каждый запрос: база, квиз, ОС, блог, три URL Сочи, политика, плюс только опубликованные статьи. Черновик review в список не попадает — поэтому этой статьи в /sitemap.xml сейчас нет, и это правильно.
export const dynamic = "force-dynamic";
export default function sitemap() {
const base = "https://night-sniper.ru";
const published = listPublishedArticles();
return [
{ url: base, changeFrequency: "weekly", priority: 1 },
{ url: `${base}/sochi`, changeFrequency: "weekly", priority: 0.85 },
{ url: `${base}/sochi/sajty`, changeFrequency: "monthly", priority: 0.8 },
{ url: `${base}/sochi/boty`, changeFrequency: "monthly", priority: 0.8 },
...published.map((a) => ({
url: `${base}/blog/${a.slug}`,
changeFrequency: "monthly",
priority: 0.7,
})),
];
}Не обещайте в карте /admin, /crm, превью Villa Castalia и внутренние стенды. Sitemap — контракт с роботом, не склад всего, что слушает nginx.
robots: закрыть кабинет, не город
Боевой app/robots.ts закрывает /api/, /admin/, /crm/ и служебные контуры, а не /sochi. Отдельно перечислены Yandex / YandexBot / Googlebot с тем же disallow. Поле host в файле ещё стоит — для Яндекса директива Host: давно не рычаг, на неё не рассчитывайте.
export default function robots() {
const disallow = ["/api/", "/admin/", "/crm/", "/docs/", "/aida/", "/disk/"];
return {
rules: [
{ userAgent: "*", allow: "/", disallow },
{ userAgent: "Yandex", allow: "/", disallow },
],
sitemap: "https://night-sniper.ru/sitemap.xml",
};
}Clean-param для utm_* и yclid в этот объект Next может не сериализовать. Если строка нужна, её держат отдельным фрагментом text/plain и сверяют curl https://night-sniper.ru/robots.txt. Два разных robots — путь к сюрпризу.
Не ставьте Disallow: /sochi. Не ставьте Allow: /api «чтобы формы работали»: робот не должен жить в API, форма живёт в браузере.
FAQ в HTML и в JSON-LD — один источник
На посадочной Сочи FAQ рисуется из массива и тем же массивом уходит в FAQPage. Если поправите текст в JSX и забудете JSON-LD — получите расхождение, Яндекс это не любит.
const FAQ = [
{
q: "Почему не Тильда?",
a: "Конструктор удобен, пока хватает блоков. Каталог и своя CRM — уже свой код.",
},
{
q: "Какой срок?",
a: "Лендинг запускаю от 5 дней. Точный срок фиксирую после разбора задачи.",
},
];
export function sochiFaqLd(faq: { q: string; a: string }[]) {
return {
"@context": "https://schema.org",
"@type": "FAQPage",
mainEntity: faq.map((f) => ({
"@type": "Question",
name: f.q,
acceptedAnswer: { "@type": "Answer", text: f.a },
})),
};
}В блоге FAQ-разметка в поиск не отдаётся, пока статья в review. На странице блок «Вопросы» вы всё равно видите — для согласования.
Посадочная и статья — не дубль
/sochi/sajty продаёт работу: H1 под ключ, форма, адрес, кейсы, сроки. Эта статья учит, как сайт устроен, с кодом. Перелинковка обязательна, копипаста абзацев — нет.
Хабу /sochi достаточно объяснить студию. Не тащите сюда весь how-to. Не тащите на хаб весь оффер сайтов.
Боты — соседний интент: /sochi/boty, статьи MAX и Telegram.
Мета для черновика блога
Пока статус review, страница отдаёт noindex, nofollow. Баннер на странице говорит об этом прямо. После вашего «ок» статус станет published, баннер уйдёт, URL попадёт в sitemap, пинганём IndexNow. Не просите пинг «на всякий случай» до правки текста: вычищать черновик из индекса дороже, чем день задержки.
const noindex = article.status === "review";
return {
title: `${title} — Night Sniper`,
robots: noindex ? { index: false, follow: false } : undefined,
alternates: { canonical: `https://night-sniper.ru/blog/${article.slug}` },
};Типичные ошибки гео-страниц, которые я не повторяю
- Поддомен сети
sochi.franchise-site.ruс тем же текстом, что у Краснодара. - H1 «создание сайтов в сочи недорого» при смете, которая в десять раз выше.
- Десять вхождений ключа в первых двух абзацах.
- Отзывы в schema без отзывов в HTML.
- Точка на карте «примерно центр».
- Страница «создание сайтов в Адлере» без офиса, кейсов и спроса.
- Обещание «подключим Битрикс24 за вечер», если в контуре его нет.
Как заказать
Опишите задачу в форме на странице сайтов или в квизе. Нужны: цель страницы, примеры, что уже есть (бренд, фото, домен). Офис: Сочи, ул. Несербская, 6.
Вопросы
- Почему не Тильда?
- Конструктор удобен, пока хватает блоков. Каталог, своя CRM и контроль скорости — уже свой код. Исходники ваши, абонплаты за платформу нет.
- Сколько стоит?
- Смета после описания задачи. На сайте нет вилки «от N рублей»: лендинг и портал несравнимы одной цифрой.
- Какой срок?
- Лендинг — от 5 дней после фиксации объёма. Точный срок фиксирую после разбора задачи.
- Нужно ли ехать в офис?
- Нет. Офис на Несербской, 6 — по желанию. Большую часть проектов веду удалённо.
- Почему title не равен H1?
- Title — сниппет в выдаче, H1 — заголовок на странице. Копировать один в один «для SEO» не нужно.
- Делаете ли страницы под каждый город?
- Нет. Тонкий city-pSEO на нулевых показах не плодим. Сочи — потому что здесь офис и чистый спрос в Wordstat.
- Когда страница попадёт в поиск?
- После публикации: sitemap, снят noindex, IndexNow. Черновики блога с баннером «на согласование» в индекс не отдаём.
- Форма куда уходит?
- В CRM через /api/leads/ingest, source=site, source_detail свой на каждой посадочной. Не в общий ящик конструктора.
Нужен сайт — опишите задачу, смета после брифа. Квиз