[ ← BACK TO NOTES INDEX ]
FIELD NOTE // TECHNICAL
2026-02-207 min read

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.

#Next.js App Router#Spotify API#OAuth 2.0#In-Memory Caching

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.

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.

let me know what you're building