How the Spotify Currently Playing Widget Works
An architectural walkthrough of building a server-side cached Spotify OAuth route handler in Next.js to display live listening activity securely.
01. THE CONTEXT
I wanted to display my currently playing Spotify track on the website's desk header. However, making direct client-side requests to Spotify's Web API exposes secret credentials and quickly hits rate limits (HTTP 429) when multiple visitors view the site.
02. THE APPROACH
I engineered an isolated server-side API route (`/api/spotify/currently-playing/route.ts`) powered by a backend helper in `lib/spotify.ts`. The server requests access tokens via Spotify OAuth 2.0 refresh tokens, caches both the access token and currently playing response in server memory, and returns a sanitized JSON payload to the client widget.
03. THE IMPLEMENTATION
The helper uses dual in-memory cache pointers: one for the OAuth access token (valid for ~1 hour) and one for currently playing track data (valid for 15 seconds).
OAuth Refresh Flow & Token Caching
The `getAccessToken()` function checks if the cached access token is still valid. If expired, it sends a Base64-encoded Client Credentials request to `https://accounts.spotify.com/api/token` using environment variables.
let cachedAccessToken: string | null = null;
let tokenExpiresAt = 0;
async function getAccessToken(): Promise<string> {
if (cachedAccessToken && Date.now() < tokenExpiresAt - 60000) {
return cachedAccessToken;
}
const basic = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
const response = await fetch("https://accounts.spotify.com/api/token", {
method: "POST",
headers: {
Authorization: `Basic ${basic}`,
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refreshToken,
}),
cache: "no-store",
});
const data = await response.json();
cachedAccessToken = data.access_token;
tokenExpiresAt = Date.now() + data.expires_in * 1000;
return data.access_token;
}// Credentials remain isolated on the server. The token is reused across client requests until expiry.
Response Normalization & Rate-Limit Protection
The endpoint intercepts HTTP 204 (idle player), HTTP 401 (token refresh retry), HTTP 429 (rate-limit backoff), and HTTP 500 error codes, returning a uniform `SpotifyResponse` contract to prevent client UI crashes.
export async function getCurrentlyPlaying(): Promise<SpotifyResponse> {
if (cachedPlayingData && Date.now() < playingDataExpiresAt) {
return cachedPlayingData;
}
try {
const accessToken = await getAccessToken();
const response = await fetch("https://api.spotify.com/v1/me/player/currently-playing", {
headers: { Authorization: `Bearer ${accessToken}` },
cache: "no-store",
});
if (response.status === 204 || response.status === 404) {
const result = { isConfigured: true, isPlaying: false, track: null, timestamp: Date.now() };
cachedPlayingData = result;
playingDataExpiresAt = Date.now() + 15000;
return result;
}
// Parse track payload & cache for 15s
} catch (error) {
return { isConfigured: true, isPlaying: false, track: null, timestamp: Date.now(), error: "connection-error" };
}
}// In-memory caching guarantees that client polling (every 15s) never exceeds Spotify API quota thresholds.
04. WHAT CHANGED (VERIFIED)
- ✓Created backend utility `lib/spotify.ts` with OAuth token refresh and in-memory response caching.
- ✓Created server route `/api/spotify/currently-playing/route.ts` delivering lightweight normalized JSON.
- ✓Built client component `CurrentlyPlaying.tsx` with polling interval and clean offline fallbacks.
05. WHAT I LEARNED
- •Server-side proxy routes with memory caching protect API keys while insulating application UI from third-party rate limits.
- •Handling empty HTTP 204 responses gracefully is essential for audio player status endpoints.
06. RELATED WORK & SERVICES
Related Case Studies:
Tactile Portfolio Website
A Next.js developer portfolio with a custom dark tactile desk-themed visual design system and real Spotify integration.
[ READ CASE STUDY → ]Related Freelance Services:
HAVE A SIMILAR PROJECT IN MIND?
Send me your brief. Let's discuss how to apply clean engineering solutions to your website or web application.