Programmatic SEO на Astro: как мы создали 100+ гео-страниц со скоростью 98+ без CMS

Создание каталогов, агрегаторов локальных услуг или мультирыночных сервисов обычно загоняет команды в стандартную ловушку: раздутый WordPress с десятками плагинов либо связка с Headless CMS (Strapi, Sanity), которая обходится в сотни евро за квоты API и выдает медленный отклик сервера (TTFB 800мс+).

При проектировании платформы LocalAnyDay, работающей на рынках Великобритании и Польши, мы полностью отказались от традиционных CMS-подходов.

Вся платформа была собрана на Astro с использованием статических коллекций контента, JSON-слоев на этапе компиляции и легковесных островов на чистом JavaScript. Результат: более 100 локализованных страниц услуг с мобильным рейтингом PageSpeed 98+, временем отклика сервера до 150мс и нулевыми ежемесячными расходами на сторонние подписки.

Главные тезисы статьи


1. Бутылочное горлышко CMS в Programmatic SEO

Большинство programmatic SEO проектов проваливаются еще до того, как успевают войти в индекс. Когда Googlebot приходит на новый домен с сотней страниц, он выделяет минимальный краулинговый бюджет.

Если ваши страницы зависят от динамических запросов к базе данных (PHP-скрипты WordPress или серверный рендеринг Next.js SSR):

  1. TTFB превышает 600–1200мс: Поисковые боты упираются в тайм-ауты и замедляют обход структуры городов.
  2. Сдвиги макета портят Core Web Vitals: Каскадная гидратация скриптов вызывает скачки элементов (CLS), опуская показатели скорости в красную зону (<60).
  3. Лимиты API блокируют сборку: Пересборка сайта на 500 страниц через API удаленной CMS упирается в лимиты запросов или требует дорогих тарифов.

Перенос генерации страниц на этап сборки в Astro превращает каждый URL в чистый HTML и CSS. При запросе пользователя серверу не нужно обращаться к базе данных.

БЕНЧМАРК АРХИТЕКТУРЫ: CMS ПРОТИВ СТЕКА ASTRO
Сравнение рантайма
🤖 Пользователь / Бот Входящий HTTP-запрос
PHP / Запрос в БД Тяжелая генерация
🐢 TTFB 840мс Потеря краулинг-бюджета
🤖 Пользователь / Бот Входящий HTTP-запрос
Edge-кэш Готовый HTML / CSS
🚀 TTFB 65мс Зеленая зона 99/100 CWV

2. Структура каталога и компиляция данных

В нашей архитектуре каждый город, категория услуги и ценовой бенчмарк хранятся в виде структурированных файлов JSON и Markdown прямо в репозитории:

/src/content/
├── services/
│   ├── driveway-cleaning.json
│   └── floor-sanding.json
└── locations/
    ├── uk/
    │   ├── bristol.json
    │   └── bath.json
    └── pl/
        └── wroclaw.json

Функция Astro getStaticPaths() считывает эти массивы данных и генерирует взаимосвязанные посадочные страницы со сквозной внутренней перелинковкой:

// src/pages/[country]/[city]/[service].astro
import { getCollection } from 'astro:content';

export async function getStaticPaths() {
  const cities = await getCollection('cities');
  const services = await getCollection('services');

  return cities.flatMap((city) => {
    return services.map((service) => ({
      params: {
        country: city.data.countryCode,
        city: city.slug,
        service: service.slug,
      },
      props: { city, service },
    }));
  });
}

const { city, service } = Astro.props;

Сборка всего дерева из сотен посадочных страниц занимает менее 40 секунд.


3. Интерактивные калькуляторы без тяжелых фреймворков

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

Вместо подключения тяжелых библиотек React или Vue (добавляющих 50–80 КБ скриптов), мы реализовали эти элементы через нативные Custom Elements браузера на чистом JS:

// Легковесный калькулятор стоимости (0 КБ внешних зависимостей)
class ServicePriceCalculator extends HTMLElement {
  connectedCallback() {
    const slider = this.querySelector('input[type="range"]');
    const output = this.querySelector('.calculated-price');
    const baseRate = parseFloat(this.dataset.baseRate || '12');

    slider.addEventListener('input', (e) => {
      const area = parseFloat(e.target.value);
      const total = Math.round(area * baseRate);
      output.textContent = `£${total}`;
    });
  }
}
customElements.define('service-price-calculator', ServicePriceCalculator);

Поскольку Astro по умолчанию не загружает клиентский JavaScript, этот скрипт исполняется только на страницах калькулятора. Общий вес JS на мобильного пользователя составляет менее 3.2 КБ.


4. Сравнение производительности

Мы протестировали нашу архитектуру на Astro против типового решения на WordPress и динамической сборки Next.js SSR на одинаковых серверах Hetzner VPS:

ПоказательWordPressNext.js 16 (SSR + CMS)Архитектура ILF Studio (Astro)
Мобильный Core Web Vitals48 / 10078 / 10099 / 100
Время до первого байта (TTFB)840мс380мс72мс
Отрисовка крупного контента (LCP)3.6с2.1с0.9с
Сдвиг макета (CLS)0.180.040.00
Объем клиентского JS240 КБ85 КБ3.2 КБ
Затраты на хостинг и CMS~60 €/мес~120 €/мес5 €/мес (Обычный VPS)

5. Защита от фильтров за шаблонный спам

Алгоритмы Google 2025–2026 годов пессимизируют сайты, где один и тот же шаблон размножается заменой названия города.

Чтобы гарантировать стабильный рост и безопасность домена, мы внедрили 3 инженерных правила:

  1. Уникальные региональные данные: Файл каждого города содержит реальные правила утилизации отходов, регламенты дренажа и параметры жесткости воды.
  2. Капельный ввод страниц в индекс (Drip-Feed): Сначала запускается базовое ядро из 15–20 страниц, а новые города подключаются с шагом в 7–10 дней после подтверждения индексации через Google Search Console.
  3. Семантическая микроразметка: Каждая страница формирует граф Schema.org (Service, LocalBusiness, BreadcrumbList) с привязкой к географическим координатам.

Выводы для фаундеров и руководителей разработки