Как задеплоить Nuxt 3 приложение

Nuxt 3 — не просто фреймворк для страниц: внутри него живёт серверный движок Nitro, который решает, что отдать сразу готовым HTML, а что дорисовать на сервере при каждом запросе. Из-за этого деплой Nuxt выглядит сложнее, чем публикация статики: нужно выбрать режим, правильно отдать порт и не вынести секреты в браузер. Ниже — путь от папки с кодом до рабочей HTTPS-ссылки на Dockbox без аренды сервера, nginx и ручных сертификатов.

Два режима Nuxt: что вы публикуете на самом деле

Nuxt 3 собирается в двух принципиально разных режимах, и от выбора зависит всё остальное.

Статика (SSG). Команда nuxt generate заранее превращает страницы в готовые HTML-файлы и складывает их в .output/public. Это обычный набор файлов: он открывается мгновенно, ничего не держит процесс и не может упасть.

Сервер (SSR). Обычный nuxt build оставляет серверную часть: приложение становится Node-приложением на движке Nitro, которое слушает порт и отвечает на каждый запрос. Только в этом режиме работают папка server/api, запросы к базе, обработка форм и контент, вычисляемый под конкретного посетителя.

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

Шаг 1. Соберите статику локально или отдайте сборку платформе

Для статического варианта запустите на своей машине:

npm run generate

Nuxt положит готовые страницы в .output/public, и содержимое этой папки — ваш сайт. Его можно загрузить архивом, и платформа отдаст файлы по публичной HTTPS-ссылке. Это единственный случай, когда сборка идёт на вашей машине: в серверном режиме сборку выполняет платформа, а вы загружаете исходники. Чтобы обновить статический сайт, сгенерируйте страницы заново и залейте новую версию — ссылка останется прежней.

Шаг 2. Оставьте порт и адрес платформе

В серверном режиме сайт откроется только если приложение слушает там, где его ждут: порт берётся из переменной окружения PORT, а адрес должен быть 0.0.0.0, а не localhost. Nitro делает это правильно по умолчанию, поэтому ваша задача — убрать то, что вы дописали сами.

Проверьте nuxt.config и .env на жёстко вписанный порт 3000 и хост localhost:

nitro: {
  devServer: { port: 3000 }
}

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

Шаг 3. Пропишите пресет и start-скрипт

Сборка Nuxt кладёт приложение в папку .output, и запускается оно из файла .output/server/index.mjs. Убедитесь, что в nuxt.config не переопределён пресет Nitro — по умолчанию нужен node-server:

nitro: {
  preset: 'node-server'
}

Затем добавьте в package.json явный start, чтобы платформа точно знала, чем поднимать проект:

"scripts": {
  "build": "nuxt build",
  "start": "node .output/server/index.mjs"
}

Скрипт build обычно уже есть, и платформа выполнит его сама при публикации — собирать проект у себя в серверном режиме не нужно.

Шаг 4. Разделите runtimeConfig на серверную и публичную части

В Nuxt значения из runtimeConfig делятся на две группы, и разница здесь принципиальная.

Внутри public — всё, что уезжает в браузер вместе со страницей. Эти значения видит любой посетитель, поэтому там место только безобидным вещам: адрес внешнего API, имя проекта.

runtimeConfig: {
  apiSecret: '',
  public: {
    apiBase: '/api'
  }
}

Снаружи public — ключи, токены и строки подключения к базе. Они остаются на сервере и в браузер не попадают. Сами значения в код не вписывайте: их задают при настройке проекта, они передаются как переменные окружения и хранятся зашифрованно. Nuxt подхватывает их по своим правилам: серверное значение apiSecret читается из NUXT_API_SECRET, публичное apiBase — из NUXT_PUBLIC_API_BASE.

NUXT_API_SECRET=super-secret
NUXT_PUBLIC_API_BASE=https://api.example.com

Файл .env при этом ни в архив, ни в репозиторий не кладите: всё, что в нём лежит, переносится в раздел переменных окружения панели.

Шаг 5. Загрузите код и получите ссылку

Проект можно отправить ZIP-архивом или подключить GitHub-репозиторий, включая приватный. Если загружаете исходники, не включайте в архив node_modules, .nuxt и .output — зависимости платформа поставит сама, а лишние папки только раздувают загрузку.

Дальше платформа собирает проект, запускает его и отдаёт публичную HTTPS-ссылку. Логи и статус публикации видны в панели в реальном времени, поэтому ошибка сборки проявляется сразу, а не пустой страницей. Обновление уезжает тем же путём, адрес остаётся прежним.

Сколько это стоит и как работают контейнеры

Своим сервером и nginx заниматься не нужно: контейнеры работают постоянно, без «засыпания» и без ограничений по часам. Упавший контейнер платформа перезапускает сама, а логи остаются в панели.

Starter — 990 ₽/мес. 1 vCPU, 2 GB RAM, 20 GB SSD. Первый месяц бесплатный. Одного Nuxt-проекта с серверным рендерингом это покрывает с запасом.

Pro — 2 490 ₽/мес. 2 vCPU, 4 GB RAM, до пяти проектов — если проектов несколько или нужен запас ресурсов.

Team — 4 990 ₽/мес — для команд, которым нужен запас и SLA.

Свой Dockerfile поддерживается, но не обязателен: Nuxt платформа распознаёт по package.json. Тарифы — на dockbox.host.

Частые вопросы

Чем отличается nuxt generate от обычной сборки?

nuxt generate вычисляет страницы заранее и записывает их готовыми файлами — получается статический сайт, которому не нужен запущенный процесс. Обычный nuxt build оставляет серверную часть, и сайт становится Node-приложением, которое слушает порт и отвечает на каждый запрос. Серверные маршруты из server/api, доступ к базе и контент под конкретного посетителя существуют только во втором варианте.

Можно ли обойтись без Vercel?

Да. Nuxt не привязан к одной платформе: в серверном режиме это обычное Node-приложение, а после nuxt generate — обычная папка готовых страниц. Dockbox принимает оба варианта: загружаете код архивом или из GitHub и получаете публичную HTTPS-ссылку.

Сборка проходит, а ссылка не открывается — почему?

Чаще всего порт или адрес заданы вручную: приложение слушает 3000 или localhost, тогда как платформа ждёт ответа на своём порту из PORT и на 0.0.0.0. Уберите эти значения из nuxt.config и .env. Вторая причина — приложение вообще не стартовало, и это прямым текстом написано в логах панели.

Куда девать .env?

Файл не кладите ни в архив, ни в репозиторий. Значения вводите при настройке проекта: они передаются как переменные окружения и хранятся зашифрованно. Nuxt подхватывает их в runtimeConfig по своим правилам именования — серверное apiSecret из NUXT_API_SECRET, публичное apiBase из NUXT_PUBLIC_API_BASE.

Попадают ли ключи из runtimeConfig в браузер?

Всё, что лежит под public, уезжает в браузер вместе со страницей, и любой посетитель может эти значения прочитать. Остальной runtimeConfig остаётся на сервере и наружу не отправляется. В статическом режиме сервера нет вообще, поэтому всё, что вшито в сгенерированные страницы, становится публичным — секретные ключи в статическую сборку попадать не должны.