Music showcase for Bohns — albums and tracks streamed from Supabase, rebuilt on Next.js (App Router) with a full redesign of the Home and Album pages.
- Next.js 16 (App Router, TypeScript, CSS Modules)
- Supabase — public content (
albums,track_overviewview,trackslyrics) plus Google sign-in and per-user likes (@supabase/ssr) - lucide-react icons
- Fonts: Gloock (display) + Space Grotesk via
next/font
- The site header renders with
mix-blend-mode: color-dodgeover the dark mauve background (#11090C) — its soft-grey content composites to pink. - Each album has a color palette theme (
light/accent/deep) applied through CSS custom properties. Palettes are hardcoded insrc/lib/palettes.tsfor now (no Supabase model change, step 1). - Track durations are not stored in the database; they resolve client-side
from audio metadata (
preload="metadata").
/— artist header (brand, bio, social links) + every album as a card with cover, meta tiles, description and playlist/albums/[id]— compact header, album switcher (rail on desktop, horizontal strip on mobile) and album detail with track descriptions and a lyrics sheet
Playing a track queues its whole album into a global player bar: waveform
timeline (wavesurfer.js) recolored per album palette, play/pause,
next/previous, repeat (all by default), scrubbing, and MediaSession
metadata + handlers for lock-screen controls on iOS/Android. Playback
survives page navigation (the player lives above the router).
- Sign in with Google from the account button (top-right on every page).
OAuth runs through Supabase Auth: the browser client starts the flow, Google
redirects back to
/auth/callback, and the route handler exchanges the code for a cookie session.middleware.tskeeps the session fresh. - Likes are per-user and persisted in the
track_likestable. The heart on each track toggles a row via the browser client (writes are guarded by row-level security), with an optimistic count and rollback on failure. The public catalog stays statically cached (ISR) — only the like state is resolved client-side, so pages don't become per-user dynamic. /profileshows the signed-in user and a sign-out control;/likeslists the tracks the user has liked, most recent first.
Supabase clients live in src/lib/supabase/: anon.ts (public read-only
content), client.ts (browser, auth + likes) and server.ts (cookie-bound, for
the callback route and the protected pages). The track_likes schema + RLS are
in supabase/migrations/20260710_track_likes.sql.
The hosted project must have Google enabled as an auth provider and
<site>/auth/callbackin its redirect allow-list. No extra environment variables are needed beyond the two below.
cp .env.example .env.local # fill in the Supabase URL + anon key
npm install
npm run devRequired environment variables (also needed on Vercel):
| Variable | Description |
|---|---|
NEXT_PUBLIC_SUPABASE_URL |
Supabase project URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Supabase anonymous key |
This project is licensed under the Creative Commons Attribution-NonCommercial 4.0 International License.
You are free to share, adapt, and build upon this work for non-commercial purposes only, as long as you provide appropriate credit.