NASA APOD — Bringing the Astronomy Picture of the Day to the Web
Integrating the NASA Astronomy Picture of the Day API — date navigation, image/video handling, and dealing with Eastern Time update schedules
The Starting Point — A Universe That Changes Every Day
Since 1995, NASA has selected and published one astronomical image or video each day. This service — Astronomy Picture of the Day, or APOD — is now available as a public API.
The goal wasn't just to display a photo. The aim was to build an interface for browsing through past cosmic images by navigating dates freely.
API Requests — Handling Eastern Time
The first gotcha with the APOD API was timezone. NASA updates the picture at midnight Eastern Time (UTC-5, UTC-4 during DST). For users in Korea (UTC+9), the previous day's picture is technically the latest until 9 AM local time (10 AM during DST).
Using new Date() without adjustment causes requests for dates that don't exist yet from NASA's perspective — resulting in 404 errors.
// NASA APOD updates on Eastern Time (UTC-5)
const EST_OFFSET = -5 * 60; // minutes
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}`;
}
This function determines the default date. When the user selects a date manually, that date is used as-is.
Image vs. Video Branching
The media_type field in the APOD response is either 'image' or 'video'. Videos are usually YouTube URLs.
function extractYouTubeId(url: string): string | null {
const match = url.match(/(?:v=|youtu\.be\/)([^&?]+)/);
return match?.[1] ?? null;
}
When the type is video, an <iframe> embeds the YouTube player. For images, a Next.js Image component handles rendering. Occasionally the URL is Vimeo or a direct link — in those cases, a fallback link to the original URL is shown instead.
Client-Side Caching
Re-calling the API for the same date every time wastes quota. NASA's free API key allows 1,000 requests per hour, making client caching a necessity.
const CACHE_KEY_PREFIX = 'apod_cache_';
function getCachedAPOD(date: string): APODData | null {
return storageGet<APODData | null>(`${CACHE_KEY_PREFIX}${date}`, null);
}
function setCachedAPOD(date: string, data: APODData): void {
storageSet(`${CACHE_KEY_PREFIX}${date}`, data);
}
Each date gets its own storage key. Once fetched, that date's data is available instantly on subsequent visits — no additional API calls needed. No expiration logic is applied since APOD historical data never changes.
Date Navigation
Three ways to navigate dates are supported.
First, previous/next buttons to step one day at a time. Dates before APOD's launch (June 16, 1995) are disabled, as are future dates beyond today's EST date.
Second, a date input field for direct entry. An <input type="date"> with min and max attributes restricts input to the valid range.
Third, a "Today" button to jump back to the latest available date based on EST.
Fetch State Management
Network request states are broken into fine-grained variants.
type FetchState = 'idle' | 'loading' | 'success' | 'error';
During loading, a skeleton UI with a flowing star particle effect is displayed. On error, a message and retry button appear. Missing API key errors and 404s from out-of-range dates produce different user-facing messages.
Closing Thoughts
After finishing the APOD page, the first thing done was pressing the date navigation button repeatedly — watching Hubble images from the late 1990s appear one after another.
A page where some corner of the universe shows up every day. It ended up being the simplest feature built for the project, and the one most likely to draw a long, quiet stare.