The Spotify Card component is a premium, interactive interface that fetches live metadata from Spotify. When clicked, the album cover smoothly morphs into a rotating vinyl disc and plays an audio preview.
Preview
Usage
Installation
API Setup
The Spotify Card requires a server-side route to fetch metadata. The registry should automatically create this at app/api/spotify/metadata/route.ts.
The route accepts a validated Spotify track ID (?trackId=...), never a raw URL, so the server only ever requests https://open.spotify.com/track/<id> — this prevents server-side request forgery (SSRF). The card extracts the track ID from your trackUrl prop automatically.
Note: If the registry fails to install the API route automatically, please manually create the file at
app/api/spotify/metadata/route.tsand paste the code below.
import * as cheerio from "cheerio";
import got from "got";
import { NextRequest, NextResponse } from "next/server";
// SSRF guard: the outbound request destination is always built server-side
// from a validated Spotify track ID — never from a caller-supplied URL.
const TRACK_ID_RE = /^[A-Za-z0-9]{1,64}$/;
const MAX_RESPONSE_BYTES = 1_000_000;
export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url);
const trackId = searchParams.get("trackId") ?? "";
if (!TRACK_ID_RE.test(trackId)) {
return NextResponse.json({ error: "Invalid track ID" }, { status: 400 });
}
try {
const request = got(`https://open.spotify.com/track/${trackId}`, {
headers: {
"user-agent": "Mozilla/5.0 (compatible; SparkUI-SpotifyCard/1.0)",
accept: "text/html",
},
followRedirect: false,
retry: { limit: 0 },
timeout: { request: 5000 },
});
request.on("downloadProgress", ({ transferred }) => {
if (transferred > MAX_RESPONSE_BYTES) request.cancel();
});
const { headers, body: html } = await request;
if (!headers["content-type"]?.includes("text/html")) {
return NextResponse.json(
{ error: "Failed to fetch metadata" },
{ status: 502 },
);
}
const $ = cheerio.load(html);
const title =
$('meta[property="og:title"]').attr("content") || "Unknown Track";
const image = $('meta[property="og:image"]').attr("content") || "";
const previewUrl =
$('meta[property="og:audio"]').attr("content") ||
$('meta[name="twitter:audio:src"]').attr("content") ||
"";
// Spotify's og:description is often: "Artist · Album · Song · Year"
const description =
$('meta[property="og:description"]').attr("content") || "";
// Usually the first part is the Artist
const artist = description.split("·")[0]?.trim() || "Unknown Artist";
return NextResponse.json({
title,
artist,
albumArt: image,
previewUrl,
});
} catch {
// Non-2xx, redirect attempt, timeout, or oversized response. Keep the
// client response generic — no upstream details.
return NextResponse.json(
{ error: "Failed to fetch metadata" },
{ status: 502 },
);
}
}
Basic Example
import { SpotifyCard } from "@/components/spotify-card";
export default function App() {
return (
<div className="flex items-center justify-center p-8">
<SpotifyCard trackUrl="https://open.spotify.com/track/3sK8wGT43QFpWrvNQsrQya" />
</div>
);
}
Properties
| Prop | Type | Default | Description |
|---|---|---|---|
trackUrl | string | — | The full Spotify track URL (https://open.spotify.com/track/<id>) to fetch metadata from. |
className | string | — | Additional CSS classes for the card container. |