Skip to content

Tech stack

Built for performance & creativity

A modern full stack Next.js portfolio with OAuth 2.0, AWS cloud integration, and custom audio playback architecture.

The stack

What it runs on

Languages & framework

  • Next.js 16React framework + API routes
  • React 19Client UI + external stores
  • TypeScriptStrict mode, app to API
  • Tailwind CSSDesign tokens + utilities
  • Motion (Framer)Queue drags, sheets and panels

Cloud & backend

  • AWS S3Cloud storage for audio files
  • Spotify API + OAuth 2.0Web Playback SDK + token refresh
  • VercelServerless deployment & hosting

State & data

  • Player storeOne player for the visit, read with useSyncExternalStore
  • Link cachingSigned S3 links reused for 40 min, refreshed on failure
  • Cookie-based sessionsSpotify OAuth token storage

Key features

What makes it tick

  1. 01

    Spotify OAuth 2.0 integration

    Full OAuth flow with cookie-based session management, automatic token refresh, and server-side token caching. Supports both client credentials and user authorization flows.

  2. 02

    Signed audio that heals itself

    Beats stream from a private S3 bucket through links signed per request. Links are reused for 40 minutes, requests give up after 10 seconds, and if a link lapses mid-track the player fetches a fresh one and carries on from the same second.

  3. 03

    Dual audio sources

    One player drives both HTML5 audio for beats and the Spotify Web Playback SDK for streaming, so the queue, controls, lock screen and media keys work the same whichever is playing.

  4. 04

    A real queue

    Play next, add to queue, drag to reorder, shuffle and repeat, kept in a pure reducer with its own tests. Every item has its own id, so the same beat can be queued twice and moves animate cleanly.

  5. 05

    Persistent playback

    The player lives outside React and is mounted once from the root layout, so music carries on across pages. Components subscribe to just the slices they need, so the progress bar ticking doesn’t re-render the page.

  6. 06

    Beat-synced visuals

    Every beat ships with its BPM, so the playhead gives the beat position directly. The LED wall pumps with the kick, the logo flips on the downbeat, and covers pulse in time, without a Web Audio graph.

Backend

API routes

Audio delivery

  • GET/api/beatsReturns all beats with metadata
  • GET/api/beats/playlistsReturns the beat packs
  • POST/api/beats/signedUrlGenerates a signed URL for a specific beat

Spotify OAuth

  • GET/api/spotify/loginInitiates Spotify OAuth 2.0 authorization flow
  • GET/api/spotify/callbackHandles OAuth callback and exchanges code for tokens
  • GET/api/spotify/tokenRetrieves or refreshes Spotify access token
  • POST/api/spotify/logoutClears the Spotify session cookies

Spotify data

  • GET/api/spotify/playlistsFetches user playlists from Spotify API
  • GET/api/spotify/playlist/[id]Fetches one playlist with its tracks
  • GET/api/spotify/stats/topTracksFetches user’s most played tracks
  • GET/api/spotify/stats/topArtistsFetches user’s top artists

Under the hood

Signed audio, cached

providers/beatEngine.ts
// A signed S3 link: reused for 40 minutes, shared while it's being
// fetched, and never allowed to hang the player
private linkFor(track: BeatTrack, fresh = false): Promise<string> {
  const hit = this.links.get(track.id)
  if (!fresh && hit && Date.now() - hit.at < LINK_TTL) return Promise.resolve(hit.url)
  const inFlight = this.pending.get(track.id)
  if (!fresh && inFlight) return inFlight
  const abort = new AbortController()
  const timer = window.setTimeout(() => abort.abort(), LINK_TIMEOUT)
  const request = getBeatSignedUrl(track.id, abort.signal)
    .then(url => {
      if (!url) throw new Error('No audio link')
      this.links.set(track.id, { url, at: Date.now() })
      return url
    })
    .finally(() => {
      window.clearTimeout(timer)
      if (this.pending.get(track.id) === request) this.pending.delete(track.id)
    })
  this.pending.set(track.id, request)
  return request
}

Performance

Fast by default

First load
<2s
Bundle size
~220KB
Lighthouse
95+

Security & best practices

Locked down

Token & session security

  • Spotify tokens stored in HttpOnly, Secure cookies and cleared on logout
  • Refresh flow runs server-side only; client never sees secrets
  • Short-lived access tokens; refresh token retained for 30 days

Signed URL access control

  • S3 pre-signed URLs generated per request, valid for one hour
  • Bucket kept private; audio is never publicly exposed
  • Links reused for 40 minutes at most, then signed again

API design & validation

  • Beat/playlist IDs and required fields are validated before calling Spotify/S3
  • All secrets stay server-side; Spotify client secret never ships to the client
  • API routes handle OAuth exchanges and signing; UI consumes safe endpoints

Resilience & error handling

  • Stalls, expired links and dropped connections retried from the same second
  • Ad-block detection with user-facing guidance instead of silent failure
  • A lost connection waits it out instead of skipping through the queue

Check out the code

View the full source code, architecture decisions, and implementation details. Clean codebase, full TypeScript, production-ready.

View on GitHub