프로젝트를 진행하다보면 클라이언트에서 백엔드 API로 data mutation(HTTP POST,PATCH,PUT,DELETE)를 할 일이 발생합니다.
하지만 nextjs에서는 위 방법으로써 Server Action 기능을 제안하고 있는데요.
Server Action을 굳이 왜 써야하나
라는 의문이 들었습니다. Server Action은 서버를 통해서 API 서버를 통신하는 것인데, 굳이 왜 불필요하게 웹 서버를 통해서 통신해야하나
라는 의문이 든거죠
nextjs로 풀스택으로 구현하지 않는다면 ,백엔드 API가 있을텐데요.
nextjs에서는 data mutation의 두가지 방법이 있습니다.
a. 클라이언트 통신시
클라이언트 → 백엔드 API → 응답
b. server action 사용시
클라이언트 → Next.js 서버(Server Action) → 백엔드 API → Next.js 서버 → 응답
제가 들었던 의문은 다음과 같습니다.
a는 바로 백엔드 API로 통신하면 되지만 b는 클라이언트에서 next 서버로 통신하고 next서버에서 백엔드 API 통신, 응답도 반대 과정을 거칠텐데 오히려 통신단계가 하나 더 추가되는 것인데 이는 더 복잡하고 안 좋은 것이 아닐까?
그래서 통신 단계 증가에 따름을 감수하고도 server action을 사용하면서 얻을 수 있는 장점이 뭔지 파악해보려고 합니다.
Server actions can be invoked using the action attribute in a
<form>
element:
- Server Components support progressive enhancement by default, meaning the form will be submitted even if JavaScript hasn't loaded yet or is disabled.
문서에서는 Server action을 form action에서 사용할 때의 장점을 설명해주는데요.
이 문장은 Server Components가 Progressive Enhancement(점진적 향상) 원칙을 지원한다는 뜻으로, 클라이언트 측의 JavaScript가 로드되지 않았거나 비활성화된 경우에도 서버와 상호작용이 가능하다는 장점을 설명합니다.
그렇다면 어떻게 JavaScript가 로드되지 않았는데에도 서버와 상호작용이 가능할까요?
이는 HTML form 태그에 대해 알아보면 되는데요.
HTML
<form>
요소는 브라우저에서 기본적으로 지원하는 기능입니다. action 속성을 통해 폼 데이터를 서버로 전송하며, JavaScript 없이도 서버 요청이 가능합니다.
export default function Page() {
async function createInvoice(formData: FormData) {
'use server'
const rawFormData = {
customerId: formData.get('customerId'),
amount: formData.get('amount'),
status: formData.get('status'),
}
// mutate data
// revalidate cache
}
return <form action={createInvoice}>...</form>
}
따라서 Next.js에서 Server Action을 폼에 연결하면, 브라우저의 기본 폼 제출 동작을 활용하여 데이터를 서버로 보냅니다.
만약 클라이언트에서 JavaScript가 로드되지 않았거나 비활성화된 경우,Server Action을 통해 데이터를 처리하고, 응답을 반환할 수 있습니다.
server action을 사용하는 경우와 사용하지 않는 경우를 예시로 살펴볼게요
// server action을 사용하지 않는 경우
'use client';
export default function FormWithEnhancements() {
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const response = await fetch("/api/submit", {
method: "POST",
body: formData,
});
console.log(await response.json());
};
return (
<form onSubmit={handleSubmit}>
<label htmlFor="name">Name:</label>
<input id="name" name="name" type="text" />
<button type="submit">Submit</button>
</form>
);
}
이 방식은 브라우저에서 JavaScript가 반드시 활성화되어야 동작합니다. JavaScript가 비활성화된 경우, 폼 제출 자체가 동작하지 않습니다.
// server action을 사용하는 경우
export default function Page() {
async function handleFormSubmit(formData: FormData) {
"use server";
const name = formData.get("name");
console.log(`Name submitted: ${name}`);
}
return (
<form action={handleFormSubmit}>
<label htmlFor="name">Name:</label>
<input id="name" name="name" type="text" />
<button type="submit">Submit</button>
</form>
);
}
위와 같이 handleFormSubmit
server action 함수가 있습니다.
Javascript가 로드되지 않았더라도 form의 action을 통해 form 제출이 가능합니다.
그리고 handleFormSubmit
함수는 브라우저가 아닌 Next 서버에서 실행됩니다. 위 함수가 서버에서 실행되기 때문에 브라우저에 Javascript가 로드되지 않더라도 사용할 수 있는거죠.
이처럼 server action을 사용하면 Javascript가 로드되지 않더라도 폼 제출이 가능하다는 장점이 있습니다.
이는 느린 네트워크를 사용하는 사용자를 위해 Javascript가 로드되지 않더라도 폼 제출을 할 수 있도록 해줄 수 있습니다.
server action을 사용하면 UI를 초기 렌더링에 빠르게 보여줄 수 있습니다. 이것이 무슨 말이냐하면 특정 UI를 클라이언트 컴포넌트로 사용하는 대신 서버 컴포넌트로 작성하여 페이지 로드시 UI는 빠르게 보여줄 수 있습니다.
클라이언트 컴포넌트보다 서버 컴포넌트가 더 빠르게 로드되니깐요.
예를 들어, SSG 방식으로 렌더링된 포스팅 페이지가 있고 내부에는 좋아요 UI가 있다고 해봅시다.
좋아요 UI가 클라이언트 컴포넌트로 작성되어있을 경우와 서버 컴포넌트로 작성되어있는 경우로 나누어 보겠습니다.
다음은 클라이언트 컴포넌트로 좋아요 UI 작성한 코드입니다.
// components/LikeButton.tsx
"use client";
import { useState } from "react";
export function LikeButton({ postId, initialLikes }: { postId: string; initialLikes: number }) {
const [likes, setLikes] = useState(initialLikes);
const [loading, setLoading] = useState(false);
const handleLike = async () => {
setLoading(true);
try {
const response = await fetch("/api/like", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ postId }),
});
if (response.ok) {
const data = await response.json();
setLikes(data.likes); // 업데이트된 좋아요 수 반영
} else {
console.error("Failed to like the post");
}
} catch (error) {
console.error("Error:", error);
} finally {
setLoading(false);
}
};
return (
<button onClick={handleLike} disabled={loading}>
{loading ? "Liking..." : `Like (${likes})`}
</button>
);
}
export default function PostPage({ postId, initialLikes }: { postId: string; initialLikes: number }) {
return (
<div>
<h1>Post Title</h1>
<p>Static Content about the Post</p>
{/* 좋아요 버튼 */}
<LikeButton postId={postId} initialLikes={initialLikes} />
</div>
);
}
위 LikeButton UI는 클라이언트 컴포넌트로 작성되어있습니다. PostPage가 SSG 방식으로 설정되어있어도 Like Button은 클라이언트에서 렌더링되기 때문에 바로 UI가 나타나지 않습니다.
다음은 서버 컴포넌트로 좋아요 UI 작성한 코드입니다.
// app/post/[id]/actions.ts
"use server";
import { revalidatePath } from "next/cache";
export async function likePost(postId: string) {
// 예: DB에서 좋아요 증가 처리
console.log(`Post liked: ${postId}`);
// 캐시 무효화 (좋아요 수 업데이트)
revalidatePath(`/post/${postId}`);
}
// app/post/[id]/page.tsx
import { likePost } from "./actions";
export default function PostPage({ params }: { params: { id: string } }) {
const { id } = params;
return (
<div>
<h1>Post Title</h1>
<p>Static Content about the Post</p>
{/* JavaScript 없이도 동작하는 폼 */}
<form action={likePost.bind(null, id)}>
<button type="submit">Like</button>
</form>
<p id="like-count">Likes: 0</p>
</div>
);
}
위 포스트 페이지를 SSG 방식으로 로드했다고 했을 때, 좋아요 UI는 페이지 로드시 바로 나타나게 됩니다. 하지만 Javascript들은 로드가 되지 않았을 수도 있죠.
하지만 위처럼 server action을 사용하면 Javascript가 로드되지 않았던 시점에 사용자가 좋아요 버튼을 눌렀다해도 좋아요 함수
기능은 작동합니다.
이처럼 server action을 사용하면 빠르게 UI를 보여주면서도 기능도 작동하게 할 수 있습니다.
폼 데이터를 처리하는 로직을 클라이언트에서 서버로 위임하면 컴포넌트가 UI에만 집중할 수 있게 해주고 클라이언트 번들 사이즈를 줄일 수도 있습니다.
클라이언트 컴포넌트에서 폼 로직을 처리한다면 state handling이 추가되어야하고 validation ,API 통신 등 로직이 컴포넌트에 있을 수 있습니다. 이는 컴포넌트의 역할, UI에 대한 책임을 불분명하게 할 수 있습니다. 또한 클라이언트에 대한 Javascript 로직이 늘어 클라이언트 번들 사이즈가 커질 수도 있습니다.
하지만 server action을 사용하면 위 로직들을 서버로 위임할 수 있습니다. 이로 인해, 컴포넌트가 UI에 대한 책임을 분명히 할 수 있고 로직을 서버로 위임함으로써 번들 사이즈도 줄일 수 있습니다.
클라이언트 번들 사이즈가 커질수록?
클라이언트 번들 사이즈가 커질수록 Javascript 로드해야할 양이 더 많아집니다. 이로 인해 시간도 더 오래 걸릴 수 있겠죠. 때문에 클라이언트 번들 사이즈 Javascript를 가능하다면 서버로 위임하는 것이 최적화의 방법일 수 있어요.
아래는 폼 로직을 클라이언트쪽에서 처리할 경우의 코드입니다.
"use client"
import React, { useState } from "react";
function ClientForm() {
const [formData, setFormData] = useState({ name: "", email: "" });
const [errors, setErrors] = useState({ name: "", email: "" });
const validate = () => {
const newErrors = {};
if (!formData.name) newErrors.name = "Name is required.";
if (!formData.email) newErrors.email = "Email is required.";
else if (!/^\S+@\S+\.\S+$/.test(formData.email)) newErrors.email = "Email is invalid.";
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
setErrors({ ...errors, [e.target.name]: "" }); // Clear error on change
};
const handleSubmit = async (e) => {
e.preventDefault();
if (!validate()) return; // Stop if validation fails
try {
const response = await fetch("/api/submit", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(formData),
});
const data = await response.json();
console.log("Server Response:", data);
} catch (error) {
console.error("Error submitting form:", error);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<input
type="text"
name="name"
placeholder="Name"
value={formData.name}
onChange={handleChange}
/>
{errors.name && <span style={{ color: "red" }}>{errors.name}</span>}
</div>
<div>
<input
type="email"
name="email"
placeholder="Email"
value={formData.email}
onChange={handleChange}
/>
{errors.email && <span style={{ color: "red" }}>{errors.email}</span>}
</div>
<button type="submit">Submit</button>
</form>
);
}
export default ClientForm;
ClientForm
컴포넌트의 역할은 다음과 같습니다.
위에 대한 로직들이 ClientForm에 집약됩니다.
컴포넌트는 UI에 대한 책임말고도 많은 책임을 담당하고 있죠. 물론 custom hook을 통해 분리해도 되죠.
또한 클라이언트 로직이 추가됨으로써 클라이언트 Javascript 번들 크기가 증가합니다.
다음은 server action을 통해 폼 로직을 처리하는 코드입니다.
'use server'
import { redirect } from 'next/navigation'
export async function createUser(prevState: any, formData: FormData) {
const res = await fetch('https://...')
const json = await res.json()
if (!res.ok) {
return { message: 'Please enter a valid email' }
}
redirect('/dashboard')
}
'use client'
import { useFormState } from 'react-dom'
import { createUser } from '@/app/actions'
const initialState = {
message: '',
}
export function Signup() {
const [state, formAction] = useFormState(createUser, initialState)
return (
<form action={formAction}>
<label htmlFor="email">Email</label>
<input type="text" id="email" name="email" required />
{/* ... */}
<p aria-live="polite">{state?.message}</p>
<button>Sign up</button>
</form>
)
}
action
에서 폼 로직을 처리하고 있습니다. 이 덕분에 Signup
컴포넌트는 UI에만 더욱 집중할 수 있습니다. 뿐만 아니라 state handling하는 코드도 줄었다는 장점이 있습니다. validation 로직 또한 action으로 이관하였습니다. action에서 처리한 에러는 클라이언트에서 useFormState
를 사용하여 UI로 보여줄 수 있습니다.
위처럼 server action을 사용하면 폼 로직을 서버로 이관할 수 있고 에러 처리도 가능하여 컴포넌트가 UI에만 집중할 수 있게 해주고 클라이언트 번들 사이즈를 줄일 수도 있습니다.
server action을 사용하면 next의 cache 기능들을 활용하고 재검증할 수 있습니다. 하지만 server action을 사용하지 않으면 캐시를 재검증할 방법이 없습니다.
여기서 말하는 캐시는 Next의 Data Cache인데요.
먼저 Data Cache에 대해 간략히 짚고 넘어가야 캐시와 재검증을 파악할 수 있습니다.
Data Cache
Next.js has a built-in Data Cache that persists the result of data fetches across incoming server requests and deployments.
Next.js는 서버에서 데이터를 요청(fetch)할 때, 데이터를 메모리에 저장해두는 Data Cache라는 공간을 제공하는데요.
Next.js의 Data Cache는 서버 컴포넌트에서만 적용됩니다.
이 Data Cache는 서버 요청 사이에서도 유지되며, 서버가 다시 시작되거나 코드가 배포(deployment)되더라도 계속 유지될 수 있습니다.
즉,nextjs 서버 캐싱 공간에 백엔드 API에서 불러왔던 데이터가 저장되있다는 것인데요.
때문에 DB에 있는 데이터가 변경된다하더라도 DB에 있는 변경된 데이터를 가져오는 것이 아닌, nextjs 서버내 캐싱 데이터를 계속해서 가져오는 것입니다.
// 퀴즈 상세 조회(상세 URL)
async fetchQuizDetailByUrl(detailUrl: string): Promise<IResponse<QuizItem>> {
return this.request<QuizItem>(`quiz/detail-url/${detailUrl}`, {
method: "GET",
cache: "no-store",
});
}
const Page = async ({
params
}:{
params:{
detailUrl:string
}
}) => {
const { detailUrl } = await params
const {data} = await quizApiHandler.fetchQuizDetailByUrl(detailUrl)
return (
<QuizDetails
quizData={data}
/>
);
};
위처럼 서버컴포넌트에서 data fetching(GET)을 해올 때,Data Cache를 활용할 수 있습니다.
이 Data Cache를 무효화하기 위해서는 Next 기능인 revalidatePath
또는 revalidateTag
를 사용해야 재검증할 수 있습니다. 이는 서버에서만 사용할 수 있는 기능입니다.
즉, 서버컴포넌트에서 data fetching(GET)한 데이터를 업데이트하기 위해서는 data mutation(POST...DELETE)을 할 시에, 서버 함수인 revalidatePath
나 revalidateTag
를 사용해야하죠.
// app/data/page.tsx
export default async function Page() {
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/data`, {
next: { tags: ["data"] }, // "data" 태그로 캐싱 그룹화
});
const data = await response.json();
return (
<div>
<h1>Server Component</h1>
<p>Data: {data.name}</p>
<UpdateDataComponent initialData={data.name} />
</div>
);
}
// app/data/UpdateDataComponent.tsx
"use client";
import { useState } from "react";
import { updateDataServer } from "./actions";
export default function UpdateDataComponent({ initialData }: { initialData: string }) {
const [name, setName] = useState(initialData);
const [loading, setLoading] = useState(false);
const handleUpdate = async () => {
setLoading(true);
try {
await updateDataServer(name); // Server Action 호출
console.log("Data updated and cache invalidated!");
} catch (error) {
console.error("Update failed:", error);
} finally {
setLoading(false);
}
};
return (
<div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Update data"
/>
<button onClick={handleUpdate} disabled={loading}>
{loading ? "Updating..." : "Update"}
</button>
</div>
);
}
// app/data/actions.ts
"use server";
import { revalidateTag } from "next/cache";
export async function updateDataServer(newName: string) {
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/data`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: newName }),
});
if (!response.ok) throw new Error("Failed to update data");
// "data" 태그의 캐시 무효화
revalidateTag("data");
}
위처럼 updateDataServer
라는 server action의 revalidateTag
통해 페이지 컴포넌트에서 호출하는 API 데이터를 재검증할 수 있죠.
하지만 클라이언트에서는 revalidatePath
나 revalidateTag
를 사용할 수 없습니다. 서버에서 사용할 수 있는 기능이니깐요.
// app/data/UpdateDataComponent.tsx
"use client";
import { useState } from "react";
import { updateDataServer } from "./actions";
export default function UpdateDataComponent({ initialData }: { initialData: string }) {
const [name, setName] = useState(initialData);
const [loading, setLoading] = useState(false);
const handleUpdate = async () => {
setLoading(true);
try {
await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/data`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: newName }),
});
revalidateTag("data"); // 사용 불가
} catch (error) {
console.error("Update failed:", error);
} finally {
setLoading(false);
}
};
return (
<div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Update data"
/>
<button onClick={handleUpdate} disabled={loading}>
{loading ? "Updating..." : "Update"}
</button>
</div>
);
}
위와 같이 handleUpdate
에서는 revalidate 기능을 사용할 수 없어 페이지 컴포넌트의 fetch Data를 재검증할 방법이 없어요.
페이지 컴포넌트의 fetch API 옵션을
cache:no-store
로 설정하면 캐시를 사용하지 않게 할 순 있어요.
다음과 같이 서버 컴포넌트에서 Data Cache를 사용하지 않고 클라이언트 컴포넌트에서 통신하여 react-query 캐싱 같은 것으로 활용할 수 있겠죠.
만약 데이터 캐싱을 한다면 다음과 같이 리액트 쿼리를 쓰든가 하여 데이터를 불러오는 API를 클라이언트 컴포넌트에서 호출하여 캐싱 전략을 사용해야하죠.
"use client";
import { useState } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
async function fetchData() {
const response = await fetch("/api/data");
if (!response.ok) throw new Error("Network response was not ok");
return response.json();
}
async function updateData(newName: string) {
const response = await fetch("/api/data", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: newName }),
});
if (!response.ok) throw new Error("Failed to update data");
return response.json();
}
export default function DataComponent() {
const queryClient = useQueryClient();
const { data, isLoading, error } = useQuery(["data"], fetchData);
const mutation = useMutation(updateData, {
onSuccess: () => {
// 데이터 변경 시 캐시를 무효화하거나 갱신
queryClient.invalidateQueries(["data"]);
},
});
const [inputValue, setInputValue] = useState("");
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<h1>Data: {data.name}</h1>
<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Update data"
/>
<button onClick={() => mutation.mutate(inputValue)}>Update</button>
</div>
);
}
따라서 Data Cache 기능을 사용하고, 이를 클라이언트 컴포넌트에서 data mutation해야한다면 server action을 사용해야합니다.
server action을 사용하면 전통적인 클라이언트-서버 통신 방식보다 효율적으로 렌더링 할 수 있습니다.
문서에는 server action 관련 내용 중 다음과 같은 내용이 있습니다.
When an action is invoked, Next.js can return both the updated UI and new data in a single server roundtrip.
server action을 사용하면 업데이트된 UI와 업데이트된 데이터를 한번에 받을 수 있다는 건데요.
여기서 업데이트된 UI와 새로운 데이터는 다음을 말합니다.
업데이트된 UI: 서버에서 처리된 결과에 따라 클라이언트에게 즉시 갱신된 사용자 인터페이스(UI)가 전달
새로운 데이터: 데이터베이스 수정이나 외부 API 호출 등을 통해 얻어진 갱신된 데이터가 클라이언트로 함께 반환
이 과정은 전통적인 클라이언트-서버 통신 방식보다 효율적입니다. 일반적으로 클라이언트에서 데이터를 서버에 요청하고, 서버는 이를 처리한 뒤 응답으로 데이터를 반환하는 과정이 따로따로 진행됩니다. 하지만 Server Action은 위 과정을 한번에 제공합니다.
Server Action 호출 시, Next.js는 다음과 같은 단계를 거칩니다
클라이언트에서 액션 호출:
Server Action 함수가 호출되면, 브라우저는 이 요청을 POST 메서드로 서버에 전달합니다.
서버에서 데이터 처리:
외부 API를 호출합니다. 이 과정에서 필요한 로직과 데이터를 서버에서 처리합니다.
업데이트된 데이터와 UI 동기화:
서버는 처리된 결과를 기반으로 클라이언트에게 새로운 데이터를 반환합니다.
동시에 서버는 해당 데이터로 렌더링된 업데이트된 UI를 생성하여 클라이언트로 전달합니다.
클라이언트 갱신:
클라이언트는 서버에서 반환된 데이터와 UI를 받아 새롭게 렌더링합니다.
이때 페이지 리로드 없이도 화면이 갱신됩니다.
이에 대한 장점은
예시 코드로 살펴보겠습니다.
우선 클라이언트 컴포넌트에서 데이터를 가져오고 업데이트하는 방식에 대한 코드입니다.
'use client';
import { useState, useEffect } from 'react';
export function DataFetcher() {
const [data, setData] = useState<{ id: number; name: string } | null>(null);
const [input, setInput] = useState('');
// 데이터 가져오기
const fetchData = async () => {
const response = await fetch('/api/item', { method: 'GET' });
const result = await response.json();
setData(result);
};
// 데이터 갱신하기
const updateData = async () => {
const response = await fetch('/api/item', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: input }),
});
if(response.ok) {
await fetchData()
}
};
useEffect(() => {
fetchData(); // 컴포넌트 마운트 시 데이터 가져오기
}, []);
return (
<div>
{data ? (
<p>
ID: {data.id}, Name: {data.name}
</p>
) : (
<p>Loading...</p>
)}
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Update name"
/>
<button onClick={updateData}>Update</button>
</div>
);
}
위와 같은 과정으로 데이트를 업데이트합니다.
갱신한 뒤에 , 데이터 조회하는 네트워크 통신과 렌더링 작업이 따로따로죠.
// app/actions.ts
'use server';
let item = { id: 1, name: 'Initial Item' }; // 초기 데이터
// 데이터 가져오기
export async function fetchData() {
return item;
}
// 데이터 갱신하기
export async function updateData(name: string) {
item = { id: 1, name }; // 데이터 갱신
return item;
}
// app/page.tsx
import { fetchData } from '@/app/actions';
import ClientComponent from '@/app/client-component';
export default async function Page() {
// 서버에서 데이터 불러오기
const data = await fetchData();
return (
<div>
<h1>Server Component</h1>
<p>
ID: {data.id}, Name: {data.name}
</p>
{/* 데이터를 Client Component로 전달 */}
<ClientComponent initialData={data} />
</div>
);
}
'use client';
import { useState } from 'react';
import { updateData } from '@/app/actions';
export default function ClientComponent({
initialData,
}: {
initialData: { id: number; name: string };
const [input, setInput] = useState('');
// 데이터 갱신하기
const handleUpdate = async () => {
const updatedData = await updateData(input); // Server Action 호출
};
return (
<div>
<h2>Client Component</h2>
<p>
ID: {data.id}, Name: {data.name}
</p>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Update name"
/>
<button onClick={handleUpdate}>Update</button>
</div>
);
}
server action으로 호출할 때에는
1. 데이터 갱신(updateData)
2. 데이터 조회 + 갱신된 데이터로 렌더링
전통적인 방식에서는 데이터 조회와 렌더링을 따로따로 하였지만 server action 방식에서는 한번에 처리할 수 있습니다.
이렇게 server action을 통하여 서버에서 데이터를 미리 처리하면 클라이언트는 최소한의 작업만 수행할 수 있습니다.
이처럼 대부분의 상황에서 server action을 사용하면 이점이 많습니다. 하지만 항상 data mutation시, server action을 써야할까요?
만약 data Mutation이 자주 발생하는 상황이라면 server action보다는 전통적인 방식이 더 나을 수 있습니다.
무한스크롤,채팅,지도와 같이 mutation-fetching이 거의 계속 발생하는 상황일 때, server action을 사용하면 웹 서버(next)는 부담을 느낄 수 있습니다.
계속해서 웹 서버는 렌더링 역할과 API 통신의 중계 역할까지 맡아야하니까요.
따라서 위와 같은 상황에서는 server action을 사용하기보단 전통적인 방식을 따르는 것이 더 좋을 수 있습니다.