"To share, or not to share, that is the question." - every React Native dev, probably
If you build apps with React Native, you've definitely asked yourself this βοΈ. You've got navigation, state management, business logic, UI components, most of it is the same, and is probably using the same libraries, regardless of whether you're targeting Android TV, Apple TV, or Fire TV. So why maintain separate codebases?
With Vega OS, we had a chance to get this right from the start. Rather than shipping another platform that locks you into a single target, we wanted to make it easy to build for multiple TV platforms at the same time. Depending on your app's architecture, you can realistically share 70-85% of your codebase across TV operating systems. I want to walk you through how it works, and help you figure out what 'to share or not to share'.
To help, we've published a few projects on Github. You can pick the one that fits your workflow and start your journey, or read on and I'll walk through the workspace setup, some of the tooling that makes it all work and what code you can share today (and what you shouldn't).
π Multi-TV Hello World - starter template
πΊ React Native Multi-TV App Sample - production-ready reference app
π€ Vega Multi-TV Migration agent skill - AI assisted migration
The shared workspace approach
So what does sharing code across TV platforms actually look like in practice? You want a setup where the shared code lives in one place and the OS specific features (video playback, native integrations, platform quirks) stay isolated.
To set this up, we refactor the Vega project into a monorepo using Yarn v4 workspaces. On the Vega side, you keep using the Vega SDK as normal. For the other platforms, we use Expo TV, which gives you Android TV, Apple TV, and Web support out of the box with a single Expo project. You split things into three workspaces:
-
packages/shared/- OS-agnostic code (the majority of your app) -
packages/vega/- Vega-specific code for Fire TV -
packages/expotv/- Expo TV for Android TV, Apple TV, and Web
A root package.json coordinates builds across these sub projects. Each workspace has its own dependencies and build configuration, but they share common code through workspace imports.
my-app/
βββ package.json # Root workspace config (Yarn 4.x)
βββ packages/
β βββ shared/ # @myapp/shared - OS agnostic code
β β βββ package.json
β β βββ index.ts
β β βββ src/
β β βββ components/ # Shared UI (Banner.tsx, Banner.kepler.tsx, Banner.android.tsx)
β β βββ hooks/
β β βββ services/
β β βββ utils/
β βββ vega/ # @myapp/vega - Fire TV
β β βββ src/App.tsx
β β βββ manifest.toml
β β βββ metro.config.js
β βββ expotv/ # @myapp/expotv - Android TV / Apple TV / Web
β βββ src/App.tsx
β βββ app.json
β βββ metro.config.js
The key rule to remember is OS packages import from shared, but shared never imports from OS packages.
Note: By default, the Vega SDK works with npm, but this monorepo approach uses Yarn v4 workspaces.
The tooling that makes sharing work
The workspace structure gets you most of the way there, but two pieces of tooling tie it all together.
The first is VMRP (Vega Module Resolver Preset). Your shared code uses standard React Native imports like react-native-gesture-handler or react-native-reanimated, but Vega has its own ported versions of these libraries. VMRP is a Babel preset that automatically swaps those imports for their Vega equivalents at build time, so your shared code stays clean and portable.
You configure it in your Vega package's babel.config.js:
// packages/vega/babel.config.js
module.exports = {
presets: [
'module:metro-react-native-babel-preset',
'module:@amazon-devices/kepler-module-resolver-preset',
],
};
With this in place, from 'react-native-gesture-handler' in your shared code automatically resolves to @amazon-devices/react-native-gesture-handler when building for Vega. No conditional imports, no platform checks.
The second is Vega Studio's monorepo support. It automatically detects your workspace layout and imports Vega sub packages when you open the project. Enable it in Settings > Vega > Features: Monorepo, and it handles package discovery, workspace synchronisation, and build task coordination for you.
Now lets discuss:
What makes sense to share
Not all code belongs in packages/shared/. Some things work identically across platforms, some need OS-specific implementations, and some sit in between. Here's how to think about what goes where.
Business logic and state management (share it)
This is the easiest win and where you get the most reuse. Your API calls, Redux/Zustand stores, data transformations, validation logic, formatting utilities usually don't have any OS specific dependencies. As a rule of thumb, if it doesn't touch a native API or render anything to screen, it belongs in packages/shared/.
The Multi-TV App Sample demonstrates this with its dynamic content loading. The catalog API client, data transforms, and type definitions all live in the shared package and every platform consumes the same data layer.
UI components (share most of it)
Most of your UI components are shareable too. Buttons, cards, lists, layouts, modals, grid views are usually standard React Native that works across platforms without changes.
Where it gets interesting is platform specific styling. React Native's file extension resolution handles this cleanly. Write your base component as Banner.tsx, then add platform specific extentions Banner.kepler.tsx. Metro picks the right file at build time (this relies on your Metro config being set up for monorepo resolution, both the Hello World and Multi-TV App Sample repos include this configuration).
The Hello World repo shows this pattern with its HeaderLogo component, which loads different platform logos using .kepler.tsx, .android.tsx, .ios.tsx, and .web.tsx variants.
You can also use Platform.select() for smaller differences that don't warrant separate files.
Navigation and screen flows (share with care)
There are two types of navigation patterns in TV apps, and they share differently.
Screen-to-screen routing (moving between pages, tabs, drawers) is usually fully shareable. If you're using React Navigation (which Vega supports via its react-navigation package), your screen definitions, route configs, and navigation structure work the same across platforms.
Spatial / Focus navigation (moving focus between elements on screen with a remote control d-pad) is where it gets platform-specific. The Multi-TV App Sample uses React TV Space Navigation to handle focus movement across all platforms, but the layer underneath that captures remote control key events differs per OS. The RemoteControlManager has separate .android.ts, .ios.ts, and .kepler.ts files because each platform fires different key events. The focus logic is shared, the input handling is forked.
Layout and theming (share the system, tweak per platform)
TV apps need to look right across different screen sizes and display densities. The pattern here is the same as with the UI: share the design system (tokens, spacing, typography scales), and use platform-specific files or Platform.select() where individual platforms need adjustments. For production apps, consider building out a proper theme layer with responsive layouts rather than relying on simple scaling alone.
The Hello World repo includes scaling utilities that normalise dimensions across TV displays based on a 1920x1080 baseline. The scaling logic itself is shared, but you might need platform-specific tweaks for things like safe areas or overscan.
What you should keep separate
Media Player
Production streaming apps tend to use OS-specific media players for optimal performance. You can either follow that path, using native implementations per OS, or use an abstraction to work across likeΒ react-native-videoΒ (which now hasΒ Vega support). Unless you use these abstractions, your media implementations should live in your OS-specific packages.
Amazon specific features
Content Launcher, In-App Purchase, Amazon Device Messaging, and similar features currently need separate implementations for Vega and Fire OS because their underlying APIs are OS-specific. We're working to migrate these behind single RN libraries, but for now, structure these implementations so they're easy to consolidate later. Keep them in your platform packages with clean interfaces that shared code can call through.
Vega UI Components (VUIC)
Worth flagging early, simple VUIC components like Button and Text migrate easily to standard React Native equivalents. But Carousel and SeekBar need more work to replace. Identify these dependencies during your analysis phase so you're not surprised mid-migration.
Native modules and DRM
Anything that touches native code directly (custom TurboModules, DRM implementations, hardware-specific features) stays platform-specific. No way around it.
Try it yourself: the Hello World starter
The fastest way to see this in action is the Multi-TV Hello World repo. It's a hello-world project with a shared Header component that renders across Vega, Android TV, Apple TV, and Web from the same codebase.
See it in production: the Multi-TV App Sample
For something thats closer to a production app, check out the React Native Multi-TV App Sample. This is a TV app template with:
- Video playback via react-native-video
- Spatial navigation with React TV Space Navigation
- Drawer navigation, grid layouts, and a dynamic hero banner
- Remote control support across all platforms
- A shared UI library (
@multi-tv/shared-ui) with platform-specific file resolution
It supports Android TV, Apple TV, Fire TV (Fire OS), Fire TV (Vega OS), and Web from a single monorepo. Good reference for how the shared workspace pattern holds up at scale.
Migrating your own app: the AI-assisted approach
We've also packaged our migration process into a three-phase agent skill that works with Kiro, Claude, or other AI coding assistants. It's available on GitHub: vega-multi-tv-migration.
To use it with Kiro, copy the vega-multi-tv-migration directory into ~/.kiro/skills/ and start a conversation about migrating your app. It activates automatically based on your conversation context.
Phase 1: Analyse your codebase
The skill runs static code analysis and gives you an executive summary with estimated code reuse percentage, a dependency classification (what goes to shared, what stays OS-specific, what VMRP handles), and a screen-by-screen migration plan.
"Analyse my Vega app for multi-platform migration."
Phase 2: Build the shared workspace structure
This creates the monorepo scaffold, moves code into shared and vega packages, configures Metro for monorepo resolution, and sets up VMRP. After this step, your Vega app should build and run exactly as before, but with shared code properly separated.
"Convert my Vega project to a yarn workspaces monorepo using the analysis from Phase 1."
Phase 3: Add OS-specific implementations
This sets up the Expo TV package and implements replacements for Vega-specific dependencies:
-
@amazon-devices/kepler-player-clientbecomes react-native-video -
@amazon-devices/kepler-ui-componentsbecomes custom components or community libraries -
@amazon-devices/kepler-file-systembecomes expo-file-system
"Add Android TV and Apple TV support using Expo TV. Replace my Vega-specific dependencies with stock React Native equivalents."
Best practices
A few things we've learned the hard way:
- Clean up before migration: AI tools migrate code as is, including unused files and existing bugs - Do some optimisation first.
- Manage React Native version deltas: Platforms don't need identical RN versions, but keep the delta to 4-6 versions max for third-party library compatibility.
- Test on real devices: Emulators are great for development, but validate on real hardware before shipping. TV apps behave differently on actual TVs - shocking, I know π±.
- Prebuild targets separately first: Get each target running independently before building together to save you some debugging headache.
Get started building today:
- Try the Multi-TV Hello World to see Fast Refresh across Vega, Android TV, Apple TV, and Web.
- Explore the Multi-TV App Sample for a production-ready reference.
- Run the migration agent skill on your own app.
- Tell us what works, what doesn't, and what you'd like to see next- you can open a discussion on GitHub or in our Forums.



Top comments (0)