The nonce-based script-src is incompatible with Next's static prerendering. The nonce is minted per request in middleware, but statically generated HTML was produced at build time with no nonce on its inline scripts, so on "/" and the marketing routes every inline hydration script was blocked: the page painted from SSR HTML, React failed to hydrate, and the tree unmounted to a blank screen a moment later. Dynamic routes (/sign-in, /dashboard) render per request, receive the nonce, and worked — which is why this was missed. Browsers that honour a nonce ignore 'unsafe-inline', so the two cannot be combined. Drop the nonce and allow inline scripts; the policy still restricts which origins may serve scripts, and frame-src/connect-src stay strict. Restoring nonces would require every route to render dynamically. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
🎙️ Podcast Distribution AI
An all-in-one AI platform that takes you from a topic idea to a finished, publishable podcast episode in minutes — it writes the script (GPT-4), records realistic multi-voice audio (ElevenLabs), and designs the cover art (DALL·E), then lets you fine-tune, download, and repurpose the result.
Stack
| Concern | Choice |
|---|---|
| Framework | Next.js (App Router) + TypeScript |
| UI | Tailwind CSS + shadcn/ui + Recharts |
| Database | PostgreSQL + Prisma |
| Auth | Better Auth (email/password + Google, admin + organization plugins) |
| Queue | pg-boss (Postgres-backed, no Redis) + a separate worker process |
| Storage | Local disk on a persistent volume (swappable StorageProvider → S3/R2) |
| AI | OpenAI (GPT-4 + DALL·E), ElevenLabs (TTS + dialogue) |
| Billing | Stripe and PayPal → one unified Subscription model |
| Resend | |
| Deploy | Dokploy (Docker Compose + Traefik) — see deploy/README.md |
Architecture
Browser ──► Next.js web (PM2) ──enqueue──► Postgres (app data + pgboss queue)
│ SSE status ▲ claim job (SKIP LOCKED)
▼ │
/api/assets (private mp3) Worker (PM2, ffmpeg)
script → segment → ElevenLabs
→ ffmpeg stitch → DALL·E → save → meter
│
▼
Local disk storage/{mp3,art}
Local development
npm install
cp .env.example .env # fill DATABASE_URL, BETTER_AUTH_SECRET, OPENAI_API_KEY, ELEVENLABS_API_KEY…
npx prisma migrate dev # create tables
npm run db:seed # seed the plan catalog
# Two processes:
npm run dev # web (http://localhost:3000)
npm run worker:dev # generation worker (needs ffmpeg on PATH)
ffmpeg must be installed and on PATH for audio stitching
(brew install ffmpeg / apt install ffmpeg / choco install ffmpeg).
Make yourself an admin after signing up:
npm run make-admin you@email.com # then visit /admin
Key directories
app/(marketing) public landing + pricing
app/(auth) sign-in / sign-up / password reset
app/(app) user dashboard (episodes, wizard, usage, billing, team, api-keys, settings)
app/(admin) admin dashboard (users, subscriptions, AI cost, moderation, health, flags, audit)
app/api auth, webhooks/{stripe,paypal}, episodes/[id]/stream (SSE), assets, v1 (API)
lib/ai provider abstraction + pipeline (generate-episode, segment, stitch, repurpose)
lib/billing plans, stripe, paypal, catalog, unified subscription writer, webhooks
lib/queue pg-boss client + job definitions
lib/storage StorageProvider interface + local-disk impl
worker/ pg-boss consumer (runs the generation pipeline)
How generation works
- The 3-step wizard creates an
Episode (QUEUED)+ speaker config, then enqueues a pg-boss job (afterenforceLimitchecks the plan). - The worker runs the pipeline, updating
Episode.statusat each stage. - The episode page subscribes to
/api/episodes/[id]/stream(SSE) and shows a live stepper, then renders the script editor, audio player, and cover art whenREADY.
Long scripts are chunked to stay under ElevenLabs' ~2k-char dialogue limit, synthesized per segment, then concatenated and loudness-normalized with ffmpeg.
Deployment
Containerized for Dokploy (Docker Compose: web + worker, Traefik for
domain/TLS/SSE, a persistent storage volume). See deploy/README.md
for the full runbook (env, domain, migrations-on-boot, webhooks, backups).