
Now Playing: how I wired Spotify into my site
On this page
The goal
I wanted a minimal "Now Playing" module that is reliable, lightweight, and easy to reason about. The core idea: use a server route to talk to Spotify, then let the UI poll on a gentle interval. 1
The data flow (server first)
The server route is the only place that touches Spotify. It uses a refresh token to get a short-lived access token, then calls the "currently playing" endpoint.
const TOKEN_ENDPOINT = "https://accounts.spotify.com/api/token";
const NOW_PLAYING_ENDPOINT =
"https://api.spotify.com/v1/me/player/currently-playing";
const getAccessToken = async () => {
if (!clientId || !clientSecret || !refreshToken) {
return null;
}
const basic = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
const body = new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refreshToken,
});
const response = await fetch(TOKEN_ENDPOINT, {
method: "POST",
headers: {
Authorization: `Basic ${basic}`,
"Content-Type": "application/x-www-form-urlencoded",
},
body,
});
if (!response.ok) {
return null;
}
return response.json();
};From there, /api/now-playing maps the payload into a small response shape and
bails early when nothing is playing.
if (!response || response.status === 204 || response.status > 400) {
return NextResponse.json({ isPlaying: false });
}
const song = await response.json();
if (!song?.item) {
return NextResponse.json({ isPlaying: false });
}That early return keeps the UI consistent when playback is idle or Spotify is unreachable. 2
The client loop (small and steady)
The UI polls /api/now-playing once on mount and then every 60 seconds. If the
request fails, I reset the state back to null and show a clean fallback.
const fetchNowPlaying = async () => {
try {
const response = await fetch("/api/now-playing");
const json = (await response.json()) as NowPlayingSong;
if (isMounted) {
setData(json);
}
} catch {
if (isMounted) {
setData(null);
}
}
};This keeps the UI stable without introducing complex caches or hydration edge cases. 3
UI details I like
- When a song is playing, I show animated bars to signal activity.
- When nothing is playing, I show the Spotify glyph and a clean "Not Playing" label.
- The track title only becomes a link when the API gives me a valid URL.
The goal is a small UI that still feels alive.
Final result (live component)
Not Playing
Footnotes
-
The Spotify endpoint returns 204 when there is no active playback. Treating this as an "idle" state keeps the UI predictable and avoids false error modes. ↩
-
The server route always returns a consistent shape. That means the client never has to branch on status codes or Spotify-specific edge cases. ↩
-
A 60 second poll cadence is gentle on the API, aligns with track changes, and avoids excess refresh-token churn. ↩