서버 함수(Server Function)는 'use server'로 표시해 서버에서만 실행되지만 클라이언트에서 네트워크 요청으로 호출할 수 있는 async 함수다. 그중 폼 제출이나 데이터 변경처럼 action 맥락(form action, transition)에서 쓰이는 것을 서버 액션(Server Action)이라 부른다. 서버 액션은 서버 함수의 부분집합이다.
// actions.ts
'use server'
export async function createProduct(formData: FormData) {
const session = await auth() // 인가 확인 필수
if (!session) throw new Error('Unauthorized')
const name = z.string().min(1).parse(formData.get('name')) // 입력 검증 필수
await db.product.create({ data: { name } })
revalidatePath('/products') // 관련 캐시 무효화
}<form action={createProduct}><input name="name" /><button>저장</button></form>- 흔한 오해: 서버 컴포넌트에서
await getProducts()로 부르는 일반 데이터 조회 함수는 서버에서 실행될 뿐 서버 함수가 아니다.'use server'가 없으면 클라이언트가 부를 수 있는 엔드포인트가 생기지 않는다 - 동작: 함수 코드는 클라이언트로 가지 않고 참조만 넘어간다(페이로드의
$h). 호출하면 React가 인자를 직렬화해 POST로 보내고 결과를 돌려받는다(RPC). 인자와 반환값은 직렬화할 수 있는 값이어야 한다 - 역할: 읽기는 서버 컴포넌트의
await나 use()로, 쓰기는 서버 액션으로 한다. 서버 액션은 렌더 중이 아니라 사용자 이벤트로 실행되므로 "렌더 중 부수 효과 금지" 규칙과 맞는다(부수 효과와 Effect) - API(Application Programming Interface) 라우트·fetch·로딩 상태를 손으로 배선하던 일을 줄여 준다. 대신 외부 클라이언트가 쓰는 공개 API에는 맞지 않는다
- 폼 상태는 useActionState와 폼 액션와 함께 쓴다. 추가 인자는 숨은 input보다
action.bind(null, id)가 낫다. 숨은 input은 값이 HTML(HyperText Markup Language)에 그대로 드러난다
보안
서버 함수는 앱 UI를 거치지 않고도 POST로 직접 호출할 수 있다. 인자는 전부 클라이언트가 조작할 수 있는 신뢰할 수 없는 입력이다. 함수마다 입력을 검증하고 인증·인가를 확인한다. 예전 API 라우트에서 하던 검사를 그대로 해야 한다(CSRF(Cross-Site Request Forgery)).