publicFetch.ts 1.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
  1. import 'server-only';
  2. import { ResultDto } from '@/types/response/common';
  3. const API_URL = process.env.API_URL;
  4. type PublicFetchOptions = {
  5. // Next Data Cache 재검증 주기(초). 기본 300초.
  6. revalidate?: number;
  7. // revalidateTag 로 무효화할 태그
  8. tags?: string[];
  9. headers?: Record<string, string>;
  10. };
  11. // 익명 공개 데이터 전용 서버 fetch — 요청자 쿠키/토큰을 붙이지 않으므로 응답이 모든 사용자 공통이고,
  12. // 따라서 Next Data Cache(revalidate/tags)로 요청 간 캐시가 안전하다. 라우트가 cookies()/headers() 로
  13. // 동적이어도 개별 fetch 는 Data Cache 에서 서빙된다.
  14. // ⚠️ 사용자별 데이터·인증 필요 호출은 반드시 기존 fetchJson 사용(그쪽은 쿠키/Authorization 주입 → 캐시 금지).
  15. export async function fetchPublicJson<T>(url: string, options: PublicFetchOptions = {}): Promise<ResultDto<T>>
  16. {
  17. const { revalidate = 300, tags, headers } = options;
  18. try {
  19. const actionURL = `${API_URL}${url.startsWith('/') ? url : `/${url}`}`;
  20. const res = await fetch(actionURL, {
  21. method: 'GET',
  22. headers: {
  23. 'Accept': 'application/json',
  24. ...(headers ?? {})
  25. },
  26. next: {
  27. revalidate,
  28. ...(tags && tags.length > 0 ? { tags } : {})
  29. }
  30. });
  31. if (res.status === 204) {
  32. return { success: true, status: 204, message: null, data: null, errors: null } satisfies ResultDto<T>;
  33. }
  34. const contentType = res.headers.get('content-type');
  35. if (!contentType || !contentType.includes('application/json')) {
  36. return { success: false, status: res.status, message: null, data: null, errors: null } satisfies ResultDto<T>;
  37. }
  38. return await res.json() as ResultDto<T>;
  39. } catch (err) {
  40. const message = err instanceof Error ? err.message : '서버와 통신이 불가합니다.';
  41. console.warn(`[500] ${message}`);
  42. return { success: false, status: 500, message, data: null, errors: null } satisfies ResultDto<T>;
  43. }
  44. }