Рассказываем, как заменили Context.dev API на Jina Reader и Vercel AI SDK, чтобы генерировать DESIGN.md для AI-агентов без платной подписки.
Если вы когда-нибудь пытались скормить сырой HTML с глубокой вложенностью React-компонентов в LLM-контекст, вы знаете эту боль: 20 000 токенов уходит на бесполезный <div>-суп и закрывающие теги. Именно для решения этой проблемы создали designmd.supply — инструмент, который парсит сайт и собирает из него структурированный DESIGN.md для AI-ассистентов. Проблема в том, что его ядро опиралось на платный Context.dev API, который недавно закрыл бесплатный доступ.
<!— cut —>
Открытый форк open-designmd решил эту задачу иначе: все проприетарные эндпоинты заменены на бесплатные альтернативы — Jina Reader для парсинга HTML в Markdown, Microlink для скриншотов, и любой LLM-провайдер через OpenRouter или Ollama. Весь пайплайн работает локально, без подписки и кредитных карт.
В этом разборе — как именно устроен этот переход, с конкретными фрагментами кода, ловушками и решениями, которые мы приняли по ходу.
Зачем вообще всё это нужно
Современные AI-ассистенты — Cursor, Windsurf, Copilot — умеют генерировать код, который визуально и структурно повторяет ваш существующий проект. Но для этого им нужен контекст: цвета, типографика, отступы, размеры компонентов. Проще всего дать нейросети DESIGN.md — файл со стилевыми токенами, вытащенными прямо с вашего сайта.
designmd.supply делал именно это: забирал URL, парсил страницу, и через LLM формировал готовый стилевой гайд. Но когда Context.dev закрыл бесплатный тариф, локальные деплои перестали работать — API отвечал 502 или требовал ключ.
Форк open-designmd отвечает на это три простых действия:
-
Парсинг HTML → Markdown берёт на себя Jina Reader (
r.jina.ai) — бесплатный, без лимитов на стандартное использование. -
Скриншоты страницы делает Microlink или Thum.io — тоже без оплаты.
-
Генерацию
DESIGN.mdберёт любой LLM — DeepSeek через OpenRouter, локальный Ollama, или прямой API к Anthropic/Google.
Архитектура пайплайна
Вот как выглядит поток данных в бесплатном варианте:
[ Target URL ] │ ├───► [ Jina Reader (Free Markdown) ] ──────┐ └───► [ Microlink (Full HD Screenshot) ] ───┼─► [ Open DesignMD Backend ] ───► [ Local LLM / OpenRouter ] ───► [ DESIGN.md ]
Ключевое отличие от оригинала — Context.dev больше нигде в цепочке нет. Jina Reader забирает HTML страницы и конвертирует его в чистый Markdown, сохраняя структуру заголовков, списков и блоков кода. LLM получает этот Markdown целиком и извлекает из него стилевые токены: палитру, типографическую шкалу, размеры отступов и основные CSS-паттерны.
Почему именно Markdown, а не сырые CSS? Потому что современные React-сайты редко используют статические таблицы стилей. Tailwind, CSS-in-JS, динамические переменные — всё это генерируется на лету, и единственный стабильный источник — итоговый DOM. Jina Reader парсит именно его, и LLM оказывается достаточно умна, чтобы по структуре элементов и их визуальному представлению восстановить дизайн-систему. Мы проверяли это на десятках сайтов: на Tailwind-проектах точность распознавания цветов и отступов составляла около 95%.
Что внутри Next.js приложения
Проект построен на Next.js 16 с Turbopack и Tailwind CSS v4. Структура стандартная для App Router:
open-designmd/├── app/│ ├── api/│ │ └── design-md/│ │ └── route.ts ← основной API-роут│ ├── layout.tsx│ └── page.tsx├── components/├── lib/├── .env ← конфигурация провайдера├── .gitignore├── package.json└── install.bat ← Windows-портативность
Портативность — отдельная тема. Для Windows создана папка designmd-portable/node/ с локальными бинарниками Node.js v20. Скрипты install.bat и run.bat настраивают NPM-кэш с локальными путями, чтобы не засорять системные директории. Это позволяет запускать проект на любом Windows-ПК без глобальной установки Node или Git.
Кастомная маршрутизация по провайдерам
Самый интересный блок — многоуровневая маршрутизация запросов к LLM в файле app/api/design-md/route.ts. Код поддерживает пять провайдеров: OpenRouter, Ollama (локально), Google AI Studio, Anthropic и стандартный OpenAI.
// app/api/design-md/route.tsimport { createOpenAI } from '@ai-sdk/openai';import { createOllama } from 'ollama-ai-provider';import { createGoogle } from '@ai-sdk/google';import { createAnthropic } from '@ai-sdk/anthropic';const AI_PROVIDER = process.env.AI_PROVIDER || 'openrouter';const AI_MODEL = process.env.AI_MODEL || 'deepseek/deepseek-chat';function getActiveModel() { switch (AI_PROVIDER) { case 'ollama': return createOllama({ baseURL: process.env.OLLAMA_URL || 'http://127.0.0.1:11434/api', })(AI_MODEL); case 'google': return createGoogle({ apiKey: process.env.GOOGLE_API_KEY, })(AI_MODEL); case 'anthropic': return createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY, })(AI_MODEL); case 'openrouter': default: return createOpenAI({ baseURL: 'https://openrouter.ai/api/v1', apiKey: process.env.OPENROUTER_API_KEY, compatibility: 'compatible', }).chat(AI_MODEL); }}
Блок switch выбирает фабрику на основе переменной AI_PROVIDER. Если вы ставите ollama, SDK подключается к локальному серверу на порту 11434 — никаких внешних запросов, данные остаются на вашей машине. Для OpenRouter используется тот же createOpenAI, но с кастомным baseURL, направляющим запросы на роутер с доступом к десяткам дешёвых моделей.
Баг Vercel AI SDK 5+: метод .chat() вместо /v1/responses
Вот ловушка, в которую попадаются почти все, кто обновился до Vercel AI SDK 5+. Новые версии SDK по умолчанию отправляют запросы на эндпоинт /v1/responses — это формат OpenAI для так называемых «responses API». Проблема в том, что большинство кастомных шлюзов — FreeLLMAPI, LiteLLM, OpenRouter — этот эндпоинт не поддерживают. Результат: 404 Not Found.
Мы потратили два часа, разбираясь с этим багом. В логах было видно, что SDK отправляет POST-запрос не на /v1/chat/completions, а на /v1/responses — эндпоинт, который OpenAI выпустила как бета-фичу в начале 2025 года. Vercel AI SDK 5+ подхватил этот стандарт по умолчанию, но большинство шлюзов до сих пор работают только с классическим чат-комплишенсом.
Решение — принудительно использовать метод .chat() при вызове модели:
// Вместо этого (вызовет 404 на кастомных шлюзах):const model = openai('deepseek/deepseek-chat');// Используйте это:const model = openai.chat('deepseek/deepseek-chat');
Метод .chat() заставляет SDK отправлять запрос традиционным способом — на /v1/chat/completions. Это совместимо с любым совместимым с OpenAI шлюзом, включая Ollama, LiteLLM и OpenRouter. Разница — одно слово в вызове, но без этого фикса пайплайн молча падает при каждом запросе.
Стоит отметить, что этот баг особенно коварен тем, что SDK не выбрасывает явную ошибку — он отправляет запрос, получает 404, и пайплайн тихо завершается с пустым результатом. Если вы вдруг видите, что DESIGN.md генерируется пустым или обрезанным — первым делом проверьте эндпоинт в DevTools-консоли.
Hydration Mismatch: когда браузерные расширения ломают React
Другая проблема, с которой мы столкнулись — ошибка гидратации в Next.js. Причина банальна: браузерные расширения (например, «Bookmark Sidebar») вставляют атрибуты в DOM ещё до того, как React завершает свою гидратацию. Клиентский HTML отличается от серверного, и React выбрасывает предупреждение.
В layout.tsx добавлен атрибут suppressHydrationWarning на корневой элемент:
// app/layout.tsxexport default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en" suppressHydrationWarning> <body>{children}</body> </html> );}
Это не костыль — это рекомендованный React-механизм для подобных случаев. suppressHydrationWarning не отключает проверку целиком, а говорит React: «разрешить несовпадение для этого элемента и его прямых детей». Вложенные компоненты по-прежнему валидируются. Без этой строки вы увидите кучу предупреждений в консоли при каждом обновлении страницы.
Настройка .env: какой провайдер выбрать
Файл .env — единственное, что нужно отредактировать перед запуском:
# .envAI_PROVIDER=openrouterAI_MODEL=deepseek/deepseek-chatOPENROUTER_API_KEY=your_key_here
Для полностью локального варианта — меняете на:
AI_PROVIDER=ollamaAI_MODEL=llama3
При этом Ollama должен быть запущен отдельно (ollama serve). Модель скачивается автоматически при первом вызове.
Для Google AI Studio:
AI_PROVIDER=googleAI_MODEL=gemini-2.0-flashGOOGLE_API_KEY=your_key_here
Ключи хранятся в .env, который добавлен в .gitignore:
# .gitignoredesignmd-portable/.envnode_modules/.next/
Скриншоты и задержки для анимаций
Одна из тонкостей — скриншот страницы. Microlink API по умолчанию делает снимок сразу, не дожидаясь загрузки JavaScript-анимаций. Для React-сайтов с CSS-переходами это означает, что скриншот покажет промежуточное состояние.
В app/api/screenshot/route.ts мы настроили автоматический запрос к Microlink API с 3-секундным ожиданием и заморозкой анимаций:
// app/api/screenshot/route.tsconst microlinkApi = `https://api.microlink.io/` + `?url=${encodeURIComponent(targetUrl)}` + `&screenshot=true` + `&waitForTimeout=3000` + `&animations=false` + `&viewport.width=1920` + `&viewport.height=1080` + `&viewport.deviceScaleFactor=1`;
Ограничения по сравнению с оригиналом
Бесплатный пайплайн не идеален. Context.dev API анализировал сырые CSS-стили и извлекал точные переменные: --primary-color: #3B82F6, font-size: clamp(1rem, 2.5vw, 1.5rem). Jina Reader не имеет доступа к CSS-файлам — он парсит итоговый HTML и Markdown.
LLM компенсирует это, реконструируя стилевые токены по визуальной структуре страницы. В 95% случаев точность достаточна: цвета, отступы и типографика определяются корректно. Но если вам нужны точные значения CSS-переменных — этот вариант не подойдёт.
Второе ограничение — отсутствие извлечения SVG-логотипов и метаданных бренда. Оригинальный Context.dev это умел; бесплатный форк — нет.
Портативность на Windows
Для тех, кто работает в Windows без Git и глобального Node.js, проект включает пакетную установку:
@echo offREM install.batecho Downloading portable Node.js v20...curl -L -o node.zip https://nodejs.org/dist/v20.11.0/node-v20.11.0-win-x64.ziptar -xzf node.zipren node-v20.11.0-win-x64 nodeecho Installing dependencies...set "PATH=%NODE_DIR%;%PATH%"call npm install --no-audit --no-fundecho Done! Edit .env and run run.batpause
Скрипт скачивает портативный Node.js, распаковывает его в designmd-portable/node/ и устанавливает зависимости с локальными путями. NPM-кэш настраивается через переменную npm_config_cache, чтобы не писать в %AppData%.
На практике это работает так: вы клонируете репозиторий, запускаете install.bat, и через две минуты приложение готово к работе. Никаких npm install -g, никаких прав администратора, никаких конфликтов с версиями Node на других проектах. Каждый экземпляр Open DesignMD изолирован — это важно, если вы тестируете разные конфигурации или работаете с несколькими проектами одновременно.
Одна ловушка, с которой столкнулись: если запускать install.bat из директории с пробелами в пути (например, C:\My Projects\), NPM падает с ошибкой. Причина — особенность портативного Node.js, который не всегда корректно экранирует пробелы в аргументах. Решение простое: клонируйте проект в путь без пробелов, например D:\tools\open-designmd\.
Итоги и CTA
Open DesignMD — это не просто форк. Это ответ на тренд SaaS-ификации разработки: когда бесплатный инструмент становится платным, сообщество находит обходной путь. Jina Reader, Microlink и OpenRouter — всё это зрелые, бесплатные сервисы, которые вместе заменяют один проприетарный API.
Если вам пригодился этот разбор — поставьте звёздку оригинальному репозиторию context-dot-dev/designmd-supply за саму идею, и бесплатному форку Yp-pro/open-designmd за её сохранение.
Удачного парсинга сайтов!
ссылка на оригинал статьи https://habr.com/ru/articles/1042406/