블로그에 글을 포스팅하고, 포스팅된 전체 글을 조회할 수 있는 기능을 Next.Js 13 버전으로 개발하고 있었다. 기존의 React 기반 프로젝트 같은 경우에는, React-query의 invalidateQueries(쿼리 무효화)를 이용하여 손쉽게 데이터를 동기화 할 수 있다. Next.Js에서는 어떤 방법을 사용하면 될까?
우선 코드를 보도록 하자.
// createPostApi.ts
async function createPostApi(userData: ICreatePostValues) {
const response = await fetch(
process.env.NEXT_PUBLIC_API_BASE_URL + "/posts/post/add",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(userData),
}
);
const data = await response.json();
return { response, data };
}
export default createPostApi;
블로그 글을 작성하는 API 통신 함수이다. userData를 매개변수로 받아, json문자열로 변환하여 서버에 전송하는 기능을 한다.
// getPostsAllApi.ts
async function getPostsAllApi() {
const response = await fetch(
process.env.NEXT_PUBLIC_API_BASE_URL + "/posts",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
);
const data = await response.json();
return { data };
}
export default getPostsAllApi;
포스팅된 전체 글들을 조회하는 API 통신 함수이다.
글을 작성해도 새로운 글들이 화면에 나타나지 않는 문제가 발생했다. 심지어 새로고침을 해도 오직 이전 글들만 보였다.
문제의 원인은 캐시였다. 서버 측에서 데이터가 캐시되어 초기 상태의 데이터가 저장되어 있었다. 그 결과, 아무리 글을 작성해도 API가 재통신되지 않았고, 이로 인해 이전의 데이터만 계속해서 보여지고 있었던 것이다.
Next.js 공식 문서를 참고해 두 가지 해결 방안을 찾았다. 첫 번째는 캐시를 전혀 사용하지 않는 방법이었고, 두 번째는 Next.js의 Server Action 기능 중 하나인 revalidate를 이용하는 것이었다.
fetch('https://...', { cache: 'no-store' })
이 방법은 데이터 동기화를 매우 간단하게 해결할 수 있지만, 웹 애플리케이션의 성능 저하를 야기할 수 있다. 따라서 이 방법은 바람직하지 않다고 생각했다.
그럼 두번째 방법을 알아보자.
// getPostsAllApi.ts
async function getPostsAllApi() {
const response = await fetch(
process.env.NEXT_PUBLIC_API_BASE_URL + "/posts",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
// 이 부분 추가!!
next: { tags: ["postsAll"] },
}
);
const data = await response.json();
return { data };
}
export default getPostsAllApi;
먼저, 글 조회 기능을 담당하는 fetch 함수에, 두 번째 인자로 들어가는 객체에 'next' 옵션을 추가했다. 이 'next' 옵션은 객체를 가지며, 여기에 'tags'라는 key 값과 함께 "postsAll"을 배열에 넣어 지정했다. 이는 React-query의 query key와 유사한 역할을 한다.
// createPostApi.ts
"use server";
import { revalidatePath, revalidateTag } from "next/cache";
async function createPostApi(userData: ICreatePostValues) {
const response = await fetch(
process.env.NEXT_PUBLIC_API_BASE_URL + "/posts/post/add",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(userData),
}
);
// postsAll 키 값을 가지는 데이터를 전부 동기화
revalidateTag("postsAll");
// refresh
revalidatePath("/createPostPage");
// 예외 처리를 위한 변수와 조건문
let responseStatus;
let responseStatusText;
if (response.status === 200) {
responseStatus = 200;
} else {
responseStatus = response.status;
responseStatusText = response.statusText;
}
const data = await response.json();
return { responseStatus, responseStatusText, data };
}
export default createPostApi;
이후에, 블로그 글 작성 기능의 API 통신을 담당하는 함수를 수정했다. 이 과정에서 'reavlidatePath'와 'revalidateTag' 두 함수를 사용했으며, 이 함수들을 사용하기 위해서는 코드 상단에 "use server"를 작성해야 했다. revalidateTag는 createPostApi 함수 내부에서 호출되었는데, 여기에 getPostsAllApi 함수에서 사용된 키 값인 "postsAll"을 인자로 전달했다. 이는 "postsAll"을 키 값으로 갖는 모든 데이터들을 동기화 한다는 것을 의미한다. 이는 React-query의 invalidateQueries와 유사하다. 그리고 요청이 끝난 후 현재 페이지를 refresh하기 위해 'revalidatePath'를 호출하고, 인자로 현재 페이지의 url을 전달해주었다.
그리고, "use server"로 작성된 Server Action 함수는 API 응답의 객체 자체를 반환할 수 없다. 따라서, createPostApi 함수 내에서 약간의 조건문을 사용하여 상태(status)를 반환했다. 이로 인해 함수가 효율적으로 응답을 처리하고 필요한 정보를 제공할 수 있게 되었다.
이러한 Server Action 기능을 이용하고 싶다면 next.config.js의 설정을 수정해주어야 한다.
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
serverActions: true,
},
};
module.exports = nextConfig;
Next.Js 환경에서 React-query 라이브러리 없이 데이터를 재검증하고 동기화하는 방법을 배웠다. Next.Js는 기본적으로 서버 사이드 렌더링을 지원하기 때문에, 클라이언트 사이드 렌더링 기반인 React의 방식보다는 Next.Js의 방법론을 따르고자 했다. Server Action은 아직 실험적인 기능이지만, 후에 꽤나 유용하게 사용될 수 있는 가능성이 높아 보인다고 생각한다.