Отладка действий сервера Next.js: ошибки развертывания, CORS и ограничения объема данных
Практическое руководство по устранению неполадок, объясняющее, почему функции Server Actions и API-маршруты Next.js работают без ошибок или выдают неясные сообщения об ошибках, с конкретными решениями для каждого случая.
Источники: Common Server Action and API Route Errors in Next.js (and How to Fix Them)
Функции сервера значительно уменьшили сложность работы с логикой бэкенда в Next.js — теперь вам больше не нужна отдельная API-маршрут для обработки отправки форм. Однако такой удобство сопровождается своими проблемами, которые сильно отличаются от ошибок, встречавшихся при использовании старого роутера Pages Router. Ниже приведен обзор того, что часто идет не так, и как с этим бороться.
1. «Ошибка: не удалось найти функцию сервера» после развертывания
Всё работает нормально на вашем компьютере, но как только выпускается новая версия — пользователи в процессе работы внезапно сталкиваются с этой ошибкой.
Причина: Функции сервера компилируются с идентификаторами, связанными с конкретной версией приложения. Если у кого-то открыта вкладка браузера во время развертывания, его клиент продолжает использовать идентификаторы функций из старой версии, которые сервер уже не распознает.
Решение: полностью предотвратить это у пользователей, уже активных во время развертывания, невозможно, но его можно обработать более грамотно, вместо того чтобы позволять происходить бесшумному сбою:
'use client';
import { useTransition } from 'react';
function SubmitButton({ action }) {
const [isPending, startTransition] = useTransition(); async function handleClick() {
try {
await action();
} catch (err) {
if (err.message?.includes('Failed to find Server Action')) {
window.location.reload(); // force a fresh client with current build IDs
}
}
} return <button onClick={() => startTransition(handleClick)}>Submit</button>;
}
Ловля этой конкретной ошибки и побуждение пользователя обновить страницу дают гораздо лучший опыт, чем простой, необъясненный сбой.
2. Действие сервера тихо не выполняется
Вы вызываете действие, ничего не происходит, и нигде не выводится ошибка.
Частая причина: отсутствие директивы 'use server' в начале функции или размещение действия внутри клиентского компонента без соответствующей маркировки.
// Wrong — no directive
async function saveUser(formData) {
await db.user.update(/* ... */);
}
Решение:
'use server';
export async function saveUser(formData) {
await db.user.update(/* ... */);
}
Если действие определено в том же файле, что и клиентский компонент, директива должна находиться в верхней части самой функции, а не просто в верхней части файла:
'use client';
export default function Form() {
async function saveUser(formData) {
'use server'; // required here specifically
await db.user.update(/* ... */);
}
return <form action={saveUser}>{/* ... */}</form>;
}
3. «Ошибка: в клиентские компоненты можно передавать только простые объекты»
Эта ошибка возникает, когда значение возврата серверного действия не является сериализуемым — например, экземпляр класса, объект Date, находящийся в необычной структуре, или модель базы данных с собственными методами.
'use server';
export async function getUser(id) {
return await db.user.findUnique({ where: { id } }); // ORM might return a class instance
}
Решение — вручную преобразовать результат в простой объект:
'use server';
export async function getUser(id) {
const user = await db.user.findUnique({ where: { id } });
return {
id: user.id,
name: user.name,
email: user.email,
createdAt: user.createdAt.toISOString(), // Dates need explicit serialization too
};
}
Не передавайте результат работы ORM напрямую в клиентский компонент. Сначала преобразуйте его в простой, сериализуемый объект.
4. Серверное действие работает, но данные формы возвращаются как undefined
<form action={createPost}>
<input name="title" />
</form>
'use server';
export async function createPost(formData) {
console.log(formData.title); // undefined
}
аргумент formData, который вы получаете, представляет собой нативный экземпляр FormData, а не обычный объект JavaScript, поэтому для извлечения значений из него необходимо вызвать метод .get().
Решение:
'use server';
export async function createPost(formData) {
const title = formData.get('title');
console.log(title); // correct
}
5. «Объем тела запроса превысил лимит в 1 МБ» при использовании Server Actions
Server Actions устанавливают стандартный лимит размера тела запроса, что становится проблемой при обработке загрузки файлов или больших объемов данных формы.
Решение — явно увеличьте лимит в файле next.config.js:
// next.config.js
module.exports = {
experimental: {
serverActions: {
bodySizeLimit: '10mb',
},
},
};
Для любых ситуаций, похожих на реальную загрузку файлов, обычно лучше вообще не использовать Server Actions для обработки файла. Загружайте файл непосредственно с клиента в объектное хранилище — S3, R2 или аналогичные сервисы — с использованием подписанной URL. Это позволяет обойти ограничение по размеру и снизить нагрузку на сервер.
6. Ошибки CORS при вызове API-маршрутов с другого источника
Эта проблема обычно возникает, когда мобильное приложение, расширение браузера или отдельный фронтенд напрямую обращаются к API-маршрутам Next.js, вместо того чтобы использовать собственные страницы.
Access to fetch at 'https://yourapp.com/api/data' from origin 'https://otherapp.com' has been blocked by CORS policy
Решение — явно установить заголовки внутри обработчика маршрута:
// app/api/data/route.js
export async function GET(request) {
const data = await getData();
return Response.json(data, {
headers: {
'Access-Control-Allow-Origin': 'https://otherapp.com', // never '*' for authenticated routes
'Access-Control-Allow-Methods': 'GET, POST',
},
});
}
export async function OPTIONS() {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': 'https://otherapp.com',
'Access-Control-Allow-Methods': 'GET, POST',
},
});
}
Избегайте установки Access-Control-Allow-Origin: '*' на любые конечные точки, которые зависят от куки аутентификации. Это фактически передает данные пользователя любому источнику, который попросит их получить.
7. Обработчик маршрута возвращает HTML вместо JSON при ошибках
Когда обработчик маршрута вызывает неконтролируемое исключение, клиент не получает структурированную ошибку в формате JSON — вместо этого он получает встроенную страницу ошибок HTML от Next.js. Это проблема для любого клиентского кода, который предполагает возможность безопасного использования метода response.json().
Решение — обернуть логику обработчика в блок try/catch, который всегда возвращает JSON:
export async function POST(request) {
try {
const body = await request.json();
const result = await processOrder(body);
return Response.json(result);
} catch (error) {
return Response.json(
{ error: error.message ?? 'Something went wrong' },
{ status: 500 }
);
}
}
Благодаря этому клиент всегда получает данные, которые можно разобрать как JSON, независимо от того, увенчалась ли запросом успех или неудача, вместо HTML-документа, который нарушает работу вызова .json() позже в коде.
Почему эти ошибки кажутся более серьезными, чем баги в старом стиле API-маршрутов
Действия сервера объединяют логику клиента и сервера в один файл, из-за чего крайне легко нарушить границы между ними, даже не заметив этого. Значение, возвращаемое функцией, может выглядеть совершенно нормальным в редакторе, но всё равно вызвать ошибку во время выполнения из-за отсутствия возможности его сериализации, или же отсутствие определённой директивы может привести к тихой ошибке вместо того, чтобы вызвать исключение в ожидаемом месте. Обычные методы отладки для API в стиле REST — открытие вкладки сети и проверка ответа — здесь не всегда помогают, поскольку многие из этих ошибок возникают ещё до того, как вообще отправляется сетевой запрос.
Для команд, создающих панели управления, содержащие множество форм, загрузок файлов и операций изменения данных, распределенных по нескольким страницам, целесообразно единообразно решить такие проблемы, как сериализация данных, обработка ошибок и ограничения по размеру тела запросов, во всем кодовом базисе, вместо того чтобы снова и снова отлаживать одни и те же проблемы в каждой новой форме. Некоторые готовые шаблоны администрирования для SaaS-продуктов с искусственным интеллектом уже поставляются с правильно настроенными Server Actions и API-маршрутами, включая проверку данных с помощью Zod и надлежащую обработку ошибок с самого начала, благодаря чему эти семь проблем решаются один раз, а не возникают снова и снова в каждой создаваемой форме.
Вывод
Действия сервера сокращают объем множества стандартных элементов, которые раньше требовались API-маршрутами, но при этом привносят собственный набор проблем: устаревание идентификаторов сборки после развертывания, отсутствие необходимых указаний, значения возврата, которые невозможно сериализовать, неправильное использование FormData, ограничения по размеру тела запроса, сбои CORS между доменами и ответы на ошибки, которые на самом деле не являются JSON. Все семь проблем встречаются часто, все их можно исправить всего за несколько строк, и все они требуют быстрой проверки прежде чем предполагать, что произошла более сложная проблема.