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

radioalvoradatv.com.br

A gospel media home that never stops playing

The public website is where audiences discover content, see what is on air in São Paulo time, install the PWA — and keep listening while they browse.

Role

Product Designer & Design EngineerIA, UI, frontend, SEO

Stack

Next.js 16 · React 19Tailwind v4 · Vercel

Audio

Shoutcast AAC+NinjasCast origin

Deploy

Standalone Vercel appMonorepo /website

  • 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 hub everything else points to

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

Designing a media platform around one persistent truth: the radio is live

I owned the product architecture, information architecture, interaction design, frontend implementation, deployment model, and the operational surfaces that make the site trustworthy after launch. This was not a static brand site project; it was a small broadcast product where the homepage, program schedule, content library, PWA install prompt, WhatsApp community actions, and bottom radio player all had to agree about what the station is doing now.

The main constraint was that the public website should feel editorial and calm while still behaving like a live media app. A typical approach would make the player dominant and push content into secondary pages. I took the opposite route: the site remains 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

How the pieces connect

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 radio player

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

São Paulo airtime, not server UTC

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

A content hub that behaves like a broadcast product

The website organizes devotional content, podcasts, program pages, music, clips, and articles through a typed content registry. That matters because the future CMS migration should not require redesigning the mental model. Each item already has a slug, category, source channel, media type, and canonical detail page, so the interface can grow from static TypeScript data 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. In hiring terms, this is the product decision I would want to see from a senior designer-engineer: make the primary behavior persistent, then let all secondary content orbit around it.

PWA

Installable, but not annoying

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

CMS-ready without a CMS yet

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

What shipped — and why it matters

The result is a production gospel media platform on Vercel where the radio experience remains stable across navigation, the schedule uses Brazilian broadcast time rather than 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

What this case study proves

This work demonstrates product design for a real media system, not only visual composition. It shows that I can translate a brand and audience into navigation, streaming behavior, player resilience, metadata architecture, SEO foundations, PWA affordances, and a deployable Next.js application.

If you are hiring for a product designer, design engineer, or senior UX role around media, community, publishing, or content-heavy products, this is the pattern: reduce the number of mental models users must hold while keeping the technical model explicit enough for the product to survive production.