Как создан веб-сайт Mind.com: Архитектура на Nuxt 4
Технический обзор маркетингового сайта Mind.com — Nuxt 4 SSR, @nuxt/content, i18n, серверные маршруты Nitro и компромиссы, стоящие за каждым выбором.
Как создан веб-сайт Mind.com: Архитектура на Nuxt 4

Это пошаговое описание того, как на самом деле создан mind.com, и почему мы сделали тот или иной выбор. Это маркетинговый сайт — посадочная страница, блог, юридические страницы и форма обратной связи — поэтому интересная часть не в какой-то одной технологии. Она заключается в том, где мы провели границы: что отрисовывается на сервере, что отправляется в браузер и что остается скриптом времени сборки. Если решение было компромиссом, а не очевидным выигрышем, мы так и говорим.
Сайт представляет собой один пакет в монорепозитории pnpm (репозиторий называется InterMIND). Он работает на Nuxt 4 и развертывается на Vercel. Здесь нет ничего экзотического; ценность заключается в сочетании элементов.
Почему Nuxt 4 с SSR, а не статическая сборка
Маркетинговый сайт мог бы быть чисто статическим. Вместо этого мы выполняем рендеринг на сервере, используя движок Nuxt Nitro с предустановкой vercel, и причины этого вполне конкретны, а не абстрактны.
У нас есть форма обратной связи, которая отправляет данные на бэкенд, маршрутизация для семи языков по локалям, а также контент, который меняется достаточно часто, чтобы мы не хотели думать о том, какие страницы устарели. SSR позволяет одной кодовой базе обрабатывать маркетинговые страницы, локализованные маршруты и конечную точку формы без привязки отдельного сервиса к статическому пакету. Nitro компилирует сервер в функции Vercel во время сборки, поэтому мы получаем рендеринг на сервере без необходимости запускать или поддерживать собственный сервер.
Компромисс честен: SSR означает, что есть работа во время запроса, которой у полностью статического сайта не было бы. Для сайта такого размера эти затраты невелики, и взамен мы избегаем проблем координации при связывании статического фронтенда с отдельным API. Если бы это был сайт документации на 10 000 страниц без динамического контента, статическая генерация была бы лучшим решением. Это не так, поэтому SSR выигрывает.
Контент: @nuxt/content и MDC
Контент блога и юридических страниц — это файлы Markdown в каталогах content/blog/ и content/legal/. Мы используем @nuxt/content v3, который разбирает эти файлы в локальную базу данных better-sqlite3 во время сборки и позволяет нам запрашивать их как данные. Форма Frontmatter обеспечивается схемами Zod в content.config.ts, поэтому пост без title или с некорректной date приведет к сбою сборки, а не к выпуску нерабочего контента.
Блог написан на MDC — Markdown с компонентами. Выноска представляет собой :::tip{title="..."}, а не чистый HTML, что сохраняет исходный код читаемым и позволяет нам контролировать рендеринг в одном месте (компонент ProseTip) вместо того, чтобы разбрасывать разметку по каждому посту.
Хранение контента в виде файлов в репозитории означает, что он версионируется, рецензируется в запросах на слияние и поддается сравнению. Нет отдельной CMS для входа и базы данных для резервного копирования. Обратная сторона этого ограничения: редактирование требует коммита, поэтому это подходит для команды, которая уже работает в Git, а не для большого нетехнического редакционного персонала.
Интернационализация и как работает перевод
Сайт поставляется на семи локалях — английском, испанском, португальском, французском, немецком, русском и китайском — через @nuxtjs/i18n со стратегией prefix_except_default. Английский язык обслуживается из корня; каждая другая локаль находится под собственным префиксом пути. Строки пользовательского интерфейса хранятся в app/locales/<code>.json.
Перевод — это двухэтапная задача, и мы разделяем эти этапы. Строки пользовательского интерфейса и контент переводятся двумя скриптами Node — scripts/i18n-translate-ui.ts и scripts/i18n-translate-content.ts — созданными на базе AI SDK с моделями Anthropic. Они запускаются как этап авторской работы во время сборки, а не во время запроса: человек запускает их, просматривает результат и коммитит его. Переводы являются обычными файлами в репозитории, как и все остальное.
Это намеренная граница. Отсутствует перевод во время выполнения, нет вызова языковой модели в реальном времени, когда посетитель загружает страницу. Переводы генерируются заранее и обслуживаются как простые локализованные маршруты, что делает рендеринг предсказуемым и позволяет нам вычитывать их до того, как что-либо будет опубликовано.
Система дизайна: Tailwind v4 и @nuxt/ui
Стилизация осуществляется с помощью Tailwind CSS v4 поверх @nuxt/ui v4. Новые посетители по умолчанию попадают в темный режим; фон страницы — это намеренно почти черный цвет, #0a0b0d, а не чистый черный. Каждая страница и компонент созданы для работы как в светлом, так и в темном режиме — это ограничение, которого мы придерживаемся, а не второстепенная мысль, поэтому ничто не кодирует цвет, предназначенный только для светлого режима.
Опираясь на @nuxt/ui, мы наследуем доступные, согласованные компоненты вместо того, чтобы перестраивать кнопки, элементы управления форм и оверлеи с нуля. Цена этого — зависимость, чьим соглашениям мы следуем, а не полностью индивидуальная система, что для маркетингового сайта является правильной стороной этого компромисса.
Бэкенд: серверные маршруты Nitro и Pipedrive
Бэкенд невелик и находится в том же проекте, как серверные маршруты Nitro в каталоге server/:
server/api/submit-form.post.tsобрабатывает форму обратной связи.server/api/health.get.tsвыполняет проверку работоспособности.server/api/__sitemap__/urls.tsпредоставляет данные для карты сайта.server/routes/llms.txt.tsиllms-full.txt.tsпредоставляют машиночитаемые сводки для AI-краулеров.server/middleware/содержит сквозную логику, включая обработчик 410-gone для устаревших URL и обработку аудитории.
Когда кто-то отправляет форму обратной связи, лид отправляется в Pipedrive через server/utils/pipedrive.ts, который создает человека и лид. Это единственная интеграция с CRM и единственное место, куда попадают данные формы. Нет ни очереди, ни конвейера данных, ни объектного хранилища между ними — конечная точка проверяет ввод и вызывает один API. Такая прямая связь означает, что очень мало что может сломаться и очень мало о чем нужно беспокоиться, когда что-то происходит.
Также, намеренно, на маркетинговом сайте нет аутентификации пользователей. Это брошюра и форма обратной связи, а не приложение; добавление логина и сессий создало бы дополнительную поверхность, которую пришлось бы защищать без какой-либо выгоды.
Наблюдаемость: Sentry и PostHog, с управлением согласием
Мы используем два инструмента наблюдаемости, и они выполняют разные задачи.
Sentry (@sentry/nuxt) перехватывает ошибки. Он подключается как модуль времени сборки и отключается в разработке, чтобы локальный шум не достигал его. Его задача — сообщать нам, когда что-то ломается в продакшене.
PostHog (nuxt-posthog) обрабатывает аналитику продукта, и его поведение в отношении согласия стоит описать подробнее. Он по умолчанию отключен. Ничего не фиксируется, пока платформа согласия Usercentrics не предоставит разрешение, после чего app/plugins/posthog-consent.client.ts включает его. По умолчанию отслеживание отключено; согласие включает его, а не наоборот. PostHog занимается аналитикой, а Sentry — ошибками — это полная картина, без отдельного менеджера тегов или стороннего слоя аналитики.
SEO и AI-краулеры
SEO обрабатывается с помощью @nuxtjs/sitemap и @nuxtjs/robots для карты сайта и директив robots. Перенаправления и заголовки кеша — включая длинный список перенаправлений устаревших URL, которые сайт накопил — находятся в vercel.json, близко к месту, где они вступают в силу на периферии.
Упомянутые выше маршруты llms.txt и llms-full.txt — это дань тому, как сейчас читается веб: они предоставляют AI-краулерам чистую, структурированную сводку сайта вместо того, чтобы позволять им парсить отрисованные страницы. Это дешево в обслуживании и означает, что машины, читающие сайт, получают его точную версию.
Развертывание
Все развертывается на Vercel. Пресет vercel Nitro превращает серверные маршруты в функции Vercel, а страницы — в результат серверного рендеринга, так что git push становится развертыванием без необходимости поддерживать отдельный конвейер сборки и доставки. Находясь внутри монорепозитория pnpm, mind.com разделяет инструментарий и дисциплину блокировочных файлов с другими пакетами, оставаясь при этом независимо развертываемым.
Суть
Ничего из этого не является необычным, и в этом вся суть. Nuxt 4 предоставляет нам SSR без запуска серверов; @nuxt/content хранит посты и юридические страницы в виде файлов, доступных для проверки; i18n и скрипты перевода локализуют сайт как шаг сборки, который мы можем вычитать; тонкий бэкенд Nitro передает лиды в Pipedrive; Sentry и PostHog следят за сбоями и поведением, при этом аналитика отключена, пока не будет получено согласие.
Сайт легко понять, потому что мы минимизировали количество движущихся частей и поместили каждую из них на свое место. Для маркетингового сайта скучное и понятное всегда лучше хитрого.