Web & Mobile App Modernization
Rebuilding 16 years of a volunteer-run station's website, CMS, and mobile apps from the ground up
Overview
I'd been a CHIRP listener for 16 years before I ever got the chance to work directly with them on a project. CHIRP Radio is 107.1 FM in Chicago, an independent, listener-supported station run almost entirely by volunteers. Its website and mobile apps hadn't been meaningfully rebuilt in more than a decade, and I owned the full modernization end to end, as product lead, UX lead, and front-end developer: a new website, a new CMS, and new iOS and Android apps, all replacing a legacy ExpressionEngine site that had outgrown what it was ever built to do.
Role
Owner, Product, UX & Front-End Development
Scope
One year (research and rebuild), primarily myself with additional technical support brought in as needed
Tools
Figma, React, Payload CMS, Postgres, Capacitor, Docker, Google Cloud Run, Cloudflare Pages, Claude
Approach
Problem Statement
CHIRP's website, CMS, and mobile apps were built at different times by different people, without much input from leadership or from what listeners actually needed. The mobile app code specifically came from a now-defunct app company, handed off since then to a separate support company and, by the team's own description, held together with duct tape. The website, iOS app, and Android app existed as three separate codebases, so the same bug or feature often had to be built three different times. Updating the old CMS itself meant paying an outside vendor, so even routine changes came with a cost and a wait.
None of the pieces shared data or a branding concept: a listener's saved song only existed on whichever platform they'd saved it on, and there was no dark mode. Listeners had no way to favorite a DJ or get notified when they went on air, especially for unscheduled shows, and the DJ schedule itself was a dense, table-style grid with no way to search it or see who was on now. Sixteen years of station history sat inside that same CMS, hard for volunteers to use, with no clear path to carry it forward. The new CMS had to be simple enough for CHIRP's non-technical volunteers to run themselves, not something that came with an ongoing bill.
“The goal was to make the station as easy to love digitally as it's always been on the air.”Ryan Wilson
Goals
- Give CHIRP's volunteer team a modern CMS with the conveniences the old one lacked
- Unify a website and two mobile apps that had never shared a design or a database
- Carry 16 years of CHIRP's content forward without losing any of it
- Give listeners the features they'd been asking for:
- Synced saved songs and dark mode
- DJ favoriting with notifications
- Song requests for logged-in listeners
- An easier way to browse the DJ schedule
Process
CHIRP had a print brand guide but nothing built for digital, so I put together recommendations for a digital adjustment: fleshing out their color palette, adding type variations, and working with the team on font choices from Google Fonts. Those calls happened in bi-weekly sprint syncs, where CHIRP reviewed options and decided together, not something I handed down.
The navigation needed the same rework: the old site ran almost entirely on dropdown select menus, which gave every page a bolted-on feel. I regrouped how pages were organized and gave listeners other ways to navigate besides a select menu. Low- and high-fidelity mockups went through the same rhythm: Shawn and two other CHIRP stakeholders, Tony and Kevin, reviewed and discussed each round until we reached alignment, not a single sign-off at the end.
That same rigor carried into the build and testing. I built the entire component library, more than 70 components in Storybook, the same tokenized, documented discipline behind am-ui, then built the front end for the website and both mobile apps myself, including native Android Auto support alongside CarPlay. Before launch, I ran alpha testing internally with Callibrity team members across whatever devices and platforms were available, fixed what that found, then ran two weeks of beta testing with a group of CHIRP volunteers and DJs and fixed what surfaced there too. That two-round process is why so few patches were needed after launch.
- Volunteer & Listener Interviews
- Style Guide Refresh, Product Flows & Wireframes
- Low & High-Fidelity Mockups with Usability Testing
- Figma Design System & Storybook Component Library
- Unified Data Model & Self-Service CMS Design
- Content Migration (16 Years)
- Full Front-End Build (Web, iOS, Android, Android Auto)
- Testing, Launch & Bug Fixes
The entire production component library was built from scratch, matched to the design system, before a single page was wired up.
Findings
CapacitorJS held up well for wrapping one build into iOS and Android apps, and I'd use it again, but getting the apps to feel genuinely native still took real custom iOS and Android code, not just what the wrapper provided out of the box.
Solution
The site and both apps stream CHIRP live, with Now Playing and Recently Played pulled from the station's audio feed in real time. One React codebase, wrapped with Capacitor, now powers the website and both the iOS and Android apps, so a fix or feature ships everywhere at once instead of being built three separate times. The new site and apps share one Payload CMS on top of Postgres, so CHIRP's team edits content once and it's correct everywhere, on the website and on both mobile apps, with role-based permissions scoped to each person's job. Volunteers and DJs also got their own site login, so a DJ's bio, photo, or contact information stays current without going through someone else to make the change. A single audio service now owns playback across the apps instead of every surface managing its own player, which had been the source of recurring CarPlay bugs.
Saved songs and light/dark mode preferences now live on a listener's account instead of a single device, so they follow across the website and both apps. Listeners can favorite a DJ and get a push notification when they go on air, even for unscheduled shows, and the DJ schedule itself moved from a dense table to a tabbed day view that opens on today, highlights whoever's on now, and can be searched by name. Logged-in listeners can also request a song directly, on the website or in the app, something CHIRP never had before.
All 16 years of CHIRP's content moved over from the old ExpressionEngine database, and checkout moved off PayPal onto Stripe. None of it depends on me being available after launch to keep it running. As a listener myself, I'm proud of what I built, and I use it every week to listen to my favorite DJs, at home and in the car.
Impact
Results
- CHIRP's volunteers work in a modern CMS built around their day-to-day workflow, with role-based permissions so a content editor, a store admin, and a super-admin each only have access to what their job needs, instead of one flat login for everyone
- DJs and volunteers can update their own name, photo, and contact information directly instead of asking someone else to do it
- One CMS feeds the website and both mobile apps at once, so nothing has to be entered or fixed three separate times across three surfaces
- Listeners get synced saved songs and dark mode across every device, DJ favoriting with push notifications, song requests they never had before, and a searchable, day-view DJ schedule instead of a static table
- DJs have gotten noticeably more song requests since the new apps launched
- 16 years of station history carried forward with nothing lost in the move
- A single shared audio service ended the recurring CarPlay bugs that came from every surface managing its own player separately
Achievements
- Led product and technical direction for a from-scratch rebuild of a nonprofit's entire digital presence
- Designed the shared data model that finally connected CHIRP's website and mobile apps
- Replaced three separate platform codebases with one shared React and Capacitor codebase powering the website and both mobile apps
- Kept self-service simplicity for CHIRP's volunteers central to every scoping decision, not just a line in the brief
Takeaways
What would I do differently?
If I did this again, I'd bring AI into the process from the very start, not just the build: using it to move faster, track findings and requirements more accurately, and go from product flow to wireframe to mockup to prototype faster, then build out the component library faster too. I've carried that into every project since, the same approach I used on a client portal built in 13 weeks.
How did I grow?
Building CHIRP taught me a hard lesson about React: a site that depends on JavaScript to render is just a blank white page for anyone without it, screen reader users included. One of the first pieces of post-launch feedback was a listener whose screen reader wasn't picking up any content, which I fixed by building a static version of the site with Eleventy that redirects to text.chirpradio.org whenever JavaScript isn't available. I've tested with JavaScript disabled on every project since.
The gap between that early research and the actual build also changed how I think about using AI well: by the time development started, AI coding tools had matured enough that building the entire component library myself with Claude Code was faster and more thorough than it would have been on the original timeline, accessibility and responsiveness built in from day one instead of retrofitted.