unsplah api 호출 제한 변경기

최종욱·2025년 4월 18일

Uuno

목록 보기
6/7
post-thumbnail

🚨 문제 상황

무료 Unsplash API는 1시간당 50회 호출 제한이 있어, 다음과 같은 UX 흐름에서 빠르게 한도에 도달하는 문제가 발생했다:

  • 🔍 검색어 입력 시 → 실시간 API 호출

  • 📜 스크롤 이동 시 → 무한스크롤 기반 추가 호출

⛔ 디바운싱을 적용해도 TanStack Query의 fetchNextPage와 중첩되어 호출량이 급증, 기능이 차단되는 상황이 빈번히 발생했다.


🔧 초기 대응: 서버 측 200장 preload → 클라이언트 필터링

요청 수를 줄이기 위해, 서버에서 4페이지(50장 × 4)의 이미지를 한 번에 받아와 클라이언트에서 필터링하는 구조로 변경했다.

export async function GET() {
  const allImages: UnsplashImage[] = [];

  const pageRequests = Array.from({ length: 4 }, (_, i) =>
    fetch(`${BASE_URL}?page=${i + 1}&per_page=50&client_id=${ACCESS_KEY}`).then((res) => res.json())
  );

  const results = await Promise.all(pageRequests);
  for (const pageData of results) {
    if (Array.isArray(pageData)) allImages.push(...pageData);
  }

  return NextResponse.json(allImages);
}

⚠️ 하지만 두 가지 문제가 발생했다

  1. 🔤 한글 검색 정확도 부족 (단순 포함 필터링으로 처리했기 때문)
  2. 🖼️ 이미지 탐색성 저하 (제한된 200장 내에서만 탐색 가능)

✅ 최종 해결: 프로덕션 API 승인 → 무한스크롤 + 검색 구조 복구

API 쿼터를 확장(프로덕션 승인)받은 뒤, 다시 검색어 기반 호출과 무한스크롤을 활성화하는 구조로 복구했다.

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const query = searchParams.get('query') || '';
  const page = Number(searchParams.get('page') || '1');
  const perPage = Number(searchParams.get('per_page') || 20);

  if (page > MAX_UNSPALSH_API_PAGES) {
    return NextResponse.json(
      { error: '요청 페이지 수 제한 초과' },
      { status: 429 }
    );
  }

  const baseURL = query
    ? `https://api.unsplash.com/search/photos`
    : `https://api.unsplash.com/photos`;

  const url =
    `${baseURL}?client_id=${ENV.UNSPLASH_ACCESS_KEY}` +
    `&page=${page}&per_page=${perPage}` +
    (query ? `&query=${encodeURIComponent(query)}` : '');

  const res = await fetch(url, { next: { revalidate: 3600 } });

  if (!res.ok) {
    const errorData = await res.json().catch(() => null);
    return NextResponse.json(
      { error: '이미지 불러오기 실패', details: errorData || res.statusText },
      { status: res.status }
    );
  }

  const json = await res.json();

  return query
    ? NextResponse.json({ results: json.results, total_pages: json.total_pages })
    : NextResponse.json(json);
}

💡 고려 사항 및 결과

  • ✅ 검색어 유무에 따라 search/photos 또는 photos API로 자동 분기

  • ✅ TanStack Query 기반 무한스크롤 구조에 적합

  • ✅ revalidate: 3600을 적용하여 1시간 단위 캐싱으로 서버 부담 최소화

profile
항상 “Why?”로 시작하는 프론트엔드 개발자

0개의 댓글