This repository is the Web Worker podcast website. It is an Astro SSR app that renders podcast pages from local Markdown files and provides a small admin workflow for syncing the upstream podcast RSS feed into those Markdown files.
- Framework: Astro with
@astrojs/nodein standalone SSR mode. - UI: Vue 3 components hydrated where interaction is needed.
- Styling: Tailwind CSS 4 via Vite plugin.
- Data source: Markdown files in
content/episodes. - Admin:
/adminlogs in with environment-configured credentials and can refresh RSS, compare by title, and import new episodes. - Deployment target: Docker container, with
content/episodesmounted as a persistent volume.
pnpm install
pnpm run dev
pnpm run build
pnpm run import:episodes
pnpm run fetch-rss -- --preview
pnpm run fetch-rss
docker compose config
docker compose up --buildpnpm run import:episodes converts the legacy
src/data/podcast-data.json file into Markdown episode files.
pnpm run fetch-rss imports missing remote RSS items into Markdown files using
the same title-based diff rule as the admin UI.
Use .env.example as the starting point.
RSS_URL: upstream podcast RSS feed.EPISODES_DIR: runtime Markdown directory. Defaults tocontent/episodeslocally and/app/content/episodesin Docker.REDIS_URL: optional Redis URL for rate limiting and short JSON caches. When unset, the app falls back to process-local memory.PUBLIC_SITE_URL: canonical site URL for the current deployment.PUBLIC_ALLOW_INDEXING: keepfalseon staging. This makesrobots.txtdisallow all crawling and addsnoindex,nofollow,noarchive.JWT_SECRET,ADMIN_USERNAME,ADMIN_PASSWORDorADMIN_PASSWORD_SHA256: admin credentials.RSS_AUTO_SYNC: whentrue, the Node process periodically imports new RSS items.
Each episode is a Markdown file with frontmatter:
---
title: No.96 Example
pubDate: '2026-05-29T00:00:00.000Z'
link: 'https://www.xiaoyuzhoufm.com/episode/...'
guid: episode-guid
audioUrl: 'https://...m4a'
duration: '01:05:33'
imageUrl: 'https://...png'
author: Web Worker
categories: []
description: Short summary
---The Markdown body is rendered on episode detail pages.
- Preserve the existing cream/mint visual system and Chinese copy style.
- Do not reintroduce a database for podcast content unless the requirement changes. Markdown files are the source of truth.
- Keep admin APIs under
/api/admin/*protected bygetAdminFromContext. - Keep Redis as a defensive/cache layer only. Markdown files remain the content source of truth.
- Keep production Redis external. Local Compose may start Redis for development,
but production Compose must consume
REDIS_URLfrom the private server environment instead of creating a Redis service. - Public GET/HEAD pages should keep short cache headers and query stripping so random query parameters do not bypass caches.
- Keep staging safe for search engines:
PUBLIC_ALLOW_INDEXING=falseuntil the primary domain is intentionally switched. - When changing routing or data loading, verify SSR build and a running Node server, not just static output.
docker-compose.prod.yml expects an externally managed .env.production on the
server and mounts the Markdown directory:
EPISODES_HOST_DIR=/srv/webworker-tech/content/episodes
HOST_PORT=4322scripts/deploy-new-webworker.sh builds a linux/amd64 image tarball, copies it
to REMOTE_HOST, syncs current Markdown files, starts Compose, and verifies the
remote health endpoint. Keep real SSH aliases, server IPs, hosting-panel URLs,
API keys, and production environment files outside this public repository.
Do not switch the main domain without explicit approval. Use private infrastructure tooling for website, reverse proxy, DNS verification, and HTTPS operations.