---
title: "Consolidating 12 Streaming Apps Into 2"
description: "At Sky we merged twelve European territory apps into two platforms. Notes on what we unified, what we deliberately left alone, and the parts of the job that had nothing to do with code."
author: "Filipe Brito Ferreira (Senior Front-End Platform Engineer)"
canonical: https://www.fbritoferreira.com/blog/consolidating-12-streaming-apps-into-2/
published: 2026-09-12
tags: ["frontend-platform", "architecture", "streaming", "react", "ci-cd", "retrospective"]
image: https://cdn.fbritoferreira.com/images/consolidating-12-streaming-apps-into-2-1200.webp
---

A security patch had to go out. Same fix, twelve apps, because the entitlements logic existed in twelve slightly different dialects. It took a week instead of an afternoon, because each app had drifted in some way nobody had written down. That was one patch. That was roughly the maintenance tax on every cross-cutting change we made between 2018 and 2023, and it's the reason the consolidation happened at all.

I was at Sky UK for that whole period, working across Sky GO, NOW TV, NOW, Peacock TV, and Sky Showtime on web, desktop and TV. Somewhere in the middle of it I ended up technical-leading the frontend side of merging the twelve European territory apps into two platforms. We ended up with 83% less code to maintain and deployment that ran about 60% faster. Those numbers took years, not months, and the interesting parts of the story aren't in them.

## Nobody decided to have twelve apps

It's tempting to frame the territory apps as a mistake someone made. They weren't. A market launches, the deadline is real, and forking the nearest existing app is genuinely the fastest path. Two years later that market has something that works and that nobody outside the territory can safely change. Do that five or six times and you have our situation.

So by the time we started, the question was never "should we unify". It was "what do we unify, and can we do it without freezing delivery in every live market".

## What we unified, and what we didn't

Product teams will stop you the moment unification touches something they consider a market differentiator, and they're usually right to. So we drew the line early: unify everything that isn't a differentiator.

Playback, DRM integration patterns, account management, the component library, analytics, error monitoring, build tooling. None of it wins customers in Germany versus Italy. It's all cost, and duplicated cost gets paid twelve times forever. That half was honestly not controversial.

Territory differences were the hard part. Entitlement rules, content availability, regulatory requirements, launch sequencing — none of that disappeared because the codebases did. The single most important decision we made was that all of it moved into feature flags keyed on territory, and that flags became the only sanctioned way to express territory difference.

Before, the Italian app contained Italian rules in its control flow, and so did every other app. After, one platform held one set of capabilities, and a market's identity was data instead of a fork. "We need this in Spain but not yet in Germany" stopped being a branching-and-release-management conversation and became a flag flip. And the shared codebase never grew `if (territory === 'X')` weeds, because the flags contained the divergence instead of letting it leak.

There was also a third bucket that doesn't get talked about enough: code that was genuinely local and stayed local. Some markets had structurally different requirements, and forcing those into the shared platform would have made the platform worse. The goal was fewer systems, not one system.

## Strangler, not big bang

Streams don't stop because engineering wants to refactor. We never had the option of pausing product work, so the migration ran incrementally on live products.

We built the unified platform alongside the territory apps and proved it in production before anything depended on it. Then we moved capability by capability — a playback surface, an account flow — rather than migrating "the German app" in one heroic move. Each move was shippable and reversible. Run old and new in parallel per capability, cut over when the numbers said so.

The numbers part mattered more than we expected. Because we'd rolled out shared analytics and error monitoring first, every cutover was judged against real production data: performance budgets gave each migrated capability a definition of done that wasn't "it works" but "it's as fast as what it replaced". Some of those measurements killed our favorite implementations. That's what they were for.

The shared CI/CD side ended up adopted by five teams with deployment time down 60%, and the thing I'd emphasize about that number is that it's not about tool speed. Twelve apps drift apart silently. Two apps sharing one pipeline drift apart loudly, while it's still cheap to do something about it. The pipeline is the drift detector.

## The part no architecture diagram captures

The technical work was the easier half, and I don't say that to diminish it.

Territory teams gave up autonomy over their own app. That trade only holds if the unified platform earns a reputation for not blocking people — fast pipelines, quick review turnaround, flags that hand control back. We put as much effort into the deployment experience as into the platform code, and from where the territory teams sat, that effort *was* the migration.

Teams also watch the first adopter like hawks. Whatever happens with the first capability that moves onto the platform decides whether the second happens voluntarily or through mandate. We made sure the first one went well, and I still think that was worth more than any architecture document I wrote.

One thing I didn't anticipate: twelve small codebases mean twelve groups of people who each understand their whole system. Two platforms mean fewer, deeper specialists. Some of the territory engineers became the strongest platform engineers I worked with — but only where we treated their territory knowledge as input to the unified design rather than legacy to be overwritten. That period is also where I went from SD1 to SD3, and most of that growth was learning to design for teams I didn't sit with.

## What I'd do differently

Push the flag system harder, earlier. Every month a territory fork survived was a month of divergence we'd have to migrate later. Same for shared observability: we built it alongside the migration, and some early decisions ran on partial data that decent instrumentation would have settled in an afternoon.

And I'd spend less time hunting for the perfect shared abstraction. The unifications that worked were the boring 90%, with clean seams left around the interesting 10%.

Somewhere in year two there was a twelfth-and-final security patch applied once. I remember it taking an afternoon and nobody quite trusting that it was done. That's what the 83% actually felt like.
