
Displaying Your Spotify Recent Tracks with Nuxt 3
How to implement a Spotify Recent Tracks component in your Nuxt 3 application to showcase your music listening history.

Pheak Minute
Showcasing your recently played Spotify tracks on your personal website adds a personal touch that can engage visitors and reflect your music taste. This tutorial will guide you through creating a "Recent Tracks" component for your Nuxt 3 application.
Prerequisites
Before starting, you'll need:
- A Spotify Developer account with a registered application
- Your Spotify Client ID and Client Secret
- A refresh token for persistent authentication
- A Nuxt 3 project
If you haven't set up Spotify API access yet, check my previous post on Building a Spotify Now Playing Widget with Nuxt 3 for detailed instructions.
Project Structure
Our implementation will consist of three main components:
- Spotify utility functions for authentication and data fetching
- A server API endpoint to securely fetch recently played tracks
- A Vue component to display the tracks on the frontend
Step 1: Setting Up Spotify Utilities
First, we need to create utility functions for Spotify API authentication and fetching recently played tracks. Create or update your utils/spotify.ts
file:
const config = useRuntimeConfig();
const client_id = config.public.clientId;
const client_secret = config.public.clientSecret;
const refresh_token = config.public.refreshToken;
const basic = Buffer.from(`${client_id}:${client_secret}`).toString("base64");
const TOKEN_ENDPOINT = "https://accounts.spotify.com/api/token";
const RECENT_TRACKS_ENDPOINT =
"https://api.spotify.com/v1/me/player/recently-played?limit=5";
const NOW_PLAYING_ENDPOINT =
"https://api.spotify.com/v1/me/player/currently-playing";
export const getAccessToken = async () => {
const response = 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: refresh_token as string,
}),
});
return response.json();
};
export const getRecentTracks = async () => {
const { access_token } = await getAccessToken();
return fetch(RECENT_TRACKS_ENDPOINT, {
headers: {
Authorization: `Bearer ${access_token}`,
},
});
};
export const getNowPlaying = async () => {
const { access_token } = await getAccessToken();
return fetch(NOW_PLAYING_ENDPOINT, {
headers: {
Authorization: `Bearer ${access_token}`,
},
});
};
This utility file handles:
- Authentication using your Spotify credentials and refresh token
- Fetching your recently played tracks (limited to 5 for cleaner display)
- Optionally fetching your currently playing track
Step 2: Creating a Server API Endpoint
Next, create a server API endpoint for fetching recently played tracks at server/api/spotify/recent-tracks.ts
:
import { getRecentTracks } from '../../../utils/spotify';
export default defineEventHandler(async (event) => {
try {
const response = await getRecentTracks();
if (response.status === 204 || response.status > 400) {
return { tracks: [] };
}
const data = await response.json();
const tracks = data.items.map((item: any) => {
const track = item.track;
return {
artist: track.artists.map((_artist: any) => _artist.name).join(", "),
songUrl: track.external_urls.spotify,
title: track.name,
albumImageUrl: track.album.images[0]?.url,
playedAt: item.played_at
};
});
// Set appropriate cache headers
setResponseHeaders(event, {
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=30'
});
return { tracks };
} catch (error) {
console.error("Error fetching recent tracks:", error);
throw createError({
statusCode: 500,
statusMessage: 'Failed to fetch Spotify recent tracks'
});
}
});
This endpoint:
- Calls our
getRecentTracks
utility function - Processes the response to extract relevant track information
- Formats the data for our frontend component
- Implements caching to optimize performance
Step 3: Creating the Recent Tracks Component
Finally, let's create a Vue component to display the recently played tracks. Create a file at components/RecentTracks.vue
or app/components/RecentTracks.vue
depending on your project structure:
<script setup lang="ts">
interface Track {
artist: string;
songUrl: string;
title: string;
albumImageUrl: string;
playedAt: string;
}
interface RecentTracksResponse {
tracks: Track[];
}
// Use useFetch with auto-refresh
const { data, error, pending, refresh } = await useFetch<RecentTracksResponse>(
"/api/spotify/recent-tracks",
{
default: () => ({ tracks: [] }),
}
);
// Manual refresh every 60 seconds
useIntervalFn(() => {
refresh();
}, 60000);
const tracks = computed(() => data.value?.tracks || []);
const hasTracks = computed(() => tracks.value.length > 0);
</script>
<template>
<div class="space-y-4">
<div class="w-full flex flex-row items-center gap-2 mb-6">
<UIcon
name="i-heroicons-arrow-top-right-on-square"
class="h-4 w-4 text-muted-foreground opacity-50 group-hover:opacity-100 transition-opacity"
/>
<h2 class="text-xl font-semibold">Recently Played</h2>
</div>
<UAlert
v-if="error"
color="error"
variant="subtle"
icon="i-heroicons-exclamation-triangle"
title="Failed to load recent tracks"
>
</UAlert>
<div v-else-if="pending" class="flex justify-center py-2">
<UIcon name="i-heroicons-arrow-path" class="animate-spin h-5 w-5" />
</div>
<div v-else-if="!hasTracks" class="text-sm text-muted">
No recently played tracks
</div>
<ul v-else class="space-y-3">
<li v-for="(track, index) in tracks" :key="track.songUrl">
<Motion
:initial="{ opacity: 0, y: 10 }"
:animate="{
opacity: 1,
y: 0,
transition: {
delay: index * 0.05,
duration: 0.3,
},
}"
>
<ULink
:to="track.songUrl"
target="_blank"
rel="noopener noreferrer"
class="flex items-center gap-3 p-2 rounded-lg hover:bg-elevated transition-colors duration-200"
>
<div
class="relative h-10 w-10 rounded-md overflow-hidden flex-shrink-0"
>
<img
v-if="track.albumImageUrl"
:src="track.albumImageUrl"
:alt="`${track.title} album cover`"
class="h-full w-full object-cover"
/>
<div
v-else
class="h-full w-full bg-muted flex items-center justify-center"
>
<UIcon
name="i-heroicons-musical-note"
class="h-5 w-5 text-muted-foreground"
/>
</div>
</div>
<div class="flex flex-col min-w-0">
<div class="text-sm font-medium truncate text-highlighted">
{{ track.title }}
</div>
<div class="text-xs text-muted truncate">
{{ track.artist }}
</div>
<div class="text-xs text-muted truncate">
{{
new Date(track.playedAt).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})
}}
</div>
</div>
<UIcon
name="i-heroicons-arrow-top-right-on-square"
class="h-4 w-4 text-muted-foreground ml-auto opacity-50 group-hover:opacity-100 transition-opacity"
/>
</ULink>
</Motion>
</li>
</ul>
</div>
</template>
This component:
- Fetches data from our server API endpoint
- Refreshes every 60 seconds to keep the data current
- Displays a loading state while fetching
- Handles error states gracefully
- Renders a beautiful list of recently played tracks with album artwork and artist information
- Adds subtle animations for a polished look
Step 4: Environment Configuration
Don't forget to update your Nuxt configuration to include your Spotify credentials. Add these to your .env
file:
NUXT_PUBLIC_CLIENT_ID=your_spotify_client_id
NUXT_PUBLIC_CLIENT_SECRET=your_spotify_client_secret
NUXT_PUBLIC_REFRESH_TOKEN=your_spotify_refresh_token
And update your nuxt.config.ts
to include these in your runtime config:
export default defineNuxtConfig({
// ... other config
runtimeConfig: {
// ... other config
public: {
// ... other public config
clientId: process.env.NUXT_PUBLIC_CLIENT_ID,
clientSecret: process.env.NUXT_PUBLIC_CLIENT_SECRET,
refreshToken: process.env.NUXT_PUBLIC_REFRESH_TOKEN,
}
}
})
Step 5: Using the Component
Finally, you can use the RecentTracks component in any of your pages:
<template>
<div>
<h1>My Music Profile</h1>
<RecentTracks />
</div>
</template>
Conclusion
With this implementation, you now have a fully functional Recent Tracks component that automatically fetches and displays your listening history from Spotify. The component refreshes periodically to keep your data current and provides a visually appealing way to share your music taste with your website visitors.
This feature adds a personal touch to your site while showcasing modern web development techniques like API integration, server endpoints, and reactive UI components with Nuxt 3.
Feel free to customize the styling to match your site's design system - the component uses UI components from Nuxt UI, but you can adapt it to use any UI library or custom styling.
Beyond Frameworks: Becoming a True Software Engineer
Why focusing too much on frameworks limits your growth, and how expanding your technical horizons makes you a better engineer.
Building a Spotify Now Playing Widget with Nuxt 3
How to integrate the Spotify API with Nuxt 3 to display your currently playing track in real-time on your personal website or portfolio.