Next.js 14 - revalidate 관련 (w. Spring boot)

Chaedie·2024년 8월 24일
0
post-thumbnail

revalidation 방식 2가지

  • Time-based revalidation
  • On-demand revalidation

이전까진 time-based revalidation 만 진행했었다. 그 이유는 다음과 같다.

1) 현업에서 사용할 경우 react-query 사용
2) 사이드일 경우 ssg, isr, 또는 time-based 만으로 커버 가능

그러나 이번 사이드에선 관리자 페이지를 통한 데이터 수정 이 요구사항에 있어 On-demand revalidtion 을 사용했다.

Spring Boot Backend

백엔드의 경우 Spring Boot 를 사용했기 때문에 next.js Full stack 의 경우와 구현이 조금 달랐다. full stack 일 경우 server side 에서 revalidateTag, revalidatePath 를 사용하면 되지만, 스프링 백엔드를 사용하기 때문에 client side 에서 해당 요청을 진행했어야 했다.

(server Action은 굳이 라는 생각에 아직 사용하지 않았습니다.)

1. Revalidate 용 API 추가

그래서 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 });
  }
}

2. revalidate Util 추가

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 }),
  });
};

3. API Service Class 에 revalidate() 추가

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 만으로 가는것도 가성비 측면에서 괜찮은것 같다. 사실 잘 모르겠다. 뭘 쓰건 비슷한거 같아서.

profile
TIL Blog - Today's Intensive Learning!

0개의 댓글