Автоматизация SEO-рутины: как я собрал свой набор проверок в один инструмент

от автора

У каждого веб-разработчика есть моменты, когда рутинные задачи съедают больше времени, чем сама разработка. Проверить редиректы, оптимизировать мета-теги, убедиться, что изображения в порядке, а заголовки везде прописаны — всё это нужно делать регулярно. И если ты ведёшь проекты с нуля и под ключ, то таких задач становится десятки.

Я устал от этой рутины и решил автоматизировать задачи, создав удобный инструмент для себя. Что из этого получилось и какие технические проблемы пришлось решать — расскажу в этой статье.

rechecker

Оглавление

  1. Как всё началось: боль, знакомая каждому разработчику

  2. MVP: инструмент, который работал только для меня

  3. От инструмента — к сервису: что стало триггером

  4. Архитектура и технологии: как я всё это построил

  5. Основные модули

  6. API и интеграции

  7. Проблемы, с которыми столкнулся

  8. Техническое развитие

  9. Техническая архитектура монетизации

Как всё началось: боль, знакомая каждому разработчику

В какой-то момент я понял, что 30–40% времени уходит на одно и то же. Типичные задачи:

  • Проверить, работает ли редирект с http на https

  • Убедиться, что у всех страниц корректные мета-теги

  • Посмотреть, не забыли ли прописать ALT у изображений

  • Убедиться, что robots.txt не блокирует нужные страницы

  • Узнать, на какой CMS сделан сайт клиента или конкурента

Я использовал десятки сервисов: где-то онлайн, где-то локально, где-то даже использовал Телеграм ботов. Список ссылок на утилиты постоянно рос. А ещё хуже — он расползся по заметкам, проектам, Google Docs и личным перепискам. Это стало неудобно.

Тогда я решил: а почему бы не собрать всё в одном месте?

MVP: инструмент, который работал только для меня

Я не думал делать что-то публичное. Первой целью было просто решить свои задачи. Открыл редактор кода Cursor, написал инструкции в Rules, подключил парочку MCP tools и понеслось.

Вообще я PHP-разработчик, работающий с WordPress и Битриксом, но здесь решил, что нужно выбрать какой-то современный стек — с такими мыслями остановился на Next.js. Кто будет ругать Вайбкодинг — успокойтесь. Я давно в разработке и понимаю, что пишет мне ИИ Cursor, поэтому использую его больше как ментора и помощника, а не во всём полагаюсь на него.

Так появился MVP. Это была страничка, в которую можно вставить URL, нажать кнопку — и получить:

  • Цепочку редиректов

  • Все мета-теги (включая Open Graph и Twitter Cards)

  • Все изображения на странице с их весом, размерами и alt-текстами

  • Заголовки ответа сервера

  • Основные ошибки и рекомендации

Работало просто. Стек был такой:

  • Next.js 13 + App Router — для SSR и API

  • TypeScript — строгая типизация

  • TailwindCSS — базовая стилизация

  • Cheerio — парсинг HTML (версия 0.22.0 — проверенная временем)

  • Axios — сетевые запросы с кастомными заголовками

  • Whois — получение информации о доменах

  • XML2JS — парсинг sitemap и других XML

  • Sharp — обработка изображений

Вот пример кода, который определяет цепочку редиректов:

const checkRedirects = async (url: string) => {   const redirects = []   let currentUrl = url   let redirectCount = 0   const maxRedirects = 10    while (redirectCount < maxRedirects) {     const response = await fetch(currentUrl, {       method: 'HEAD',       redirect: 'manual'     })      redirects.push({       url: currentUrl,       status: response.status,       headers: Object.fromEntries(response.headers.entries())     })      if (response.status >= 300 && response.status < 400) {       const location = response.headers.get('location')       if (!location) break              currentUrl = new URL(location, currentUrl).toString()       redirectCount++     } else {       break     }   }    return redirects } 

Или вот кусок, который вытаскивает мета-теги:

const extractMetaTags = (html: string) => {   const $ = cheerio.load(html)      return {     title: $('title').text(),     description: $('meta[name="description"]').attr('content'),     ogTitle: $('meta[property="og:title"]').attr('content'),     ogDescription: $('meta[property="og:description"]').attr('content'),     twitterCard: $('meta[name="twitter:card"]').attr('content'),     canonical: $('link[rel="canonical"]').attr('href'),     hreflang: $('link[rel="alternate"][hreflang]').map((_, el) => ({       hreflang: $(el).attr('hreflang'),       href: $(el).attr('href')     })).get(),     schemas: $('script[type="application/ld+json"]').map((_, el) => {       try {         return JSON.parse($(el).html() || '')       } catch {         return null       }     }).get().filter(Boolean)   } } 

Это не был продукт. Это был инструмент «для себя», но я начал пользоваться им постоянно.

От инструмента — к сервису: что стало триггером

Я начал кидать ссылку коллегам. Типа: «Зацени чо наВайбкодил». Ответ был один и тот же: «Удобно, пили проект дальше». Коллеги начали пользоваться. Потом друзья, потом друзья друзей. Тогда я понял — это можно развивать.

Я сел и подумал: а что, если развить это в полноценный инструмент?

Так появился веб-сервис с набором утилит, объединенных в один интерфейс.

Архитектура и технологии: как я всё это построил

Я понимал, что инструмент может вырасти. Поэтому сразу заложил масштабируемую архитектуру:

Основной стек:

  • Frontend / Backend: Next.js 13 с App Router

  • Типизация: TypeScript

  • UI: TailwindCSS

  • БД: PostgreSQL

  • ORM: Prisma

  • Аутентификация: NextAuth.js + система API-ключей

  • Интеграции: OpenAI GPT-4 для AI-инструментов

Также предусмотрел:

  • Стриминговые ответы, чтобы не ждать полный анализ

  • Параллельную обработку URL

  • Кэширование результатов на уровне PostgreSQL

Схема базы данных

Основные таблицы:

-- ПользователиCREATE TABLE users (  id SERIAL PRIMARY KEY,  username VARCHAR(50) UNIQUE,  email VARCHAR(255) UNIQUE,  role VARCHAR(20) DEFAULT 'user',  createdAt TIMESTAMP DEFAULT NOW());-- API ключи (базовая версия)CREATE TABLE api_keys (  id SERIAL PRIMARY KEY,  key VARCHAR(255) UNIQUE,  userId INTEGER REFERENCES users(id),  usageCount INTEGER DEFAULT 0,  lastUsedAt TIMESTAMP,  revoked BOOLEAN DEFAULT FALSE);-- Логи использованияCREATE TABLE logs (  id SERIAL PRIMARY KEY,  userId INTEGER REFERENCES users(id),  service VARCHAR(100),  url TEXT,  createdAt TIMESTAMP DEFAULT NOW());-- Будущая таблица для токеновCREATE TABLE user_tokens (  id SERIAL PRIMARY KEY,  userId INTEGER REFERENCES users(id),  tokensBalance INTEGER DEFAULT 0,  tokensUsed INTEGER DEFAULT 0,  lastRefill TIMESTAMP DEFAULT NOW());

Основные модули

На данный момент реализовано 16 инструментов:

🔍 SEO-инструменты

🔄 Анализ редиректов

  • Проверка 301/302 редиректов

  • Поиск цепочек и бесконечных перенаправлений

  • Заголовки на каждом шаге

  • Определение финального статуса и URL

🏷️ Анализ мета-тегов

  • Проверка длины title/description

  • Анализ OG и Twitter Cards

  • Поиск дублирующихся тегов

  • Подсказки для улучшения

🖼️ Работа с изображениями

  • Извлечение размеров и веса

  • Проверка alt и SEO-дружественных названий файлов

  • Подсказки по lazy loading

🎯 Анализ ключевых слов

  • Плотность ключевых слов на странице

  • Распределение по заголовкам H1-H6

  • Рекомендации по оптимизации

🗺️ Проверка sitemap

  • Валидация XML-sitemap

  • Проверка доступности URL из карты сайта

  • Анализ структуры и ошибок

🔧 Техническая диагностика

// Пример комплексной проверки сайта const techAnalysis = async (url: string) => {   const domain = new URL(url).hostname      const [     sslInfo,     dnsRecords,     cdnInfo,     performance,     compression   ] = await Promise.all([     checkSSLCertificate(domain),     checkDNSRecords(domain),     detectCDN(domain),     measurePerformance(url),     checkCompression(url)   ])    return {     ssl: {       valid: sslInfo.valid,       issuer: sslInfo.issuer,       expiresAt: sslInfo.expiresAt,       daysUntilExpiration: sslInfo.daysUntilExpiration     },     dns: dnsRecords,     cdn: cdnInfo,     performance: {       loadTime: performance.loadTime,       firstByte: performance.ttfb,       httpVersion: performance.httpVersion     },     compression: compression.enabled   } } 
  • Проверка SSL, DNS, CDN

  • Анализ заголовков безопасности

  • Определение HTTP версии и кэширования

  • Проверка версий PHP

⚙️ Определение CMS и технологий

Изначально планировал использовать Wappalyzer — популярную библиотеку для определения технологий. Но столкнулся с проблемами совместимости в Next.js. Поэтому создал собственную систему детекции через паттерны:

const cmsSignatures = {   'WordPress': {     patterns: [       /wp-content\/themes/i,       /wp-includes/i,       /wp-json/i,       /wp-admin/i,       /wordpress/i     ],     metaTags: [       { name: 'generator', content: /wordpress/i }     ]   },   'Bitrix': {     patterns: [       /bitrix/i,       /bx\.js/i,       /\/bitrix\/templates/i,       /BX\.(message|ajax)/i     ]   },   'Tilda': {     patterns: [       /tilda\.js/i,       /tilda\.css/i,       /data-tilda/i,       /tildacdn/i     ]   }   // ... еще 20+ CMS и конструкторов }  // Алгоритм определения с весами const fallbackDetection = (html: string, headers: Record<string, any>) => {   for (const [cms, config] of Object.entries(cmsSignatures)) {     const matches = config.patterns.filter(pattern => pattern.test(html))     if (matches.length > 0) {       const confidence = Math.min((matches.length / config.patterns.length) * 100, 100)       if (confidence > 30) {         return { name: cms, confidence, detected_by: 'patterns' }       }     }   } } 

Дополнительная детекция:

  • Meta-теги generator и X-Powered-By заголовки

  • DNS записи (через whois пакет)

  • Технологии аналитики (Google Analytics, Яндекс.Метрика)

  • CDN провайдеры (Cloudflare, MaxCDN)

  • Фреймворки (React, Vue, Angular)

🤖 ИИ-инструменты

Интеграция с GPT-4 для:

// Рерайт текста const rewriteText = async (text: string, style: string) => {   const response = await openai.chat.completions.create({     model: "gpt-4",     messages: [       {         role: "system",         content: `Перепиши текст в стиле: ${style}. Сохрани смысл, но измени формулировки.`       },       { role: "user", content: text }     ],     temperature: 0.7   })      return response.choices[0].message.content } 
  • Рерайт текстов с разными стилями

  • Копирайтинг и генерация контента

  • Чат-помощник по SEO и аналитике

🛠️ Инструменты разработчика

JSON Formatter — форматирование и валидация JSON

try {   const parsed = JSON.parse(inputText)   const formatted = JSON.stringify(parsed, null, 2)   setResult({ valid: true, formatted }) } catch (error) {   setResult({ valid: false, error: error.message }) } 

PHP Редактор — онлайн исполнение PHP кода

SVG Спрайт генератор — использую библиотеку svg-sprite-generator:

import { createSprite } from 'svg-sprite-generator'  const generateSprite = async (svgFiles: File[]) => {   const sprites = await Promise.all(     svgFiles.map(file => file.text())   )   return createSprite(sprites) } 

SVG Encoder — конвертация в data URI для CSS
Конвертер эмодзи — HTML коды для верстки
Excel экспорт — через библиотеку xlsx для сохранения результатов

API и интеграции

Система API-ключей

Базовая система для внешнего доступа:

// Простая валидация API ключа const validateApiKey = async (apiKey: string) => {   const keyRecord = await prisma.apiKey.findUnique({     where: { key: apiKey },     include: { user: true }   })    if (!keyRecord || keyRecord.revoked) {     return { valid: false, error: 'Invalid API key' }   }    // Обновляем статистику использования   await prisma.apiKey.update({     where: { id: keyRecord.id },     data: {       usageCount: { increment: 1 },       lastUsedAt: new Date()     }   })    return { valid: true, userId: keyRecord.userId } } 

Текущие возможности API:

  • Базовая аутентификация по ключу

  • Отслеживание использования

  • Логирование запросов

  • CORS поддержка

В планах:

  • Токенная система оплаты

  • Ограничения по количеству запросов

  • Разные тарифы доступа

И так далее, не буду здесь устраивать документацию.

Проблемы, с которыми столкнулся

1. Проблемы с Wappalyzer в Next.js

Проблема: Wappalyzer требует браузерное окружение и конфликтует с SSR.

Решение: Создал собственную систему детекции:

// Временно отключаем Wappalyzer // const TechDetector = require('web-technology-detector') // const detector = new TechDetector() // const results = await detector.url(url)  // Используем fallback detection const fallbackResult = fallbackDetection(html, headers, url) 

2. Сайты блокируют ботов

Проблема: Многие сайты возвращают 403 или блокируют запросы от серверов.

Решение:

const headers = {   'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',   'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',   'Accept-Language': 'ru-RU,ru;q=0.9,en-US;q=0.8',   'Accept-Encoding': 'gzip, deflate, br',   'DNT': '1',   'Connection': 'keep-alive' } 

3. Большой объём данных

Проблема: HTML страницы могут весить 5+ МБ, что замедляет обработку.

Решение:

const response = await axios.get(url, {   maxContentLength: 10 * 1024 * 1024, // 10MB лимит   timeout: 30000,   responseType: 'stream' })  // Обработка частями let chunks = '' response.data.on('data', (chunk: Buffer) => {   chunks += chunk.toString()   if (chunks.length > 2 * 1024 * 1024) { // 2MB достаточно для анализа     response.data.destroy()   } }) 

4. Производительность

Проблема: Анализ может занимать 30+ секунд для больших сайтов.

Решение: Параллельная обработка и стриминг:

const analyzeWebsite = async (url: string) => {   // Запускаем все проверки параллельно   const [redirects, metaTags, images, tech] = await Promise.allSettled([     checkRedirects(url),     checkMetaTags(url),     checkImages(url),     checkTechnologies(url)   ])    // Отправляем результаты по мере готовности   return {     redirects: redirects.status === 'fulfilled' ? redirects.value : null,     metaTags: metaTags.status === 'fulfilled' ? metaTags.value : null,     images: images.status === 'fulfilled' ? images.value : null,     technologies: tech.status === 'fulfilled' ? tech.value : null   } } 

Техническое развитие

Проект продолжает развиваться. Основные направления работы:

Архитектурные улучшения

  • Система ограничений — реализация токенной логики

  • Пользовательские дашборды — интерфейс управления аккаунтом

Новые функции

  • Массовый анализ URL — проверка сразу 100+ страниц

  • Telegram-бот для экспресс-проверок

  • Chrome-расширение для анализа прямо в браузере

  • Экспорт в PDF — генерация отчетов

Производительность и масштабирование

  • GraphQL API для гибких запросов

  • Real-time мониторинг изменений сайтов

  • Кэширование для ускорения повторных запросов

  • Очередь задач для тяжелых операций

Техническая архитектура монетизации (планы)

Концепция токенной системы

Рассматриваю возможность внедрения системы токенов, где каждая операция будет «стоить» определенное количество условных единиц:

const serviceCosts = {   'redirect-check': 1,        // 1 токен   'meta-tags': 2,            // 2 токена     'tech-analysis': 5,        // 5 токенов   'ai-rewrite': 10,          // 10 токенов   'full-audit': 20           // 20 токенов }  const processRequest = async (service: string, userId: number) => {   const cost = serviceCosts[service]   const user = await getUserTokens(userId)      if (user.tokensBalance < cost) {     throw new Error('Insufficient tokens')   }      await deductTokens(userId, cost)   return await executeService(service) } 

Предварительные лимиты (концепция):

  • Базовый: 100 токенов/мес

  • Расширенный: 500 токенов/мес

  • Профессиональный: 2000 токенов/мес

Текущее состояние

На данный момент все функции доступны без ограничений. Это позволяет:

  • Собрать пользовательский фидбек

  • Протестировать нагрузку на сервер

  • Проанализировать паттерны использования

Техническая мотивация статьи

Хочется поделиться опытом разработки pet-проекта с технической стороны:

  • Какие архитектурные решения принимал и почему

  • С какими проблемами столкнулся при выборе стека

  • Как решал вопросы производительности и масштабирования

  • Какие компромиссы приходилось делать

Если интересна тема разработки подобных инструментов — готов обсуждать в комментариях.

Обратная связь

Если интересны технические детали реализации или есть вопросы по архитектуре — пишите в комментарии или в Telegram: t.me/dobryninoleg

Всегда рад обсудить опыт разработки подобных инструментов.

Заключение

Проект не изобретает ничего принципиально нового — он просто объединяет существующие инструменты проверки в один интерфейс. Но именно так, я считаю, и появляются по-настоящему полезные решения: когда начинаешь автоматизировать свою рутину, а потом понимаешь, что у коллег те же боли.

За несколько месяцев разработки я прошёл путь от простого скрипта до архитектуры, которая может масштабироваться. Самое интересное — каждая техническая проблема заставляла искать нестандартные решения, от создания собственной системы детекции CMS до реализации стриминговых ответов.

Главный урок: даже простая идея «собрать всё в одном месте» может превратиться в серьёзный технический вызов, если подходить к реализации основательно.


ссылка на оригинал статьи https://habr.com/ru/articles/929154/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *