How to Use Spotify API with Next.js Server Actions

Aug 8, 2025

7 min read


Spotify's Web API allows developers to display real-time information about what a user is currently listening to.

In this tutorial, we'll build a Spotify Currently Playing in Next.js 15 using Server Actions in `actions.ts` to securely handle Spotify credentials and API calls.

Setting Up Your Next.js Project

Before we start, ensure you already have Node.js and npm installed. After that, follow these steps to set up your Next.js project:

  1. Create a new Next.js app:

    sh
    npx create-next-app@latest next-spotify
    cd next-spotify
  2. Create a `.env.local` file in the root of your project to store sensitive information like your Spotify credentials.

    SPOTIFY_CLIENT_ID=your_spotify_client_id
    SPOTIFY_CLIENT_SECRET=your_spotify_client_secret
    SPOTIFY_REFRESH_TOKEN=your_refresh_token
    
  3. Create a folder structure:

    • `src/app/actions.ts` for server actions
    • `src/app/page.tsx` for the main page
    • `src/app/components/Spotify.tsx` for the client component

Creating a Spotify App

Before you can fetch currently playing data, you need to create a Spotify Developer App:

  1. Visit Spotify Developer Dashboard.
  2. Click Create an App and give it a name like Next.js Spotify and add a Redirect URI — for local development, you can use `http://127.0.0.1:3000/api/callback`.
  3. Save your changes.
  4. In the app settings, find your Client ID and Client Secret. You will need these to authenticate your requests.
  5. Save the `Client ID` and `Client Secret` somewhere safe.

Getting a Refresh Token

To access the Spotify API without asking the user to log in each time, we need a Refresh Token.

Authorize your app by visiting this URL in your browser (replace placeholders):

https://accounts.spotify.com/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=http://127.0.0.1:3000/api/callback&scope=user-read-currently-playing

Spotify will redirect to your callback URL with a `code` parameter. Copy the `code` from the URL.

Now, exchange it for tokens using curl:

sh
curl -X POST https://accounts.spotify.com/api/token \
 -H 'Content-Type: application/x-www-form-urlencoded' \
 -d 'grant_type=authorization_code&code=YOUR_CODE&redirect_uri=http://127.0.0.1:3000/api/callback&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET'

The response will contain:

  • `access_token` — short-lived, 1 hour
  • `refresh_token` — long-lived, can get new access tokens anytime
json
{
  "access_token": "BQCMCEb_a-p3gv0xBW2g5ktDv8RfmN1tqjWr5d0p7sznH-IC37yl07dO-H3Kg6j9s7ZHCF0Aja_G8nA9IGiqtfOnzvCKlD2ShZ_a_h6_Ph47OZzHScZe4jqDHQiolyDVZH0nK9qucdui1LBRJhaWD4Q8Tkt7HAs7EVUTv5bBX1KxcX6x5DgtsgCM4ZkbXpb5Kik6C4Ix6BK1byzpZb7ybwH3eAE",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "AQC2quhv2u8QDHeocg_f2jPlTMWlQRHVXuSbJx5GHba9ehKcbdSv8_liggExi9BLmYNlAgvFyxDdQ1Ltcwt_FESrfE2dtMGSynSVT8PgXQiSx02YiVGgEGGX0wE0T508ZCQ",
  "scope": "user-read-currently-playing"
}

Store `refresh_token` securely in your `.env.local`.

Setting Environment Variables

In `.env.local`, add:

SPOTIFY_CLIENT_ID=your_spotify_client_id
SPOTIFY_CLIENT_SECRET=your_spotify_client_secret
SPOTIFY_REFRESH_TOKEN=your_refresh_token

Creating Server Actions

Your `actions.ts` already has MDX-related functions.

Let's extend it with Spotify helpers:

ts
"use server";

const SPOTIFY_CLIENT_ID = process.env.SPOTIFY_CLIENT_ID!;
const SPOTIFY_CLIENT_SECRET = process.env.SPOTIFY_CLIENT_SECRET!;
const SPOTIFY_REFRESH_TOKEN = process.env.SPOTIFY_REFRESH_TOKEN!;

const TOKEN_ENDPOINT = "https://accounts.spotify.com/api/token";
const NOW_PLAYING_ENDPOINT =
  "https://api.spotify.com/v1/me/player/currently-playing";

async function getAccessToken(): Promise<string> {
  const basic = Buffer.from(
    `${SPOTIFY_CLIENT_ID}:${SPOTIFY_CLIENT_SECRET}`
  ).toString("base64");

  const res = await fetch(TOKEN_ENDPOINT, {
    method: "POST",
    headers: {
      Authorization: `Basic ${basic}`,
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: new URLSearchParams({
      grant_type: "refresh_token",
      refresh_token: SPOTIFY_REFRESH_TOKEN,
    }),
  });

  const data = await res.json();
  if (!res.ok) throw new Error("Failed to get Spotify token");

  return data.access_token;
}

export async function getNowPlaying() {
  const accessToken = await getAccessToken();

  const res = await fetch(NOW_PLAYING_ENDPOINT, {
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  });

  if (res.status === 204 || res.status > 400) {
    return { isPlaying: false };
  }

  const song = await res.json();
  return {
    isPlaying: song.is_playing,
    title: song.item?.name,
    artist: song.item?.artists.map((a: { name: string }) => a.name).join(", "),
    album: song.item?.album.name,
    songUrl: song.item?.external_urls.spotify,
  };
}

Creating a Client Component

Server Actions can be imported into a client component for live updates.

Here's a `Spotify.tsx` component:

tsx
"use client";

import { useEffect, useState } from "react";
import { getNowPlaying } from "@/app/actions";

export function Spotify() {
  const [track, setTrack] = useState<{
    isPlaying: boolean;
    title?: string;
    artist?: string;
    album?: string;
    songUrl?: string;
  } | null>(null);

  useEffect(() => {
    const fetchTrack = async () => {
      const data = await getNowPlaying();
      setTrack(data);
    };

    fetchTrack();
    const interval = setInterval(fetchTrack, 3000); // Poll every 3s
    return () => clearInterval(interval);
  }, []);

  if (!track) {
    return (
      <div className="flex flex-col items-center justify-center">
        <p className="text-sm tracking-normal font-semibold animate-bounce text-zinc-800 dark:text-zinc-200">
          Loading
        </p>
      </div>
    );
  }

  if (!track.isPlaying) {
    return (
      <div className="flex flex-col items-center justify-center">
        <p className="text-sm tracking-normal font-semibold text-zinc-800 dark:text-zinc-200">
          Spotify is Not Playing
        </p>
      </div>
    );
  }

  return (
    <div className="flex flex-col items-center justify-center">
      <a
        href={track.songUrl}
        target="_blank"
        rel="noopener noreferrer"
        className="text-sm tracking-normal font-semibold text-zinc-800 dark:text-zinc-200"
      >
        {track.title} by {track.artist}
      </a>
    </div>
  );
}

It will be shown in your `page.tsx`:

tsx
import { Spotify } from "@/components/Spotify";

export default function Page() {
  return (
    <div className="flex flex-col items-center justify-center min-h-screen font-mono">
      <Spotify />
    </div>
  );
}

Running Your App

Run your Next.js app:

sh
npm run dev

Open your browser at `http://localhost:3000` to see currently playing track. It will show the preview of the currently playing track from your Spotify account.

Deployment Notes

When deploying (e.g., to Vercel):

  • Ensure `.env.local` values are set in the project settings.
  • Server Actions keep your Spotify credentials safe on the server.
  • The client only receives safe, minimal track data.

Conclusion

With this setup, your Next.js app can securely fetch and display real-time Spotify Currently Playing data using Server Actions in `actions.ts`.

This approach keeps secrets safe, updates the UI every 3 seconds, and is easily extendable to other Spotify endpoints like `recently-played` or `top-tracks`.

You can check the full code on GitHub.

How to Use Spotify API with Next.js Server Actions