이번에 next.js로 프로젝트를 만들었는데 (wowmazon) 토큰 관리에서 꽤나 어려움을 느꼈다. 이때 마주한 next.js의 서버액션에 대해서 알아보려고한다.
서버액션 : next.js 서버측에서 실행되는 비동기함수.
- 데이터 변경과 폼 제출 관리하는데 사용되며, 서버액션을 통해 서버에서 데이터를 처리하고 결과를 클라이언트에 전달한다.
서버액션은 비동기적으로 실행 되기때문에 서버와 클라이언트 간의 통신 동안 사용자 인터페이스를 멈추지않고 사용자가 계속 사용할 수 있도록 해준다. 그외에도,,
<form>
태그와 연동되어 서버측에서 사용자 입력을 받아 처리할 수 있음."use server"
키워드 설정위에서 말했듯 저 키워드 하나로 서버액션을 간단히 정의할 수 있다. 함수나 파일 상단에 배치하여 해당 함수 또는 파일을 서버에서만 실행되도록 지정할 수 있다.
"use server";
export async function addNumbers(a, b) {
return a + b;
}
서버컴포넌트와 클라이언트 컴포넌트에서 모두 사용가능
한 서버액션서버액션은 클라이언트와 서버컴포넌트 모두에서 사용가능하다.
위에 서버액션 파일 안에있는 addNumber()
함수를 클라이언트 컴포넌트에서 불러올 수 있다.
"use client";
import { useState } from "react";
import { addNumbers } from "./actions";
export default function HomePage() {
const [result, setResult] = useState(null);
async function handleAddition() {
const sum = await addNumbers(5, 10); // 서버에서 계산
setResult(sum); // 결과를 상태에 저장
}
return (
<div>
<h1>Simple Server Action Example</h1>
<button onClick={handleAddition}>Add 5 + 10</button>
{result !== null && <p>Result: {result}</p>}
</div>
);
}
이런 처리 방식은 next.js 어플리케이션에서 비동기적으로 작업을 관리할 수 있으며, 데이터처리가 서버 중앙에서 관리되고 클라이언트와 서버간의 일관된 데이터 흐름을 유지할 수 있다.
// 페이지 컴포넌트
export default function Page() {
async function handleSubmit(Data) {
'use server';
// 폼 데이터 처리 로직
console.log(formData.get('name'));
}
return (
<form action={handleSubmit}>
<input name="username" type="text" placeholder="이름" />
<button type="submit">제출</button>
</form>
);
}
서버액션은 form 태그와 함께 연동해서 사용할 수 있다. form 태그 안에 action이라는 속성이 존재하는데 이걸 사용하면 폼이 제출될때 자동으로 서버액션이 호출된다고 한다.
(아쉽게도 나는 form 태그 처리시에 사용하지않았다. 예시는 간단히 이렇게만 하고 넘어가겠다.)
- 데이터베이스 쿼리
- API 호출
- 데이터 검증 및 처리
그중 우리가 사용한 동작은 api 호출이 되겠다.
"use server"
//카테고리 불어오는 함수
export const getCategoryId = async (
queryParams?: ProductCategoryParamsType
) => {
let stringRecord: Record<string, string> = {};
if (queryParams) {
stringRecord = createQueryString(queryParams);
}
const data = await fetchWithToken<GetProductCategoryResponse>(
"category/",
{},
stringRecord
);
return data;
};
위 서버액션 함수로 카테고리를 불러오는 infiniteQuery 훅 함수를 만들었고 이를 상품 목록 클라이언트 컴포넌트에서 불러왔다.
//카테고리 훅함수
export const useInfiniteCategory = () => {
return useInfiniteQuery({
queryKey: [INFINITE_CATEGORY],
queryFn: ({ pageParam = "" }) => {
const params: ProductCategoryParamsType = pageParam
? { cursor: pageParam }
: {};
return getCategoryId(params);
},
initialPageParam: "",
// 생략
동작 방식은 이러하다.
클라이언트는 서버 액션을 호출. 이 요청은 브라우저에서 Next.js 서버로만 전달되므로 CORS 문제가 발생하지 않는다.
Next.js 서버에서 외부 API를 호출한다. 여기서의 통신은 서버와 서버간의 통신이므로 브라우저 정책인 CORS 정책에 영향을 받지 않는다.
Next.js 서버가 외부 API의 응답을 클라이언트로 반환.
"use server";
import { createURLWithParams } from "@/utils/apis/create-URL-Params";
import { getValidAccessToken } from "@/auth/token";
const NITO_BASE_URL = process.env.NEXT_PUBLIC_NITO_URL;
export const fetchWithToken = async <T>(
endpoint: string,
options: RequestInit = {},
queryParams?: Record<string, string> // 쿼리 파라미터를 위한 인수 추가
): Promise<T> => {
const url = createURLWithParams(`${NITO_BASE_URL}`, endpoint, queryParams);
const token = await getValidAccessToken();
if (!token) {
throw new Error();
}
const response = await fetch(url, {
method: options.method || "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
...options.headers, // 추가 헤더 병합
},
credentials: "include", // 자격증명 포함
...options,
});
if (!response.ok) {
throw new Error(`API 요청 실패: ${response.statusText}`);
}
return response.json() as T;
};
프로젝트에서는 헤더에 토큰값을 싣는 이 함수를 통해 모든 api를 작성했는데 , 이렇게 작성한 함수는 서버에서 실행되며, 브라우저와 직접 통신하지 않고, 서버 간 통신을 통해 외부 API에 접근하기 때문에,
얼레벌레 CORS 에러를 해결하는 데 직접적인 역할을 해버렸다...
(다시 돌아와서)
또한 서버액션은 서버와 클라이언트 간의 데이터 전송시 올바르게 처리되고 전송되도록 보장하기 위해 인자와 반환값이 직렬화가 가능해야한다고 한다.
🖐🏻 여기서 잠깐
React에서 직렬화 가능 데이터란?
React가 데이터를 직렬화하려면 JSON 형태로 변환 가능한 데이터 타입이어야 한다.[일반적으로 허용되는 데이터 타입]
- 기본 타입: string, number, boolean, null
- 객체: JSON 직렬화가 가능한 단순 객체
- 배열: JSON 직렬화가 가능한 배열
- Date: JSON 문자열로 변환됨
[허용되지않는 데이터 타입]
- 함수
- undefined
- Symbol
- Map, Set, WeakMap, WeakSet
- 순환 참조가 포함된 객체
이처럼 서버액션을 잘 활용하면 next.js 애플리케이션에서 폼처리, 데이터 관리 및 비동기 작업을 효율적으로 수행 할 수있다.
(해당 기능은 next.js 14버전 이상부터는 디폴트값으로 추가가되어 따로 nextconfig 파일에서 설정하지 않아도 된다.)
굉장 한데요~~>??? 잘 읽고갑니다