이전까진 time-based revalidation 만 진행했었다. 그 이유는 다음과 같다.
1) 현업에서 사용할 경우 react-query 사용
2) 사이드일 경우 ssg, isr, 또는 time-based 만으로 커버 가능
그러나 이번 사이드에선 관리자 페이지를 통한 데이터 수정 이 요구사항에 있어 On-demand revalidtion 을 사용했다.
백엔드의 경우 Spring Boot 를 사용했기 때문에 next.js Full stack 의 경우와 구현이 조금 달랐다. full stack 일 경우 server side 에서 revalidateTag, revalidatePath 를 사용하면 되지만, 스프링 백엔드를 사용하기 때문에 client side 에서 해당 요청을 진행했어야 했다.
(server Action은 굳이 라는 생각에 아직 사용하지 않았습니다.)
그래서 revalidate api 를 추가로 생성해서 사용했다.
// app/api/revalidateTag/route.ts
import { NextRequest, NextResponse } from "next/server";
import { revalidateTag } from "next/cache";
// POST /api/revalidateTag
export async function POST(req: NextRequest) {
try {
const { tag } = await req.json();
if (!tag) {
return NextResponse.json({ message: "Tag is required in the request body" }, { status: 400 });
}
// 리밸레이데션 진행
revalidateTag(tag);
return NextResponse.json({ message: `Revalidated tag "${tag}" successfully` }, { status: 200 });
} catch (error) {
console.error("Error revalidating tag:", error);
return NextResponse.json({ message: "Failed to revalidate tag", error: error.message }, { status: 500 });
}
}
apiUtils.ts 에선 revalidation API Call 을 위한 함수 생성
// src/api/apiUtils.ts
export const revalidate = async ({ tag }: { tag: string[] | string }) => {
await fetch("/api/revalidate", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ tag }),
});
};
service 코드는 아직 개발중이라 미완성 코드이다. 어쨌든 save(), update() 의 마지막에 revalidate 를 넣었다는게 포인트이다.
import { revalidate } from "@/api/apiUtils";
export interface GameGuideEntity extends BaseEntity {
id: number;
firstCategory: string; // 가이드 대분류
title: string;
contents: string;
}
export interface GameGuideRequest extends BaseRequest {
firstCategory: string; // 가이드 대분류
title: string;
contents: string;
}
class GameGuideService {
private static instance: GameGuideService;
private baseUrl: string = `${process.env.NEXT_PUBLIC_BACKEND_API_BASE_URL}/game-guide`;
public static getInstance(): GameGuideService {
if (!GameGuideService.instance) {
GameGuideService.instance = new GameGuideService();
}
return GameGuideService.instance;
}
private async fetchFromApi(endpoint: string, options: RequestInit = {}, revalidatePath?: string): Promise<any> {
const url = `${this.baseUrl}${endpoint}`;
try {
const response = await fetch(url, {
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
next: { tags: [url] },
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error("Fetch error:", error);
throw error;
}
}
async findAll(): Promise<GameGuideEntity[]> {
return this.fetchFromApi("");
}
async findById(id: string): Promise<GameGuideEntity> {
return this.fetchFromApi(`/${id}`);
}
async save(params: GameGuideRequest): Promise<void> {
await this.fetchFromApi("", {
method: "POST",
body: JSON.stringify(params),
});
// 리밸리데이션 콜
await revalidate({ tag: `${this.baseUrl}` });
}
async update(id: string, params: GameGuideRequest): Promise<void> {
const endPoint = `/${id}`;
await this.fetchFromApi(endPoint, {
method: "PATCH",
body: JSON.stringify(params),
});
// 리밸리데이션 콜
await revalidate({ tag: `${this.baseUrl}${endPoint}` });
}
}
const gameGuideServiceInstance = GameGuideService.getInstance();
export { gameGuideServiceInstance as GameGuideService };
next.js 프로젝트를 안한지 6개월쯤 되니 revalidate 관련되어 기억 안나는 점들이 많았다. 그래서 next.js 공식문서를 오랜만에 뒤적거리게 되었는데, 그러다 보니 지금 진행하는 on-demand revalidation 또한 (revalidate Tag) 기억이 안날거 같아서 적어둔다.
개인적으론 react-query 가 조금 더 명시적이기도 하고, 전역적으로 관리하기도 좋다는 생각을 하고 있지만, 더 익숙해서 일수도 있겠다.
아무튼 소규모 사이드에선 next.js 만한게 없다.
이번엔 spring boot 를 억지로 사용한 감이 있는데... 다음 사이드부턴 next.js 로 backend 까지 해봐야할것 같다.
백엔드 개발자로 전향하기 위해 스프링을 배우고 일하고 있지만 그냥 typescript 만으로 가는것도 가성비 측면에서 괜찮은것 같다. 사실 잘 모르겠다. 뭘 쓰건 비슷한거 같아서.