BLOG

MUSIC

6 MIN

Your music page is playing someone else's bandwidth

The music page on this site does not host a single audio file. Every track is a link to someone else's server, and the page works because those servers behave. That is a comfortable position to design from and a fragile one to reason about, so I went and measured what the page actually does instead of assuming.

What is on the page right now

Thirteen published tracks, 1 hour and 42 minutes of audio, made between 25 May and 1 November 2026. Four are instrumentals, nine have vocals. The genres are uneven because they are whatever I happened to make: six glitch-hop, four lo-fi, two drum and bass, and one that the sync labelled Warm because it could not find a better word in its tag list. Track lengths run from 2:45 to 4:07, which is exactly what you get from a model that was trained on songs and returns songs.

None of that is fetched from a database. The page reads a generated file, and that file is rebuilt by one script that opens the public Suno profile and reads the feed the page itself calls.

Two URLs per song, and only one of them plays

The feed gives every song a media_url and a video_url. The first one looks like the obvious choice. It is encrypted m4a-opus, and a browser audio element will not decode it. The second is a plain MP4 with an AAC audio track and no video, and it plays anywhere. So the catalogue stores the MP4 and uses it as the src of a plain <audio> element.

The file is 6,215,761 bytes for a 2:58 track. That is a fine trade when it loads instantly and a bad one if it does not, because there is no second source to fall back to.

MEASURED ON A COLD LOAD Clicking play put first sound out in 458 milliseconds. Seeking to the ninety second mark landed in 12 milliseconds, because the browser had already buffered ahead. For a 6 MB file that is better than most podcast players.

The artwork is the real weight

This is the part I did not expect. Each row on the page shows the cover art at 52 by 52 CSS pixels. The files behind them are 1024 by 1024 JPEGs. Across the thirteen tracks that is 2.4 megabytes of images served to render a list smaller than a postage stamp, and five of those files are over 200 KB on their own.

Before measuring I assumed this was fine, because the images load in parallel and the page is interactive long before all thirteen arrive. That assumption survives a fast connection and a warm cache and quietly fails on a phone on mobile data, where somebody paying for the page is downloading three full-resolution album covers before hearing a note. Every one of those images is being downscaled by a factor of roughly twenty by the browser, after the network has already paid for it.

The street-level version of this lesson is boring and correct: if you show an image at 52 pixels, serve an image at 52 pixels. Suno's CDN does publish smaller derivatives on some of its paths, but not on a predictable one for every track, and I have not yet added the step that checks for them and rewrites the catalogue. Until then it is a known cost, written down rather than forgotten.

Why the page depends on a scrape

The catalogue is not maintained by hand. A sync script loads the profile page in a headless browser, watches the responses, and grabs the JSON from the unified feed endpoint. That JSON becomes js/tracks.js, a generated file with a comment at the top saying not to edit it.

Doing it this way is the only thing that makes publishing a song a one step operation: publish on Suno, run the sync, deploy. Unpublishing works the same way, because the feed only lists what is currently public, so removing a song from the profile removes it from the page on the next run.

The cost is that the whole feature is one endpoint deep. If that endpoint changes its shape, every track disappears at once. The sync script refuses to write an empty catalogue for exactly this reason. A genuinely empty profile and a broken scrape look identical from its side, and it is much better to fail loudly with the old file intact than to helpfully delete the music page.

What the genre labels really are

Those six glitch-hop and four lo-fi tags are not metadata from Suno. They are guessed. Suno's style field is prose, not tags, so the sync runs a short list of recognised words over it and takes the first one that matches. If nothing matches, it takes whatever comes before the first comma in the style string and truncates it. The Warm label on Warm Marimba Groove is that fallback doing what it was told.

I left it that way on purpose. A hand-curated genre for thirteen tracks would be more accurate today and drift out of date the moment I publish something new, and a catalogue that updates itself is worth more than one that is perfectly labelled.

The rules that fell out of this

  • Serve audio from a player the browser trusts. The format that looks right in an API response is not always the format that decodes.
  • Measure image weight at the size it is displayed. A 52 pixel square does not need a megapixel source, whatever the CDN hands you by default.
  • Never let a sync write an empty list. Failure and emptiness must look different, or the first bad deploy takes the page with it.
  • Write down the compromises. The oversized artwork is a real cost that I have decided to carry for now, and the only reason I can say that honestly is that the number is written down.

The whole catalogue is on the music page. It is generated from the published feed, so it is always whatever is live on Suno rather than a list somebody has to remember to update.