가져오기

const fetchIntakePurposeCategory = async (mid: string, intakePurposeId: number) => {
  const session = await getSession();

  const validatedFields = NutrientIntakePurposes.findOneV1.inputParams.parse({
    pInvokerOperatorId: session?.user.operator_id,
    pThisOperatorMenuIdentifier: mid,
    pIp: session?.user.operator_ip,
    pIntakePurposeId: intakePurposeId,
    pCallDate: spCallDate(),
  });

  const data = await NutrientIntakePurposesRepository.findOneV1(validatedFields);
  return data[0];
};
const cacheKey = (mid: string, intakePurposeId: number) => `${mid}-${intakePurposeId}`;

const cachedFetch = unstable_cache((mid, intakePurposeId) => fetchIntakePurposeCategory(mid, intakePurposeId), {
  tags: (mid, intakePurposeId) => `${mid}-${intakePurposeId}`,
  revalidate: 60, // 60초마다 캐시 재검증
  key: cacheKey,
});

export async function getIntakePurposeCategory(params: {
  mid: string;
  intakePurposeId: number;
}): Promise<ActionResponse<z.infer<typeof NutrientIntakePurposes.findOneV1.outputRs>>> {
  const session = await getSession();

  try {
    const data = await cachedFetch(params.mid, params.intakePurposeId);
    revalidatePath(`/intake-purpose`);

    return {
      status: 'SUCCESS',
      message: '대분류(섭취목적 카테고리 목록)을 불러왔습니다.',
      data: data,
    };
  } catch (error) {
    return errorHandler(error);
  }
}

업데이트

export async function updateIntakePurposeCategory(formData: FormData): Promise<ActionResponse> {
  const session = await getSession();
  try {
    const validatedFields = NutrientIntakePurposes.updateV1.inputParams.parse({
      pInvokerOperatorId: session?.user.operator_id,
      pThisOperatorMenuIdentifier: formData.get('pThisOperatorMenuIdentifier'),
      pIp: session?.user.operator_ip,
      pIntakePurposeId: Number(formData.get('pIntakePurposeId')),
      pIntakePurposeName: formData.get('pIntakePurposeName'),
      pGender: formData.get('pGender') === null ? null : Number(formData.get('pGender')),
      pAdminMemo: formData.get('pAdminMemo'),
      pIsEnabled: Number(formData.get('pIsEnabled')),
      pIsRequired: Number(formData.get('pIsRequired')),
      pCallDate: spCallDate(),
    });

    try {
      await NutrientIntakePurposesRepository.updateV1(validatedFields);
      const cacheKeyToInvalidate = cacheKey(
        validatedFields.pThisOperatorMenuIdentifier,
        validatedFields.pIntakePurposeId,
      );
      await unstable_cache.invalidateTags([cacheKeyToInvalidate]);
      revalidatePath(`/intake-purpose`);
    } catch (error) {
      console.log(error);
    }

    // await NutrientIntakePurposesRepository.updateV1(validatedFields);
    // const cacheKeyToInvalidate = cacheKey(
    //   validatedFields.pThisOperatorMenuIdentifier,
    //   validatedFields.pIntakePurposeId,
    // );
    // await unstable_cache.invalidateTags([cacheKeyToInvalidate]);
    // revalidatePath(`/intake-purpose`);

    return {
      status: 'SUCCESS',
      message: ZOD_SUCCESS_MESSAGE,
    };
  } catch (error) {
    console.log('error', error);
    return errorHandler(error);
  }
}

image.png

문제상황

→ try catch 문안에 try catch로 감싸면 문제없이 작동하지만 그냥 쓰면 위와 같은 에러가 발생한다.

https://github.com/vercel/next.js/issues/50765

실제로 캐시 무효화를 위해 revalidateTag를 다양한 방법으로 사용해 보았지만 의도대로 작동하지 않는 경우가 많았다. 오히려 revalidate를 이용해 캐시 무효화를 하는 것이 안전한 것 같다.

// 섭취목적 카테고리 수정
export async function updateIntakePurposeCategory(formData: FormData): Promise<ActionResponse> {
  const session = await getSession();
  try {
    const validatedFields = NutrientIntakePurposes.updateV1.inputParams.parse({
      pInvokerOperatorId: session?.user.operator_id,
      pThisOperatorMenuIdentifier: formData.get('pThisOperatorMenuIdentifier'),
      pIp: session?.user.operator_ip,
      pIntakePurposeId: Number(formData.get('pIntakePurposeId')),
      pIntakePurposeName: formData.get('pIntakePurposeName'),
      pGender: formData.get('pGender') === null ? null : Number(formData.get('pGender')),
      pAdminMemo: formData.get('pAdminMemo'),
      pIsEnabled: Number(formData.get('pIsEnabled')),
      pIsRequired: Number(formData.get('pIsRequired')),
      pCallDate: spCallDate(),
    });

    await NutrientIntakePurposesRepository.updateV1(validatedFields);
    revalidatePath(`/intake-purpose/${validatedFields.pIntakePurposeId}`);
    return {
      status: 'SUCCESS',
      message: ZOD_SUCCESS_MESSAGE,
    };
  } catch (error) {
    console.log('error', error);
    return errorHandler(error);
  }
}

⇒ 위 방법으로 되는 것 같았지만 빌드에서 결국 문제가 생기고 말았다.