Next.js из коробки умеет и отдавать готовые страницы, и рендерить их на сервере при каждом запросе, и обновлять в фоне. Но чтобы приложение открылось по публичной ссылке, нужно правильно выбрать режим сборки, не зашивать порт, разложить переменные окружения по двум разным адресам и не забыть про standalone-вывод. Ниже — по шагам, что проверить в проекте до публикации и как получить рабочую HTTPS-ссылку на Dockbox без аренды сервера, nginx и ручных сертификатов.
Три режима Next.js и почему от них зависит деплой
Одна и та же папка с кодом может превратиться в очень разные артефакты, и деплой каждый раз выглядит иначе.
SSG — статические страницы. При output: 'export' команда next build заранее считает все страницы в HTML и складывает их в папку out. Это обычный набор файлов: открывается мгновенно, процесс держать не нужно.
SSR — рендер на каждый запрос. Обычная сборка оставляет серверную часть: приложение становится Node-процессом, который слушает порт и отдаёт HTML под конкретного посетителя. Только здесь работают route handlers, cookies() и данные из базы.
ISR — обновление в фоне. Страница отдаётся статикой, но раз в N секунд (revalidate) пересобирается на сервере. Это требует запущенного процесса и постоянного кеша — из статического экспорта ISR недоступен.
Плюс отдельный build-этап. Независимо от режима next build — самая тяжёлая часть: минификация, tree-shaking, генерация страниц. Он ест память и минуты, а при каждом обновлении повторяется в контейнере.
Шаг 1. Проверьте скрипты в package.json
Платформа смотрит на package.json, чтобы понять, чем собирать и чем запускать проект. Нужны два скрипта:
"scripts": {
"build": "next build",
"start": "next start"
}
Если проект создавали через create-next-app, они уже на месте. Учтите: start нужен только серверному режиму — статическому экспорту запускать нечего.
Шаг 2. Не вписывайте порт — он приходит из PORT
Next.js читает порт из переменной PORT и слушает 0.0.0.0 по умолчанию, поэтому в коде порт указывать не нужно. Проблема появляется, когда его дописывают руками:
"start": "next start -p 3000"
Захардкоженный порт — причина номер один, по которой ссылка после публикации не открывается: контейнеру порт выдаёт платформа, а приложение упрямо слушает свой. Оставьте next start без флагов, а если поднимаете standalone-сервер — не трогайте server.js, он уже читает PORT и HOSTNAME из окружения.
Шаг 3. Выберите режим: статика или сервер
Если сайт только показывает контент — лендинг, блог, портфолио, документация — его можно выпустить набором готовых файлов:
const nextConfig = {
output: 'export'
}
module.exports = nextConfig
Статический экспорт ломается, если в проекте есть route handlers, middleware или ISR: next build упадёт прямым текстом. Есть база, авторизация или данные под посетителя — оставляйте обычный режим с next start.
Шаг 4. Перейдите на standalone-сборку
Для серверного режима попросите Next.js собрать минимальный автономный вывод — так в контейнер попадёт только нужное, без всего node_modules:
const nextConfig = {
output: 'standalone',
images: { unoptimized: true }
}
После сборки приложение запускается одним файлом:
npm run build
node .next/standalone/server.js
Здесь есть тонкость: в .next/standalone не попадают статика и публичные файлы. Их нужно положить внутрь руками — .next/static в .next/standalone/.next/static, а public рядом с server.js, иначе страницы откроются без стилей и картинок. В архив с исходниками node_modules, .next и out не кладите: зависимости платформа поставит сама, а сборку выполнит в контейнере.
Шаг 5. Разделите переменные: NEXT_PUBLIC_* запекаются в бандл
В Next.js переменные окружения делятся на две группы, и разница принципиальная.
Без префикса (DATABASE_URL, STRIPE_SECRET_KEY) значение живёт только на сервере и читается через process.env. В браузер оно не уезжает.
С префиксом NEXT_PUBLIC_ значение попадает в клиентский бандл на этапе next build. Его видит любой посетитель, поэтому там место только безобидному: адрес API, идентификатор проекта.
NEXT_PUBLIC_API_URL=https://api.example.com
DATABASE_URL=postgres://user:pass@db:5432/app
Главная ловушка темы: NEXT_PUBLIC_* вшиваются в момент сборки, а не читаются в рантайме. Значение, добавленное в панели после сборки, в бандл уже не попадёт — нужна пересборка. Секреты с этим префиксом не называйте никогда: они окажутся в исходниках страницы, и это уже утечка. Файл .env не кладите ни в архив, ни в репозиторий — значения переносятся в раздел переменных окружения.
Шаг 6. Загрузите код и получите ссылку
Проект отправляется ZIP-архивом или подключением GitHub-репозитория, включая приватный. Платформа распознаёт Next.js по package.json, ставит зависимости, запускает next build и отдаёт публичную HTTPS-ссылку. Логи и статус публикации видны в панели в реальном времени, поэтому ошибка сборки проявляется сразу, а не пустой страницей. Упавший контейнер перезапускается автоматически, обновление уезжает тем же путём, а адрес остаётся прежним.
Сколько это стоит и как работают контейнеры
Своим сервером и nginx заниматься не нужно: контейнеры работают постоянно, без «засыпания» и без лимита по часам.
Starter — 990 ₽/мес. 1 vCPU, 2 GB RAM, 20 GB SSD. Первый месяц бесплатный. Next.js с серверным рендерингом укладывается в это с запасом.
Pro — 2 490 ₽/мес. 2 vCPU, 4 GB RAM, до пяти проектов — если проектов несколько или нужен запас на сборку.
Team — 4 990 ₽/мес — для команд, которым нужен запас ресурсов и SLA.
Свой Dockerfile поддерживается, но не обязателен. Тарифы — на dockbox.host.
Частые вопросы
Чем SSR отличается от SSG и ISR?
SSG считает все страницы заранее и отдаёт готовый HTML — процесс для этого не нужен. SSR рендерит страницу на сервере при каждом запросе, поэтому работает с базой, куками и персонализацией. ISR — компромисс: страница отдаётся статикой, но периодически пересобирается в фоне по revalidate. ISR и SSR требуют запущенного процесса, а output: 'export' их не поддерживает.
Почему переменная NEXT_PUBLIC_ осталась пустой после деплоя?
Потому что такие переменные вшиваются в бандл во время next build, а не читаются в рантайме. Если значение добавили в панели уже после сборки, клиентский код его не увидит — нужна пересборка проекта. Серверные переменные без префикса ведут себя иначе: их читает process.env при старте.
Обязателен ли Dockerfile для Next.js?
Нет. Платформа распознаёт Next.js по package.json и сама выполняет next build и запуск. Standalone-вывод при этом полезен: он уменьшает образ и сокращает время старта. Свой Dockerfile имеет смысл, когда нужны системные пакеты или нестандартный шаг сборки.
Сборка проходит, а ссылка не открывается — почему?
Чаще всего порт задан руками: в package.json стоит next start -p 3000, тогда как контейнер ждёт приложения на своём порту из PORT. Уберите флаг и проверьте, что standalone-сервер не переопределён. Вторая причина — приложение вообще не стартовало, и это прямым текстом написано в логах панели.
Можно ли задеплоить Next.js без Vercel?
Да. Next.js не привязан к одной платформе: в серверном режиме это обычное Node-приложение, а после output: 'export' — папка готовых страниц. Dockbox принимает оба варианта: загружаете код архивом или из GitHub и получаете публичную HTTPS-ссылку.