서버 액션에 try/catch 추가
먼저 오류를 적절하게 처리할 수 있도록 JavaScript의 try/catch 문을 서버 액션에 추가해 보자.
이를 수행하는 방법을 알고 있다면 서버 액션을 업데이트하는 데 몇 분 정도 시간을 투자하거나 아래 코드를 복사할 수 있다.
/app/lib/actions.ts
export async function createInvoice(formData: FormData) {
const { customerId, amount, status } = CreateInvoice.parse({
customerId: formData.get('customerId'),
amount: formData.get('amount'),
status: formData.get('status'),
});
const amountInCents = amount * 100;
const date = new Date().toISOString().split('T')[0];
try {
await sql`
INSERT INTO invoices (customer_id, amount, status, date)
VALUES (${customerId}, ${amountInCents}, ${status}, ${date})
`;
} catch (error) {
return {
message: 'Database Error: Failed to Create Invoice.',
};
}
revalidatePath('/dashboard/invoices');
redirect('/dashboard/invoices');
}
/app/lib/actions.ts
export async function updateInvoice(id: string, formData: FormData) {
const { customerId, amount, status } = UpdateInvoice.parse({
customerId: formData.get('customerId'),
amount: formData.get('amount'),
status: formData.get('status'),
});
const amountInCents = amount * 100;
try {
await sql`
UPDATE invoices
SET customer_id = ${customerId}, amount = ${amountInCents}, status = ${status}
WHERE id = ${id}
`;
} catch (error) {
return { message: 'Database Error: Failed to Update Invoice.' };
}
revalidatePath('/dashboard/invoices');
redirect('/dashboard/invoices');
}
/app/lib/actions.ts
export async function deleteInvoice(id: string) {
try {
await sql`DELETE FROM invoices WHERE id = ${id}`;
revalidatePath('/dashboard/invoices');
return { message: 'Deleted Invoice.' };
} catch (error) {
return { message: 'Database Error: Failed to Delete Invoice.' };
}
}
try/catch 블록 외부에서 리디렉션이 어떻게 호출되는지 확인해보자. 이는 리디렉션이 catch 블록에 의해 포착되는 오류를 발생시켜 작동하기 때문이다. 이를 방지하려면 try/catch 후에 리디렉션을 호출하면 된다. 호출 시도가 성공한 경우에만 리디렉션에 연결할 수 있다.
이제 서버 액션에서 오류가 발생하면 어떤 일이 발생하는지 확인해 보자. 이전에 오류를 발생시켜 이를 수행할 수 있다. 예를 들어 deleteInvoice 작업에서 함수 상단에 오류를 발생시켜보자.
/app/lib/actions.ts
export async function deleteInvoice(id: string) {
throw new Error('Failed to Delete Invoice');
// Unreachable code block
try {
await sql`DELETE FROM invoices WHERE id = ${id}`;
revalidatePath('/dashboard/invoices');
return { message: 'Deleted Invoice' };
} catch (error) {
return { message: 'Database Error: Failed to Delete Invoice' };
}
}
송장을 삭제하려고 하면 localhost에 오류가 표시된다.
이러한 오류를 확인하면 잠재적인 문제를 조기에 발견할 수 있으므로 개발 중에 도움이 된다. 그러나 갑작스러운 오류를 방지하고 애플리케이션이 계속 실행될 수 있도록 사용자에게 오류를 표시할 수도 있다.
이것이 Next.js error.tsx 파일이 들어오는 곳이다.
Nested Routes
에러는 가장 가까운 부모 에러 바운더리로 전파된다. 이는 error.js 파일이 모든 중첩된 하위 세그먼트의 에러를 처리할 것을 의미한다. 에러 바운더리는 동일한 세그먼트 내의 layout.js 컴포넌트에서 발생한 에러를 처리하지 않는다. 이는 에러 바운더리가 해당 레이아웃 컴포넌트 내에 중첩되어 있기 때문이다.
error.tsx로 모든 오류 처리
error.tsx 파일은 경로 세그먼트에 대한 UI 경계를 정의하는 데 사용할 수 있다. 예상치 못한 오류에 대한 포괄적인 역할을 하며 사용자에게 대체 UI를 표시할 수 있다.
/dashboard/invoices 폴더 내에 error.tsx라는 새 파일을 만들고 다음 코드를 붙여넣자.
/dashboard/invoices/error.tsx
'use client';
import { useEffect } from 'react';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Optionally log the error to an error reporting service
console.error(error);
}, [error]);
return (
<main className="flex h-full flex-col items-center justify-center">
<h2 className="text-center">Something went wrong!</h2>
<button
className="mt-4 rounded-md bg-blue-500 px-4 py-2 text-sm text-white transition-colors hover:bg-blue-400"
onClick={
// Attempt to recover by trying to re-render the invoices route
() => reset()
}
>
Try again
</button>
</main>
);
}
위의 코드에서 알 수 있는 몇 가지 사항이 있다.
- "클라이언트 사용" - error.tsx는 클라이언트 컴포넌트여야 한다.
- 두 가지 prop 을 허용다:
- error : 이 객체는 JavaScript의 기본 Error 객체의 인스턴스이다.
- reset : 오류 경계를 재설정하는 기능이다. 실행되면 함수는 경로 세그먼트를 다시 렌더링하려고 시도한다.
송장을 다시 삭제하려고 하면 다음 UI가 표시된다.
notFound 함수로 404 오류 처리
오류를 적절하게 처리할 수 있는 또 다른 방법은 notFound 함수를 사용하는 것이다. error.tsx는 모든 오류를 잡는 데 유용하지만 존재하지 않는 리소스를 가져오려고 할 때 notFound를 사용할 수 있다.
예를 들어 http://localhost:3000/dashboard/invoices/2e94d1ed-d220-449f-9f11-f0bbceed9645/edit 를 열어보자.
이는 데이터베이스에 존재하지 않는 가짜 UUID이다.
error.tsx가 정의된 /invoices의 하위 경로이기 때문에 error.tsx가 시작되는 것을 즉시 확인할 수 있다.
그러나 좀 더 구체적으로 설명하고 싶다면 404 오류를 표시하여 사용자가 액세스하려는 리소스를 찾을 수 없음을 알릴 수 있을 것이다.
data.ts의 fetchInvoiceById 함수로 이동하고 반환된 송장을 콘솔에서 기록하여 리소스를 찾을 수 없음을 확인할 수 있다.
/app/lib/data.ts
export async function fetchInvoiceById(id: string) {
noStore();
try {
// ...
console.log(invoice); // Invoice is an empty array []
return invoice[0];
} catch (error) {
console.error('Database Error:', error);
throw new Error('Failed to fetch invoice.');
}
}
이제 데이터베이스에 송장이 없다는 것을 알았으므로 notFound를 사용하여 이를 처리해 보자.
/dashboard/invoices/[id]/edit/page.tsx로 이동하고 'next/navigation'에서 { notFound }를 가져온다.
그 다음 송장이 존재하지 않는 경우 조건을 사용하여 notFound를 호출할 수 있다.
/dashboard/invoices/[id]/edit/page.tsx
import { fetchInvoiceById, fetchCustomers } from '@/app/lib/data';
import { updateInvoice } from '@/app/lib/actions';
import { notFound } from 'next/navigation';
export default async function Page({ params }: { params: { id: string } }) {
const id = params.id;
const [invoice, customers] = await Promise.all([
fetchInvoiceById(id),
fetchCustomers(),
]);
if (!invoice) {
notFound();
}
// ...
}
이제 특정 청구서를 찾을 수 없으면 <페이지>에서 오류가 발생한다. 사용자에게 오류 UI를 표시하기 위해 /edit 폴더 내에 not-found.tsx 파일을 만들자.
그 다음 not-found.tsx 파일 내에 다음 코드를 붙여넣는다.
/dashboard/invoices/[id]/edit/not-found.tsx
import Link from 'next/link';
import { FaceFrownIcon } from '@heroicons/react/24/outline';
export default function NotFound() {
return (
<main className="flex h-full flex-col items-center justify-center gap-2">
<FaceFrownIcon className="w-10 text-gray-400" />
<h2 className="text-xl font-semibold">404 Not Found</h2>
<p>Could not find the requested invoice.</p>
<Link
href="/dashboard/invoices"
className="mt-4 rounded-md bg-blue-500 px-4 py-2 text-sm text-white transition-colors hover:bg-blue-400"
>
Go Back
</Link>
</main>
);
}
경로를 새로 고치면 이제 다음 UI가 표시된다.
notFound는 error.tsx보다 우선하므로 더 구체적인 오류를 처리하고 싶을 때 이를 활용 할 수 있다.
현재 [...not_found]에 notFound를 호출하도록 적용하여 요청한 페이지가 없으면 notFound 처리하도록 적용되어 있다.
'Next.js 개발 가이드 > 02. 코딩 가이드 및 필수 패키지' 카테고리의 다른 글
9. Meta Data (0) | 2024.02.01 |
---|---|
7. Parallel Routes, Intercepting Routes (0) | 2024.01.26 |
6. Middleware (0) | 2024.01.26 |
5. Internationalization (0) | 2024.01.24 |
4. loading, Suspense, error boundary, zod, dynamic routes, searchParams 사용 예시 (0) | 2024.01.21 |