Недавно я выпустил две статьи - про подход POSSE и про SEO + GEO в 2026 году. В комментариях и личных сообщениях несколько человек попросили описать техническую часть.

Решил написать. Сразу оговорюсь, я не претендую на идеальное решение. Сайт не сделан безупречно - я знаю его ограничения и расскажу о них честно. Это просто разбор: что, как и почему сделано именно так.


Что это за сайт

"На Дерево" - это статический сайт с тремя функциями:

  • Главная страница проекта

  • Лендинг для продукта MENO

  • Блог

Сайт двуязычный - русский и английский. Никакого SPA, никакого серверного рендеринга. Чистая статика.

Главная страница
Главная страница

Стек

Что

Чем

Фреймворк

Astro 6 (SSG-режим)

Язык

TypeScript, strict-режим

Контент блога

Astro Content Collections + Markdown

Сервер

Nginx на VPS

SSL

Certbot (Let's Encrypt)

Аналитика

Яндекс.Метрика

Стек простой. Это намеренно.


Почему Astro

Astro - SSG-фреймворк. Собирает статический HTML на этапе билда. На выходе - папка dist с файлами, которую можно положить куда угодно. Никакого рантайма на сервере, никакого Node.js в продакшне.

Для блога и лендинга это идеальная модель. Контент не меняется между деплоями. Отдавать статику через Nginx - быстрее и надёжнее, чем гонять SSR.

Content Collections в Astro позволяют описать схему контента и писать посты в Markdown / MDX. Типизация из коробки - если в фронтматтере не хватает обязательного поля, билд упадёт. Это удобно, когда у тебя десятки постов и два языка.


Два языка: как устроена локализация

Структура URL:

naderevo.com/ru/         - главная (русский)
naderevo.com/en/         - главная (английский)
naderevo.com/ru/blog/    - блог
naderevo.com/en/blog/    - блог
naderevo.com/            - redirect-entry

Корень / - служебная страница. Она не индексируется (noindex). Её единственная задача - определить язык пользователя по localStorage, cookie или заголовку браузера и перенаправить на /ru/ или /en/.

Конфигурация в Astro:

locales: ['ru', 'en'],
prefixDefaultLocale: true,
redirectToDefaultLocale: false

prefixDefaultLocale: true - оба языка живут с префиксом. Нет ситуации, когда русская версия на /, а английская на /en/. Оба равноправны. Это упрощает hreflangcanonical и вообще всю SEO-логику.

redirectToDefaultLocale: false - Astro не форсит редирект на дефолтный язык. Я сам решаю, куда отправить пользователя.


SEO-разметка

Каждая контентная страница содержит:

Базовые мета-теги

  • canonical - каноничный URL этой страницы

  • hreflang - ссылки на все языковые версии (включая x-default)

  • og:titleog:descriptionog:imageog:urlog:locale

  • twitter:cardtwitter:titletwitter:descriptiontwitter:image

Structured Data (JSON-LD)

Разные типы для разных страниц:

Страница

Schema.org тип

Главная

WebSite

Лендинг MENO

SoftwareApplication

Статья блога

BlogPosting

JSON-LD рендерится прямо в <head> на этапе сборки. Никаких клиентских скриптов для разметки.

Sitemap и robots.txt

// astro.config
import sitemap from '@astrojs/sitemap';

@astrojs/sitemap генерирует карту сайта при билде. Я добавил фильтр, чтобы не попадали служебные страницы - тот же корневой / и подобные.

robots.txt максимально открытый:

User-agent: *
Allow: /
Sitemap: https://naderevo.com/sitemap-index.xml

Никаких блокировок для AI-краулеров. Я писал об этом в статье про SEO и GEO - если вы хотите, чтобы языковые модели знали о вашем контенте, не закрывайте от них сайт.


Аналитика и приватность

Яндекс.Метрика подключена, но с условием: она запускается только после явного согласия пользователя.

Cookie-consent

На сайте есть баннер с двумя кнопками - принять и отклонить. Без хитростей.

Решение пользователя сохраняется в cookie и localStorage. Если человек нажал "отклонить":

  • Скрипт Метрики не загружается

  • Существующие cookie и записи в localStorage от Метрики очищаются

Это не GDPR-театр.

Цели

Для трекинга кликов использую data-metrika-goal на элементах. Метрика подхватывает их автоматически. Без кастомных скриптов на каждую кнопку.

ID Метрики передаётся через PUBLIC_YANDEX_METRIKA_ID - публичная build-time переменная в Astro. Если её нет в env - Метрика просто не встроится в сборку. Удобно для локальной разработки.


Деплой и инфраструктура

VPS + Nginx

Сайт лежит на самой дешёвой VPS. Статика через Nginx. Всё.

Почему не Vercel / Netlify / Cloudflare Pages? Потому что для статического сайта мне не нужна их инфраструктура. dist - это папка с HTML, CSS и JS. Nginx отдаёт её быстрее, чем я успеваю моргнуть. Полный контроль над конфигами, редиректами и заголовками.

SSL

Certbot с Let's Encrypt. Бесплатно, автообновление сертификатов. Настройка - 5 минут один раз.

Редиректы

# http -> https
server {
    listen 80;
    return 301 https://$host$request\_uri;
}

# www -> apex
server {
    server_name www.naderevo.com;
    return 301 https://naderevo.com$request\_uri;
}

Плюс legacy-редиректы: когда я менял слаги старых постов, добавил 301-е на новые URL. Чтобы ссылки из поисковиков и чужих статей не вели в никуда.

Кэширование

Статические ассеты (JS, CSS, шрифты, картинки) отдаются с заголовком:

Cache-Control: public, max-age=31536000, immutable

Год кэша, immutable - браузер даже не пытается перепроверять. Astro генерирует файлы с хешами в именах, поэтому при новом деплое URL меняется и кэш инвалидируется автоматически.

Security headers

Базовый набор:

X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin

Не параноидальная конфигурация, но базовые вещи закрыты.


TypeScript

tsconfig наследует astro/tsconfigs/strict. Строгий режим. Alias @/* маппится на src/* - чтобы не писать ../../../components/.

{
  "extends": "astro/tsconfigs/strict",
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

Для блога и лендинга строгий TypeScript может показаться overkill. Но когда у тебя двуязычные конфиги, Content Collections и куча переиспользуемых компонентов - он ловит ошибки, которые иначе всплыли бы в продакшне.


Конфигурация Astro

// astro.config.mjs
export default defineConfig({
  site: 'https://naderevo.com',
  // ...
});

site - это не декорация. От него зависят canonical URL, sitemap, OG-теги. Без него половина SEO-разметки сломается или будет генерировать относительные пути вместо абсолютных.


Честные ограничения

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

Корневой / не индексируется

Служебная redirect-страница на / - SEO-компромисс. Поисковик видит noindex и не считает её за контентную. Весь SEO-вес идёт на /ru/ и /en/. Это осознанное решение, но оно означает, что при заходе на голый домен пользователь видит не контент, а редирект.

OG-картинки - универсальные

Сейчас у сайта одна OG-картинка на все страницы. Для CTR в соцсетях уникальная картинка на каждую статью работает заметно лучше. Это в планах, но пока не сделано.

Webvisor и clickmap

Яндекс.Метрика умеет записывать сессии (Webvisor) и строить карту кликов. Я это не включаю - оно тяжелее для клиента.


Итого

Весь сайт - это:

  • Astro 6 собирает статику

  • Markdown-файлы для контента

  • TypeScript в strict-режиме

  • Nginx отдаёт dist

  • Certbot для SSL

  • Яндекс.Метрика с cookie-consent

  • SEO-разметка: canonical, hreflang, JSON-LD, sitemap

VPS, Nginx, статические файлы. Это работало 10 лет назад, работает сейчас и будет работать через 10 лет.


Поддержка

Если хотите поддержать автора - подписывайтесь на мой Telegram-канал "На Дерево".