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

Оглавление
Как всё началось: боль, знакомая каждому разработчику
MVP: инструмент, который работал только для меня
От инструмента — к сервису: что стало триггером
Архитектура и технологии: как я всё это построил
Основные модули
API и интеграции
Проблемы, с которыми столкнулся
Техническое развитие
Техническая архитектура монетизации
Как всё началось: боль, знакомая каждому разработчику
В какой-то момент я понял, что 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 до реализации стриминговых ответов.
Главный урок: даже простая идея «собрать всё в одном месте» может превратиться в серьёзный технический вызов, если подходить к реализации основательно.
Salamander174
Ах как чётко просматривается работа v0 особенно видно по стеку и дизайну)
Более того собирать все это самостоятельно не было ни какого смысла аналогичных и более развитых и что немало важно бесплатных инструментов на рынке просто тьма