NASA APOD — 오늘의 천문 사진을 웹으로
NASA Astronomy Picture of the Day API 연동 — 날짜 탐색, 이미지/영상 처리, 미국 동부 시간 기준 업데이트 처리까지
시작 — 매일 바뀌는 우주
NASA는 1995년부터 매일 하나의 천문 사진 또는 영상을 선정해 공개해왔습니다. APOD(Astronomy Picture of the Day)라고 불리는 이 서비스는 현재 NASA 공개 API로도 제공됩니다.
단순히 사진을 표시하는 것 이상으로, 날짜를 탐색하면서 과거의 우주 사진들을 찾아볼 수 있는 인터페이스를 만들고 싶었습니다.
API 요청 — 미국 동부 시간 처리
APOD API의 첫 번째 함정은 시간대였습니다. NASA는 미국 동부 시간(UTC-5, 서머타임 시 UTC-4) 기준으로 자정에 새 사진을 업데이트합니다. 한국(UTC+9) 기준으로는 오전 9시(서머타임 기준 10시) 이전에는 전날 사진이 최신입니다.
날짜를 그냥 new Date()로 계산하면 한국 아침 시간대에 아직 존재하지 않는 날짜로 요청을 보내 404 오류가 발생합니다.
// NASA APOD는 미국 동부 시간(UTC-5) 기준으로 업데이트
const EST_OFFSET = -5 * 60; // 분
function getESTDate(): string {
const now = new Date();
const utc = now.getTime() + now.getTimezoneOffset() * 60000;
const est = new Date(utc + EST_OFFSET * 60000);
const y = est.getFullYear();
const m = String(est.getMonth() + 1).padStart(2, '0');
const d = String(est.getDate()).padStart(2, '0');
return `${y}-${m}-${d}`;
}
이 함수로 구한 날짜를 기본 날짜로 사용하고, 사용자가 날짜를 직접 선택할 때는 해당 날짜 그대로 요청합니다.
이미지와 영상 분기
APOD의 media_type 필드는 'image' 또는 'video'입니다. 영상인 경우 대부분 YouTube URL이 제공됩니다.
function extractYouTubeId(url: string): string | null {
const match = url.match(/(?:v=|youtu\.be\/)([^&?]+)/);
return match?.[1] ?? null;
}
영상일 때는 <iframe>으로 YouTube를 임베드하고, 이미지일 때는 Next.js Image 컴포넌트로 렌더링합니다. 간혹 YouTube가 아닌 Vimeo나 직접 링크인 경우도 있어서, YouTube ID를 추출하지 못하면 원본 URL로 리다이렉트하는 링크를 대신 표시합니다.
클라이언트 캐시
같은 날짜를 반복해서 탐색할 때마다 API를 호출하는 것은 API 키 사용량 낭비입니다. NASA 무료 키는 시간당 1,000회로 제한되어 있어서 클라이언트 캐싱이 필요했습니다.
const CACHE_KEY_PREFIX = 'apod_cache_';
function getCachedAPOD(date: string): APODData | null {
const raw = storageGet<APODData | null>(`${CACHE_KEY_PREFIX}${date}`, null);
return raw;
}
function setCachedAPOD(date: string, data: APODData): void {
storageSet(`${CACHE_KEY_PREFIX}${date}`, data);
}
날짜별로 키를 분리해 저장하므로, 한 번 조회한 날짜는 페이지를 새로고침해도 API 호출 없이 즉시 표시됩니다. 캐시 만료 처리는 별도로 두지 않았습니다. APOD는 과거 데이터가 변경되지 않기 때문입니다.
날짜 네비게이션
날짜 이동은 세 가지 방식으로 구현했습니다.
첫째, 이전/다음 버튼으로 하루씩 이동합니다. APOD의 최초 날짜는 1995년 6월 16일이므로 이보다 이전 날짜는 비활성화합니다. 미래 날짜 요청도 EST 기준 오늘 이후는 막습니다.
둘째, 날짜 입력 필드에 직접 날짜를 입력할 수 있습니다. <input type="date">를 사용했고 min/max 속성으로 유효 범위를 제한했습니다.
셋째, "오늘" 버튼으로 EST 기준 최신 날짜로 돌아올 수 있습니다.
로딩 상태 관리
네트워크 요청 중 상태를 세분화해서 처리했습니다.
type FetchState = 'idle' | 'loading' | 'success' | 'error';
loading 상태에서는 별 파티클이 흘러가는 스켈레톤 UI를 표시하고, error 상태에서는 오류 메시지와 함께 재시도 버튼을 노출합니다. 특히 API 키가 없는 경우와 날짜 범위 초과로 인한 404를 구분해서 메시지를 다르게 표시했습니다.
마치며
APOD 페이지를 처음 완성하고 날짜 탐색 버튼을 연속으로 눌렀을 때, 1990년대 허블 우주망원경 사진들이 차례로 나타나는 것을 한참 동안 바라봤습니다.
매일 우주의 어느 곳 하나가 화면에 나타나는 페이지 — 개발한 기능들 중 가장 단순하지만 가장 오래 들여다보게 되는 페이지입니다.