← DEVLOG
Space Science2025.12.216 min read

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

nasa-apifetchcachenext.jsi18n

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.

Content related to this post

Try it yourself