Por Que Mostrar o Que Estou Ouvindo?
Eu queria que meu portfólio tivesse vida — não apenas uma lista estática de projetos. Mostrar o que estou ouvindo no Spotify adiciona um toque pessoal e dá aos visitantes um vislumbre de quem eu sou além do código.
O problema? A API do Spotify não é exatamente plug-and-play. Tem uma dança de OAuth, refresh tokens e edge cases pra lidar quando nada está tocando.
O Fluxo OAuth (A Parte Dolorosa)
O Spotify usa OAuth 2.0 com Authorization Code Flow. O resumo:
- Você autoriza o app no Spotify Developer Dashboard
- Recebe um authorization code (uso único)
- Troca por um access token + refresh token
- O access token expira a cada hora — o refresh token é sua salvação
A parte chata é conseguir o refresh token inicial. Você precisa acessar manualmente a URL de autorização no navegador, pegar o code do redirect e trocar via cURL:
curl -X POST https://accounts.spotify.com/api/token \
-H "Authorization: Basic <base64(client_id:client_secret)>" \
-d grant_type=authorization_code \
-d code=<seu_auth_code> \
-d redirect_uri=http://localhost:3000/callbackUma vez que você tem o refresh token, salva como variável de ambiente. Nunca mais vai precisar fazer isso (a menos que revogue o acesso).
O Route Handler
Criei uma rota API com Edge Runtime que lida com o fluxo completo a cada request:
// 1. Troca o refresh token por um access token novo
const getAccessToken = async () => {
const body = new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refresh_token
})
const response = await fetch(TOKEN_ENDPOINT, {
method: 'POST',
headers: {
Authorization: `Basic ${btoa(`${client_id}:${client_secret}`)}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: body.toString(),
cache: 'no-store',
})
return response.json()
}O btoa() codifica as credenciais como Base64 — o Spotify exige isso no endpoint de token.
Now Playing vs. Last Played
A API tem dois endpoints relevantes:
/v1/me/player/currently-playing— retorna a música tocando agora (ou204 No Contentse nada estiver tocando)/v1/me/player/recently-played— retorna as últimas 50 músicas tocadas
Minha lógica é simples: tenta "tocando agora" primeiro, faz fallback pra "tocou recentemente":
const nowPlaying = await getNowPlaying(access_token)
if (nowPlaying?.is_playing && nowPlaying.currently_playing_type === 'track') {
return { isPlaying: true, title: nowPlaying.item.name, ... }
}
// Nada tocando — pega a música mais recente
const lastPlayed = await getLastPlayed(access_token)
const mostRecent = lastPlayed.items.sort(
(a, b) => new Date(b.played_at).getTime() - new Date(a.played_at).getTime()
)[0]
return { isPlaying: false, title: mostRecent.track.name, ... }O Client Side: SWR pra Updates em Tempo Real
No frontend, uso SWR pra fazer polling na API a cada 10 segundos:
const { data, isValidating, mutate } = useSWR<SpotifyData>(
'/api/spotify',
fetcher,
{
refreshInterval: 10000,
revalidateOnFocus: false,
dedupingInterval: 5000,
}
)Isso me dá:
- Auto-refresh a cada 10 segundos
- Deduplicação pra não sobrecarregar a API
- Uma função
mutate()pro botão de refresh manual - A capa do álbum gira com uma animação do Framer Motion quando uma música está tocando
Caching: Não Cachear Isso
Como o objetivo é dados em tempo real, cache é o inimigo:
export const runtime = 'edge'
export const dynamic = 'force-dynamic'
// Mais headers agressivos de no-cache em cada response
const headers = {
'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0',
'Pragma': 'no-cache',
'Expires': '0',
}Edge Runtime mantém o cold start rápido, e force-dynamic garante que o Next.js não tente otimizar a rota estaticamente.
Problemas Que Encontrei Pelo Caminho
- O refresh token pode expirar se você não usar por 6 meses ou se revogar o acesso. Guarde-o em um lugar seguro.
- Podcasts quebram tudo — o endpoint
currently-playingretorna podcasts também. Sempre chequecurrently_playing_type === 'track'. - Rate limits — O Spotify permite ~180 requests por minuto. Com polling de 10s e poucos visitantes, tá de boa. Mas não baixe mais que isso.
- Variáveis de ambiente — Uso tanto
SPOTIFY_*(servidor) quantoNEXT_PUBLIC_SPOTIFY_*(legado) com um padrão de fallback.
O Resultado Final
Um card pequeno que mostra o que estou ouvindo em tempo real, com a capa do álbum girando e um timestamp relativo pra última música tocada:

E aqui a resposta crua da API que alimenta tudo — batendo em /api/spotify retorna tudo que o frontend precisa:

É uma feature pequena, mas faz o site parecer mais que um currículo.
