Недавно мне посчастливилось развернуть Next.js на сервере с помощью PM2. Этот способ я не нашел в документации фреймворка, хотя считаю его довольно удобным, при этом гайдов по теме оказалось очень мало. Рассказываю, как всё сделать, и привожу рабочие примеры.

Заманчивое предложение

В конце весны в команде появилась задача на разработку веб-приложения с игровыми механиками для одного крупного клиента. К тому моменту давно хотел попробовать Next.js, но руки всё никак не доходили. В этот раз появился триггер — в официальной документации по React с недавних пор настойчиво предлагают начинать проекты сразу на фреймворке, и теперь я не мог отказаться.

Администраторы нашего севера сразу же предложили уйти от способов деплоя из документации (там их всего три вместе со статическим экспортом), и вместо них распробовать демонизацию кода при помощи PM2. Про эту технологию раньше никогда не слышал, а поиск в интернете не дал ясных инструкций, как связать её с Next.js. От этого изучить заморскую штуковину стало только интереснее, и теперь я могу поделиться всеми настройками и болями от их создания.

Работаем с PM2

Для тех людей, вроде меня, кто никогда не слышал про PM2, — это менеджер процессов, с помощью которого можно запускать приложения на NodeJS. Каждое такое приложение-процесс можно перезапускать, останавливать и мониторить отдельно от другого, а ещё с PM2 удобно распределять нагрузку: есть режим кластера.

После того, как поставили пакет PM2 на сервере (в моём случае под пользователя, с которого будет выполняться деплой), заходим в директорию нашего Next.js приложения и создаём в корне файл ecosystem.config.js. Там будут лежать настройки, которые PM2 возьмёт для запуска процесса. Внутрь добавляем два главных поля:

module.exports = {
  apps: [
    {
      name: 'Моё прекрасное приложение',
      script: 'node_modules/next/dist/bin/next'
    }
  ]
}

В name указываем название приложения, оно появится в списке процессов. Следующий затем script содержит путь, куда PM2 постучится для запуска Next.js. В своём конфиге я дополнительно указал порт 4000 вместо 3000 — это перестраховка, чтобы не случилось конфликтов за дефолтный. Дополнительно можно указать exec_mode: 'cluster' для распределения нагрузки, подробнее об этом можно почитать тут. Вот финальный вид:

module.exports = {
  apps: [
    {
      name: 'Моё прекрасное приложение',
      script: 'node_modules/next/dist/bin/next',
      args: '-p 4000',
      exec_mode: 'cluster',
      instances: 'max'
    }
  ]
}

На этом всё, теперь команда pm2 start приведет к запуску того и с теми настройками, что мы указали в корне внутри ecosystem.config.js.

Nginx — заводись!

С Nginx пришлось повозиться: сперва казалось непонятным, на какой файл вести пользователя. Но чтобы всё завелось, вести нужно на порт, а не на файл. Поскольку я выбрал 4000 вместо дефолтного 3000, то два важных нам кусочка nginx.conf обрели такой вид (всё остальное опустил в троеточиях):

...

upstream nextjs_upstream {
  server 127.0.0.1:4000;
  keepalive 64;
}

...

location / {
  ...
  proxy_pass http://nextjs_upstream/;
  proxy_redirect off;
  proxy_read_timeout 240s;
}

...

С этими настройками 502-я ошибка навсегда ушла, и Nginx стал корректно отрабатывать запросы.

Запускаем Next.js

В документации по деплою приложений на Next.js с помощью Node.js сервера нам приводят две главные команды:

npm run build 
npm run start

После start запускается сервер с поддержкой всех Next.js фич. Но для нас это не актуально, потому что запускаем мы процесс PM2:

npm run build
pm2 start

Здесь важно вот что: если мы заливаем код повторно и снова выполняем npm run build, то PM2 процесс нужно остановить командой pm2 stop <название приложения>. Без этого у меня жил коварный баг, который я не мог отловить несколько дней: пайплайн то успешно переходил в passed, то спонтанно падал по таймауту на этапе npm run build. Причём изменения подгружались на сайт, что ещё больше путало.

Пока искал в интернете, наткнулся на множество тем в духе Next build hangs forever, но моего решения не нашел. Позже вспомнил, что PM2 процесс как работал, так и работает во время сборки, и фикс этого нюанса навсегда избавил меня от проблемы.

Вот и всё! Хотя поначалу деплой Next.js таким способом казался тернистым, сейчас я считаю это довольно удобным и быстрым вариантом запускать будущие приложения на сервере. Надеюсь, эта статья поможет вам комфортно развернуть свои работы на готовых примерах.

Комментарии (3)


  1. andreysam
    12.06.2024 08:55

    Можно дополнительно использовать standalone билд, чтобы избавиться от node_modules, однако надо проверять на практике, сколько места это оптимизирует


  1. Nik1984z
    12.06.2024 08:55

    А чем pm2 принципиально лучше стандартного лунуксового systemd сервиса?


    1. Neoldian
      12.06.2024 08:55

      Кластеризация из коробки, и zerodowntime restart. Из того что первое в голову приходит.