Payload CMS 3.89.0: критические исправления Next.js, безопаснее Jobs и большие загрузки без съедания RAM
10 сентября вышел Payload CMS 3.89.0. По номеру версии это обычный минорный релиз ветки 3.x, но пропускать его не стоит: внутри обновление Next.js с исправлением двух критических RCE, изменение доступа к Jobs, оптимизация загрузки больших файлов и несколько неприятных исправлений на уровне PostgreSQL, MongoDB и админки.
Разберём, что изменилось и на что обратить внимание перед обновлением.
Next.js обновлён до 16.3.3 — и это не просто dependency bump
В Payload 3.89.0 Next.js и связанные @next/* пакеты в core и официальных шаблонах обновлены с 16.3.0 до 16.3.3.
Причина — security release Next.js от 25 августа. В 16.3.3 закрыты две уязвимости критического уровня.
Первая позволяет получить unauthenticated remote code execution через Image Optimization API при обработке специально подготовленного AVIF-файла. Проблема находится в используемой для обработки изображений библиотеке libheif.
Вторая тоже позволяет выполнить код без аутентификации, но касается Next.js-серверов под Windows, одновременно использующих Pages Router и App Router без Cache Components. Linux и macOS этой конкретной проблеме не подвержены.
Для Payload это особенно актуально: начиная с третьей версии CMS фактически живёт внутри Next.js-приложения.
Если проект использует Payload 3.x, стоит проверить не только версию payload, но и прямую зависимость next в своём package.json. Обновление пакетов Payload само по себе не гарантирует, что закреплённая в приложении версия Next.js автоматически станет 16.3.3.
Jobs теперь закрыты по умолчанию
Это единственное изменение в 3.89.0, которое авторы прямо пометили как breaking change.
Внутренняя коллекция payload-jobs раньше могла быть доступна через обычные CRUD-операции. Теперь generic-доступ к ней по REST, GraphQL и Local API с overrideAccess: false запрещён по умолчанию.
Изменение логичное. В Job можно хранить входные данные, результаты выполнения, ошибки и логи. Это не та коллекция, которую разумно случайно выставлять наружу.
При этом нормальная работа очереди не меняется. Специализированные методы вроде payload.jobs.queue(), payload.jobs.run() и payload.jobs.cancel() продолжают использовать отдельные правила доступа Jobs Config.
Проблема возникнет у проектов, которые напрямую читали или изменяли payload-jobs.
Например, если вы выводили очередь Jobs в собственной админке через обычный Collection API, после обновления такой код может получить отказ в доступе.
Разрешения теперь нужно задавать явно через jobsCollectionOverrides. Например, только для чтения администраторами:
import { buildConfig } from 'payload'
export default buildConfig({
jobs: {
jobsCollectionOverrides: ({ defaultJobsCollection }) => ({
...defaultJobsCollection,
access: {
...defaultJobsCollection.access,
read: ({ req }) => Boolean(
req.user?.roles?.includes('admin')
),
},
admin: {
...defaultJobsCollection.admin,
hidden: false,
},
}),
},
})Это хороший пример изменения, которое формально ломает обратную совместимость, но делает поведение Payload безопаснее по умолчанию. В актуальной документации Payload generic create/read/update/delete для payload-jobs уже описаны именно как закрытые по умолчанию.
Большие файлы больше не должны съедать всю память сервера
Ещё одно важное изменение касается client uploads.
При прямой загрузке файла из браузера в storage сам файл обходит Payload-сервер. Но после загрузки Payload всё равно может понадобиться прочитать его — например, чтобы определить размеры изображения, проверить MIME type или сохранить локальную копию.
До 3.89.0 сервер в некоторых таких сценариях скачивал весь файл целиком в память.
Для фотографии на несколько мегабайт это незаметно. Для видео или другого объекта размером в несколько гигабайт — уже потенциальный OOM.
Особенно проблема проявилась после появления поддержки Azure client uploads размером более 5 ГБ. Один достаточно большой файл мог оказаться больше всей доступной серверу RAM.
Теперь Payload сначала определяет, сколько данных ему действительно нужно. Если хватает переданных клиентом метаданных — файл вообще не скачивается. Для определения размеров изображения может быть прочитан только небольшой диапазон байтов. Если нужен весь файл, Payload стримит его во временный файл на диск вместо огромного буфера в оперативной памяти.
Для проектов с S3/Azure-подобным storage и крупными пользовательскими загрузками это, пожалуй, одно из самых полезных изменений релиза.
PostgreSQL: Payload начал предупреждать о слишком длинных именах
Есть хороший фикс для пользователей Drizzle/PostgreSQL.
PostgreSQL ограничивает длину identifier 63 символами. Payload генерирует имена колонок, индексов и foreign keys из путей полей, поэтому при сильно вложенной структуре схемы легко получить имя длиннее лимита.
PostgreSQL молча его обрезает. В результате реальная схема БД и схема, которую ожидает Payload, могут начать расходиться. Один из симптомов — повторяющийся при каждом запуске dev-режима вопрос вида is <column> created or renamed?.
В 3.89.0 Payload проверяет такие идентификаторы при инициализации PostgreSQL.
Если имя просто длиннее 63 символов, в development появится предупреждение. Если после обрезания два разных идентификатора превращаются в одно и то же имя, Payload выбросит ошибку — такую схему PostgreSQL всё равно не сможет корректно создать.
Это небольшое изменение, но диагностировать подобные рассинхронизации раньше было крайне неприятно.
Исправлена потенциальная потеря hasMany-данных в PostgreSQL
Другой фикс Drizzle уже касается непосредственно данных.
При низкоуровневом db.updateOne частичное обновление документа могло затронуть hasMany select, даже если это поле вообще отсутствовало в update.
При определённых условиях Payload создавал для пропущенного поля пустую служебную структуру, после чего воспринимал её как команду удалить существующие связи.
Теперь отсутствие поля означает «не изменять существующее значение». Явный select: [], как и раньше, очищает связи.
Если проект использует низкоуровневые database operations поверх PostgreSQL, обновиться особенно разумно.
MongoDB избавили от лишних aggregation
Для MongoDB исправили менее опасную, но полезную проблему.
find, findOne и queryDrafts могли переходить на aggregation pipeline только потому, что в коллекции присутствует join field — даже когда конкретный запрос этот join не выбирает и реальных $lookup в pipeline нет.
В таком случае Payload уходил с обычного Model.findOne / Model.paginate на Model.aggregate без какой-либо пользы.
Теперь при пустом join pipeline используется обычный путь выполнения запроса. Особенно это может быть заметно на коллекциях с joins, где API активно использует select для выборки ограниченного набора полей.
Админка стала устойчивее
В релиз вошла целая группа менее масштабных UI-фиксов.
Например, быстрое изменение поля, используемого как useAsTitle, могло вызвать каскад React-обновлений и закончиться ошибкой Maximum update depth exceeded. Теперь частые изменения объединяются, а заголовок обновляется не чаще одного раза за animation frame.
Исправлен и неприятный баг Versions: если между версиями документа заменялся Block, просмотр diff мог падать с ошибкой No client field found for .... Особенно это проявлялось с историческими полями внутри row, collapsible или tabs.
Rich Text внутри tabs теперь корректно становится read-only для удалённых и заблокированных документов. Раньше остальные поля могли быть заблокированы, а Lexical внутри вкладки оставался редактируемым.
Кроме того, компонент Banner получил новый тип warning. Заодно разработчики починили давно неработавшие interactive states баннеров и поправили контрастность состояний hover/active.
Появилась документация по UI-компонентам Payload
Ещё одно полезное изменение не в runtime, а в документации.
Команда Payload наконец добавила отдельный раздел с компонентами @payloadcms/ui: примеры использования, props, рекомендации и accessibility notes. Первая версия документации охватывает наиболее часто используемые визуальные компоненты.
Для разработчиков кастомной админки это полезнее, чем выглядит на первый взгляд. Раньше для многих компонентов проще было идти в исходники Payload и смотреть реальное использование там.
Стоит ли обновляться до 3.89.0?
Да.
Payload 3.89.0 — не релиз с большой пользовательской фичей, но это хороший эксплуатационный апдейт. Здесь закрываются проблемы сразу на нескольких уровнях: security в Next.js, доступ к внутренним Jobs, потребление RAM при больших upload, целостность данных в Drizzle и несколько падений админки.
Перед обновлением стоит проверить только один действительно важный момент — использует ли проект прямой CRUD-доступ к payload-jobs.
Сам Payload и все установленные @payloadcms/* пакеты должны иметь одинаковую точную версию. Это официальная рекомендация проекта: смешивание разных версий Payload-пакетов может приводить к трудно диагностируемым runtime-ошибкам.
После обновления я бы проверил: версию next не ниже 16.3.3 для ветки Next 16.3; одинаковую версию всех payload / @payloadcms/*; работу Jobs, если есть кастомный интерфейс или прямой API к payload-jobs; загрузку файлов; просмотр Version Diff; и миграции/push, если используется PostgreSQL.
Полный changelog Payload CMS 3.89.0 опубликован на GitHub.