Next.js의 서버 액션으로 데이터 페칭과 변형 간소화하기
2024-10-10 18:28:31Next.js 서버 액션에 대한 이해
최근 웹 개발에서 Next.js는 그 강력한 기능과 유연성 덕분에 많은 사랑을 받고 있습니다. 이번 포스트에서는 Next.js의 최신 기능인 **서버 액션(Server Actions)**에 대해 깊이 있게 살펴보고, 이를 통해 데이터 페칭과 변형 과정을 간소화하는 방법을 설명하겠습니다. 이 글을 통해 Next.js의 서버 액션이 기존의 데이터 처리 방식에 어떻게 혁신을 가져오는지를 알아보세요.
서버 액션의 기본 개념
서버 액션은 기존의 클라이언트-서버 간의 API 호출 구조를 탈피하여, 컴포넌트에서 직접 서버 사이드 함수를 호출할 수 있게 해줍니다. 이를 통해 불필요한 네트워크 요청을 줄이고, 성능을 높일 수 있는 방법을 제공합니다. 이러한 접근 방식은 데이터 처리의 흐름을 간소화하는데 큰 도움이 됩니다.
서버 액션 정의하기
서버 액션은 "use server" 지시어를 사용하여 정의할 수 있습니다. 다음은 서버 컴포넌트 내에 서버 액션을 직접 구현하는 예시입니다.
// @/components/Catalog.tsx
export default function Catalog() {
async function deleteProduct() {
"use server"
// 삭제 로직 구현
}
return (
// ...
);
}
또한 서버 액션을 별도의 파일로 만들어 클라이언트 및 서버 컴포넌트 모두에서 재사용할 수도 있습니다.
// @/app/actions/loadProducts.tsx
"use server";
export async function deleteProduct() {
// 삭제 로직 구현
}
서버 액션을 이용한 데이터 변형
서버 액션은 데이터 변형에 특히 유용합니다. 예를 들어, 상품을 삭제해보겠습니다. 전통적으로는 API에 DELETE 요청을 보내고 응답을 처리한 후 클라이언트 상태를 업데이트해야 했습니다. 하지만 서버 액션을 활용하면 이를 훨씬 간편하게 처리할 수 있습니다.
기존의 방식은 아래와 같습니다:
import type { NextApiRequest, NextApiResponse } from 'next';
import { createClient } from "@/utils/supabase";
type ResponseData = {
products: Product[];
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<ResponseData>
) {
const requestMethod = req.method;
switch (requestMethod) {
case "GET":
// ...
case "DELETE":
const { product_id } = req.body;
await supabase
.from("products")
.delete()
.eq("id", product_id);
res.status(200).json({ message: "Success" });
break;
default:
res.setHeader('Allow', ['GET', 'POST', 'DELETE']);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
이 방식은 복잡하고 번거롭습니다. 이제 서버 액션을 사용하면 다음과 같이 간단히 변형할 수 있습니다.
// @/app/actions/deleteProduct.tsx
"use server";
import { createClient } from "@/utils/supabase";
export const deleteProduct = async (product_id: string) => {
const supabase = createClient();
await supabase
.from("products")
.delete()
.eq("id", product_id);
// Next.js 캐시 재검증
revalidatePath('/catalog');
}
위와 같이 서버 액션을 통해 데이터 삭제 시 클라이언트 요청 없이도 간단하게 데이터를 처리할 수 있습니다.
캐시 관리: revalidatePath와 revalidateTag
데이터 변형 후, UI에 변경 사항을 반영하기 위해 캐시를 업데이트해야 합니다. Next.js는 다음과 같은 두 가지 함수를 제공합니다.
-
revalidatePath: 특정 경로의 캐시를 새로 고칩니다. 예를 들어, 상품을 삭제한 경우
/catalog페이지를 재검증하여 최신 데이터를 표시해야 합니다. -
revalidateTag: 전체 페이지가 아닌 특정 데이터만 새로 고칠 수 있습니다. 다음은 예시입니다.
// @/components/Catalog.tsx
export default async function Catalog() {
const { data: products } = await fetch(
"https://someproject.supabase.co/rest/v1/products?select=*",
{
next: {
tags: ["catalog"],
},
}
);
return (
// ...
);
}
// 그 후 서버 액션 내에서 요청을 재검증할 수 있습니다.
"use server";
import { createClient } from "@/utils/supabase/server";
import { revalidateTag } from "next/cache";
export const deleteProduct = async (product_id: string) => {
await supabase
.from("products")
.delete()
.eq("id", product_id);
revalidateTag('catalog');
}
서버 액션을 통한 데이터 페칭
서버 액션은 데이터 변형 도구뿐만 아니라, 서버에서 직접 데이터를 가져오는 데에도 유용합니다.
// app/actions/loadProducts.tsx
"use server"
export async function loadProducts() {
// 서버 또는 DB에서 데이터 가져오기
}
컴포넌트에서 사용할 수 있습니다.
// components/Catalog.tsx
import { loadProducts } from '@/actions/loadProducts.tsx';
export default async function Catalog() {
const products = await loadProducts();
return (
// 상품 목록 렌더링
);
}
마지막으로, 캐시 상태를 관리하기 위해 Suspense를 활용하여 로딩 상태를 처리할 수 있습니다.
// app/catalog/page.tsx
import { Suspense } from "react";
import Catalog from "@/components/Catalog";
export default async function CatalogPage() {
return (
<Suspense fallback={<CatalogSkeleton />}>
<Catalog />
</Suspense>
);
}
서버 액션의 장단점
서버 액션은 데이터 처리의 효율성을 크게 낫추는 강력한 도구입니다. 그러나 몇 가지 중요한 점을 고려해야 합니다.
-
서버 액션의 순차적 실행: 모든 서버 액션은 하나씩 순차적으로 실행됩니다. 이는 여러 요청을 동시에 처리할 수 없다는 것을 의미하며, 응답 시간이 지연될 수 있습니다. 성능이 중요한 경우 이 점은 단점이 될 수 있습니다.
-
서버 액션 요청은 항상 POST 요청: 서버 액션 요청은 항상 POST 방식으로 제공됩니다. 전통적인 API 라우트는 캐싱을 통해 더 많은 유연성을 제공할 수 있습니다. 따라서 GET 요청을 활용할 수 없는 단점이 있습니다.
결론적으로, 서버 액션은 Next.js의 강력한 기능으로 데이터 처리 방식을 혁신적으로 변화시킵니다. 하지만 사용하기 전에는 장단점을 충분히 이해하고, 상황에 맞게 적절히 활용하는 것이 중요합니다.
참고 자료
이상으로 Next.js의 서버 액션에 대해 살펴보았습니다. 서버 액션을 활용하여 데이터를 더욱 효율적으로 관리해보세요!