Łatanie błędów w Server Actions Next.js: błędy przy deploju, CORS oraz ograniczenia ładunku
Praktyczny przewodnik naprawczy, który wyjaśnia, dlaczego funkcje Next.js Server Actions oraz trasy API zawodzą w tajemniczy sposób lub wywołują niejasne błędy, oraz przedstawia konkretne rozwiązania dla każdego przypadku.
Źródła: Common Server Action and API Route Errors in Next.js (and How to Fix Them)
Akcje serwera znacznie zmniejszyły złożoność logiki backendowej w Next.js — nie musisz już korzystać z dedykowanej trasy API tylko po to, by obsłużyć wysłanie formularza. Jednak ta wygoda niesie ze sobą własne problemy, które w ogóle nie przypominają błędów występujących w starym systemie Pages Router. Poniżej znajduje się przegląd najczęstszych problemów i sposobów ich rozwiązania.
1. „Błąd: Nie udało się znaleźć akcji serwera” po wdrożeniu
Wszystko działa poprawnie na twoim komputerze, ale zaraz po wydaniu nowej wersji pojawia się błąd — użytkownicy będący w trakcie sesji nagle napotykają ten problem.
Powód: Akcje serwera są kompilowane z identyfikatorami powiązanymi z konkretną wersją aplikacji. Jeśli ktoś ma otwartą kartę przeglądarki w momencie wdrożenia, jego klient nadal odwołuje się do identyfikatorów akcji z starej wersji, a serwer ich już nie rozpoznaje.
Rozwiązanie: nie można całkowicie zapobiec temu u użytkowników już aktywnych podczas wdrażania, ale można to lepiej obsłużyć, zamiast pozwalać na ciche załamanie aplikacji:
'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>;
}
Złapanie tego konkretnego błędu i zachęcenie użytkownika do odświeżenia jest o wiele lepszym doświadczeniem niż surowe, niewyjaśnione załamanie.
2. Działanie serwera nie jest wykonywane w tle
Zawieszasz działanie, nic się nie dzieje i nigdzie nie jest wyświetlany żaden błąd.
Częsta przyczyna: brak dyrektywy 'use server' na początku funkcji lub umieszczenie działania w komponencie klienckim bez właściwego oznaczenia.
// Wrong — no directive
async function saveUser(formData) {
await db.user.update(/* ... */);
}
Rozwiązanie:
'use server';
export async function saveUser(formData) {
await db.user.update(/* ... */);
}
Jeśli akcja jest zdefiniowana w tym samym pliku co komponent kliencki, dyrektywa musi znajdować się na początku samiej funkcji, a nie tylko na początku pliku:
'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. „Błąd: Do komponentów klienckich można przekazać tylko zwykłe obiekty”
Błąd ten pojawia się, gdy wartość zwracana przez akcję serwera nie jest serializowalna — na przykład instancja klasy, obiekt Date umieszczony w nietypowej strukturze lub model bazy danych zawierający własne metody.
'use server';
export async function getUser(id) {
return await db.user.findUnique({ where: { id } }); // ORM might return a class instance
}
Rozwiązanie — ręcznie przekształć wynik w zwykły obiekt:
'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
};
}
Nie przekazuj bezpośrednio wyniku ORM do komponentu klienckiego. Najpierw przekształć go w zwykły, serializowalny obiekt.
4. Akcja serwera działa, ale dane formularza wracają jako undefined
<form action={createPost}>
<input name="title" />
</form>
'use server';
export async function createPost(formData) {
console.log(formData.title); // undefined
}
Powód: argument formData, który otrzymujesz, to klasyczna instancja FormData, a nie zwykły obiekt JavaScript, dlatego musisz wywołać metodę .get(), aby uzyskać z niej wartości.
Rozwiązanie:
'use server';
export async function createPost(formData) {
const title = formData.get('title');
console.log(title); // correct
}
5. „Ciało żądania przekroczyło limit 1 MB” w Server Actions
Server Actions narzucają domyślny limit wielkości ciała żądania, co staje się problemem w momencie przetwarzania plików do przesłania lub dużych danych formularza.
Rozwiązanie — wyraźnie zwiększ limit w pliku next.config.js:
// next.config.js
module.exports = {
experimental: {
serverActions: {
bodySizeLimit: '10mb',
},
},
};
W przypadku czegokolwiek, co przypomina rzeczywiste przesyłanie pliku, zazwyczaj lepiej jest całkowicie uniknąć przekierowywania pliku przez Server Action. Przesyłaj go bezpośrednio z klienta do systemu przechowywania obiektów — takiego jak S3, R2 lub podobne — za pomocą podpisanej URL. Dzięki temu unikniesz ograniczeń wielkości i zmniejszysz obciążenie serwera.
6. Błędy CORS przy trasach API wywoływanych z innej domeny
Taki problem pojawia się zazwyczaj, gdy aplikacja mobilna, rozszerzenie przeglądarki lub oddzielny interfejs użytkownika bezpośrednio kontaktuje się z trasami API Next.js, zamiast korzystać z własnych stron.
Access to fetch at 'https://yourapp.com/api/data' from origin 'https://otherapp.com' has been blocked by CORS policy
Rozwiązanie — wyraźnie ustaw headery wewnątrz obsługi trasy:
// 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',
},
});
}
Unikaj ustawiania Access-Control-Allow-Origin: '*' na żadnym punkcie końcowym, który polega na plikach cookie do autoryzacji. Dzięki temu dane użytkownika trafiają do dowolnej domeny, która o to poprosi.
7. Obsługa trasy zwraca HTML zamiast JSON w przypadku błędu
Gdy obsługa trasy wywoła nieobsłużoną wyjątk, klient otrzymuje nieuporządkowaną stronę błędu w formacie JSON, a zamiast tego stronicę błędu HTML wbudowaną w Next.js. To stanowi problem dla kodu klienta, który zakłada, że może bezpiecznie używać funkcji response.json().
Rozwiązanie — otocz logikę obsługi w blok try/catch, który zawsze zwraca 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 }
);
}
}
Dzięki temu klient zawsze otrzymuje dane, które można przetworzyć jako JSON, niezależnie od tego, czy żądanie się powiodło, czy nie, zamiast dokumentu HTML, który powoduje błędy przy wywołaniu .json() później w procesie.
Dlaczego te błędy wydają się gorsze od tradycyjnych błędów w trasach API
Działania serwera łączą logikę klienta i serwera w tym samym pliku, co sprawia, że zaskakująco łatwo jest przekroczyć granice bez żadnego zauważenia. Wartość zwracana może wyglądać doskonale w edytorze, a mimo to spowodować błąd podczas wykonywania, ponieważ nie jest serializowalna, albo brakująca instrukcja może powodować błąd w taki sposób, że nie jest on widoczny tam, gdzie się tego spodziewamy. Zwykłe praktyki debugowania stosowane w API typu REST — otwarcie karty sieciowej, sprawdzenie odpowiedzi — nie zawsze pomagają w takich przypadkach, ponieważ wiele z tych błędów występuje jeszcze przed wysłaniem jakiegokolwiek żądania sieciowego.
Dla zespołów tworzących panele kontrolne pełne formularzy, przesyłań plików oraz operacji mutacyjnych rozproszonych na wielu stronach warto, aby te wzorce — serializacja, obsługa błędów, limity wielkości ciała — zostały rozwiązane spójnie raz w całym kodzie, zamiast ponownie debugować te same problemy przy każdym nowym formularzu. Niektóre gotowe szablony administracyjne dla produktów SaaS opartych na AI już zawierają poprawnie skonfigurowane Server Actions i ścieżki API, w tym walidację za pomocą Zod oraz odpowiednią obsługę błędów wbudowaną od samego początku, dzięki czemu te siedem problemów jest rozwiązywanych tylko raz, zamiast pojawiać się ponownie przy każdym formularzu, który tworzysz.
Podsumowanie
Działania serwera zmniejszają ilość standardowych elementów, których wcześniej wymagały trasy API, ale przynoszą ze sobą własne problemy: identyfikatory budowy stają się przestarzałe po wdrożeniu, brakuje niektórych instrukcji, wartości zwracane nie mogą być zserializowane, nadużywa się FormData, istnieją ograniczenia co do wielkości ciała wiadomości, występują błędy CORS między domenami oraz odpowiedzi błędowe, które w rzeczywistości nie są JSON. Wszystkie te problemy pojawiają się często, wszystkie można naprawić w zaledwie kilku liniach kodu, a każdy z nich wymaga szybkiej weryfikacji, zanim założymy, że doszło do czegoś bardziej skomplikowanego.