I built a small podcast site for publishing audio episodes whenever I want to record something and put it online without much setup.
The site works from a simple episode data structure. Each episode has an ID, title, description, audio file URL, cover image, and date. That same data is reused to power the site, the embedded player, and the podcast RSS feed.
How it works
flowchart TD
A["Episode metadata in JSON"] --> B["Site reads episode data"]
B --> C["Episode embed page"]
B --> D["Podcast RSS feed"]
C --> E["Browser loads audio file"]
A simplified episode entry looks like this:
{
"id": "1",
"title": "EKKSTACY - i walk this earth all by myself",
"description": "Sample uploaded episode using the local .m4a file from public/uploads.",
"audio_url": "/uploads/EKKSTACY-i-walk-this-earth-all-by-myself.m4a",
"cover_url": "/assets/cover-1.svg",
"date": "2026-04-22"
}
This is useful because the site only needs one source of truth for each episode. The same metadata can be used in multiple places instead of rewriting it by hand.
Episode 1
Here is episode 1 embedded directly in the post:
The iframe points to /embed/1, which means the site loads the player for the episode with ID 1.
Technical details
sequenceDiagram
participant J as Episode JSON
participant S as Site Logic
participant E as Embed Route
participant R as RSS Generator
J->>S: Load episode metadata
S->>E: Find episode by ID
S->>R: Reuse same metadata
E->>Browser: Render audio player
R->>PodcastApps: Publish feed items
A simplified version of the lookup code would look like this:
const episodes = [
{
id: "1",
title: "EKKSTACY - i walk this earth all by myself",
audio_url: "/uploads/EKKSTACY-i-walk-this-earth-all-by-myself.m4a",
cover_url: "/assets/cover-1.svg",
date: "2026-04-22"
}
];
function getEpisodeById(id) {
return episodes.find((episode) => episode.id === id);
}
Then the embed page can use that result to render the player:
const episode = getEpisodeById("1");
const html = `
<article>
<h1>${episode.title}</h1>
<audio controls src="${episode.audio_url}"></audio>
</article>
`;
The RSS feed follows the same pattern. Instead of maintaining a separate feed database, the feed generator reads the same episode metadata and turns it into podcast feed items.
Why this setup is useful
graph LR
A["Single episode object"] --> B["Website page"]
A --> C["Embedded player"]
A --> D["RSS item"]
This setup keeps the site lightweight. Adding a new episode mostly means uploading an audio file and adding one metadata object. After that, the site can render the episode, expose an embed player, and include it in the podcast feed automatically.