Rádio Alvorada TV · Platform 1 of 3 · 2025–2026

radioalvoradatv.com.br

Persistent radio, live schedule, installable PWA — one gospel media home

A production Next.js platform where audiences discover content, see what is on air in São Paulo time, install the PWA, and keep listening while they browse — with a persistent player that survives navigation.

Role

Design Engineer & Full Stack DeveloperUX, frontend, SEO, shipping

Stack

Next.js · React · TypeScriptTailwind · Vercel

Audio

Shoutcast AAC+NinjasCast origin

Deploy

Vercel (monorepo)Standalone app

  • 24/7

    Persistent player

    Follows every route change

  • 30s

    Schedule refresh

    São Paulo timezone engine

  • PWA

    Installable

    Engagement-gated prompt

Production homepage with persistent bottom radio player showing now-playing metadata

The global player survives every route change — metadata and play state stay attached to one HTMLAudioElement.

Captured from radioalvoradatv.com.br. The bar at the bottom is the product surface listeners interact with daily.

Context

The Central Platform for a Three-App Broadcast Monorepo

Rádio Alvorada TV needed a credible public presence: not a landing page, but a daily-use media home for Brazilian gospel audiences. Radio runs continuously; TV and on-demand video are separate deploys in the same monorepo — this case study is the website layer only.

The design problem was cognitive distance: listeners think “the radio is on,” while the system juggles Shoutcast streams, metadata polling, timezone math, YouTube embed rules, and install prompts — all without feeling like a utility app duct-taped to a blog.

My role and constraints

One Persistent Truth Drives the Architecture: The Radio Is Live

I owned product architecture, information architecture, interaction design, frontend implementation, deployment, and the operational surfaces that keep the site trustworthy after launch. This is not a static brand site — it is a broadcast product where homepage, schedule, content library, PWA install prompt, community actions, and persistent radio player must all agree about what the station is doing right now.

The core constraint: the website must feel editorial and calm while behaving like a live media app. A typical approach makes the player dominant and pushes content into secondary pages. I took the opposite route — the site stays readable, but a single global audio state follows the user quietly through every page.

Audience constraint

Visitors arrive for live radio, programs, devotionals, prayer, music, and videos. The design cannot assume a single task; it must help people keep listening while they decide what to do next.

Technical constraint

The audio stream comes from an external Shoutcast provider. The website can control playback, metadata, fallbacks, and UI state — but not the upstream encoder or CDN behavior.

Operational constraint

The site needed to be deployable as a standalone Vercel project inside a monorepo, with absolute media URLs so DNS migration would not break the stream.

SEO constraint

A gospel radio site has to be discoverable by program names, devotional content, music, podcasts, and local broadcast intent — not only by the station brand.

Architecture

System Architecture: Player, Proxy, Schedule, and Content Registry

Website architecture: Next.js on Vercel, Shoutcast stream, proxy fallback, schedule engineFIG 1 — PUBLIC WEBSITE DATA FLOWBrowserNext.js on VercelGlobal playerHTMLAudioElementDirect streamNinjasCast :7026/api/radio/streamsame-origin proxy fallbacktry firston CORS fail/api/radio/now-playingShoutcast metadata · cache 8sSchedule engineAmerica/Sao_Paulo · refresh 30sPWA shellmanifest + pass-through SWSAME MONOREPO · SEPARATE DEPLOYSvideo.radioalvoradatv.com.br → custom OBS/HLS vieweraudio.radioalvoradatv.com.br → self-hosted Icecast tool

FIG 1 — The website never hosts audio bytes. It orchestrates a single player, two stream paths, and shared metadata.

When direct Shoutcast fails cross-origin, the same-origin proxy preserves Icy-MetaData for track titles.

Browser (Next.js on Vercel)
├── Global HTMLAudioElement (PlayerProvider)
│     ├── Direct: stm1.ninjascast.com:7026/stream
│     └── Fallback: /api/radio/stream (same-origin proxy)
├── /api/radio/now-playing → Shoutcast currentsong (edge cache 8s)
├── Schedule engine → America/Sao_Paulo slot matching
├── Static content registry (CMS-ready TypeScript)
└── PWA manifest + pass-through service worker

Separate monorepo apps (not this deploy):
├── video.radioalvoradatv.com.br → custom OBS/HLS viewer
└── audio.radioalvoradatv.com.br → self-hosted Icecast tool

Technical deep dive

Persistent Player: One HTMLAudioElement Across Every Route

One audio element, two modes

PlayerProvider owns a single HTMLAudioElement for live radio and seekable podcast episodes. Switching modes updates src, metadata, and UI chrome — the bar at the bottom never unmounts, so playback survives navigation.

Under the hood: live streams use preload="none" and Shoutcast's icy metadata headers; podcasts use YouTube-sourced episodes with duration and seek support.

Proxy fallback chain

First attempt: direct connection to NEXT_PUBLIC_RADIO_STREAM_URL. On network error, flip useProxy=true and retry via /api/radio/stream.

Why: some mobile browsers treat cross-origin live audio differently. The proxy forwards Range requests and Icy-MetaData headers so track titles still update.

Resilience without drama

Exponential reconnect (up to 6 attempts, 1.5s → 12s cap) on MEDIA_ERR_NETWORK. Twenty-second stall detection. Resume on visibilitychange when the tab returns. Screen reader announcements via a live region so state changes aren't silent.

Player tries direct Shoutcast URL first, then same-origin proxy on failureFIG 2 — PROXY FALLBACK CHAIN1. Play tappedUser starts radio2. Direct URLstm1.ninjascast.com3. Network error?CORS / mobile quirk4. Proxy retry/api/radio/stream5. MetadataIcy-MetaData keptReconnect: up to 6 attempts · exponential backoff 1.5s → 12s · resume on tab visibility

FIG 2 — Proxy fallback is not the happy path. It is the resilience path when mobile browsers block cross-origin live audio.

Exponential reconnect and visibility resume sit on top of this chain so brief drops do not end sessions.

Schedule engine

Timezone-Aware Scheduling in America/Sao_Paulo

Programs like Ainda é Tempo and Graça Abundante recur weekly. The engine uses Intl.DateTimeFormat with America/Sao_Paulo — not the developer's laptop timezone — and walks up to seven days of slots to find what is live now and what is next.

In plain terms: when no scheduled slot matches, the UI falls back to “continuous broadcast” copy — because the Shoutcast stream never stops, even between named programs. That honesty prevents the false “offline” state that kills trust on 24/7 radio.

Information architecture

Information Architecture Built for CMS Migration

Devotional content, podcasts, program pages, music, clips, and articles share a typed content registry. Each item carries a slug, category, source channel, media type, and canonical detail page — so the interface can grow from static TypeScript into a headless CMS without changing how audiences browse.

The live radio player is deliberately not buried inside a route — it is part of the site shell. This is the product decision that matters: make the primary behavior persistent, then let all secondary content orbit around it.

PWA

Engagement-Gated PWA Install: Respect Before Conversion

The service worker is intentionally non-caching — it exists so Chrome meets installability criteria, not to offline the gospel catalog. Real install UX is gated: user must scroll or tap, wait 30 seconds, and hasn't dismissed the prompt in 14 days.

Design trade-off: aggressive install prompts convert short-term; they erode trust on a faith community site. Engagement gating aligns prompt timing with actual interest.

Content layer

Typed Content Registry Ready for Headless CMS

Sermons, podcasts, articles, and media clips live in typed TypeScript registries with shared ContentItem shapes — slug collisions resolved at build time, YouTube IDs validated for embeddability. Lazy YouTube modals defer iframe weight until the user chooses to watch.

SEO: JSON-LD for Organization, WebSite, RadioStation; dynamic OG images; sitemap includes every content slug. Legacy paths (/musica/:slug, etc.) 301 to canonical /conteudo/:slug.

Outcome

Production Results: DNS Migration to Daily Use

A production gospel media platform on Vercel: radio stays stable across navigation, the schedule runs on Brazilian broadcast time instead of server assumptions, and the content model is ready for a CMS when the organization needs editorial scale.

  • Production site on Vercel with DNS migrated from legacy host — stream URLs are absolute, migration-safe
  • Radio follows users across every page without breaking autoplay policy
  • Schedule + metadata visible in player chrome and homepage
  • Typed content registry creates a safe path toward headless CMS integration
  • Install prompt respects user engagement instead of interrupting first-time visitors

Hiring signal

End-to-End Product Ownership in Production

This work demonstrates end-to-end ownership of a production product — not visual composition alone. I translated an audience and operating reality into information architecture, streaming behavior, player resilience, metadata/schedule systems, SEO foundations, PWA affordances, and a shipping Next.js application.

If you're hiring for a remote Design Engineer, Frontend Developer, Full Stack Developer, or Product Engineer role, this is the pattern I bring: reduce the mental models users hold while keeping the technical model explicit enough to survive production.