| name | rn-template-anatomy |
| description | Template repo file structure: where screens, navigation, theme, data, and platform entries live in the monorepo |
| applies_to | ["phase_clone","phase_brand","phase_manifest","phase_screens","phase_build"] |
| load_when | any time the agent reads or writes project files |
Template anatomy
The base template is AmazonAppDev/react-native-multi-tv-app-sample. Knowing where things live is half the job. Editing the wrong file is most of the failures.
Top-level layout
react-native-multi-tv-app-sample/
├── apps/
│ ├── expo-multi-tv/ # Universal app: Android TV, Apple TV, Fire TV (FOS), Web
│ └── vega/ # Fire TV Vega OS only (separate SDK)
├── packages/
│ └── shared-ui/ # All shared components, screens, hooks, theme, data
├── package.json # Yarn workspaces root
└── tsconfig.base.json # Shared TS config
When you touch which app
apps/expo-multi-tv/ — anything that runs on Android TV, Apple TV, Fire TV FOS, or Web. Edit when:
- Changing app metadata (
app.json: name, slug, bundle ID, banner)
- Adjusting Expo plugins, native build settings
- Adding a platform-specific native dependency
apps/vega/ — Fire TV Vega OS only. Edit when:
- The target platform list includes
firetv-vega
- A feature uses
@amazon-devices/* packages
- Vega-specific manifest changes
Do not try to merge the two apps. They share UI via packages/shared-ui and that's the boundary. If a feature won't work on Vega, gate it; don't fork the shared package.
packages/shared-ui/ is the heart
This is where ~80% of edits go. Layout:
packages/shared-ui/
├── components/ # Reusable UI: Drawer, Grid, Hero, Tile, Player, etc.
├── screens/ # Composed screens: Home, Detail, Player, Settings
├── navigation/ # Drawer/route config
├── hooks/ # Data hooks (manifest consumption)
├── theme/ # Tokens, theme provider
├── data/ # Static data, manifest goes here
└── remote-control/ # Per-platform remote handlers
Metro platform resolution
Metro picks the right file by extension. This is how the template handles platform splits without if (Platform.OS === ...) everywhere.
| Extension | Resolved when |
|---|
Foo.android.ts | Android TV + Fire TV FOS builds |
Foo.ios.ts | Apple TV build |
Foo.kepler.ts | Vega OS build (Kepler is the Vega runtime) |
Foo.web.ts | Web build |
Foo.ts | Fallback for any platform without a specific file |
Decision tree: do I need a platform-specific file?
- Does the API call work identically on all platforms? → single
Foo.ts.
- Different SDK on Vega but the rest is the same? →
Foo.ts (default) + Foo.kepler.ts (override).
- Different on every platform? → drop the default and write one file per platform.
- Only differs in styling? → put platform in styles, not in files. Keep one file.
Asset locations
| Asset | Location | Notes |
|---|
| App icon | apps/expo-multi-tv/assets/icon.png | 1024×1024 |
| Splash | apps/expo-multi-tv/assets/splash.png | 1920×1080 minimum for TV |
| Android TV banner | apps/expo-multi-tv/assets/icon-400x240.png | Exact 400×240, required for leanback |
| Shared images | packages/shared-ui/assets/ | Component-local images |
| Vega assets | apps/vega/assets/ | Vega-specific |
Yarn workspaces commands
yarn
yarn workspace expo-multi-tv add <pkg>
yarn workspace @multi-tv/shared-ui add <pkg>
yarn workspace expo-multi-tv run android
Anti-pattern: running npm install in a sub-app directory. It breaks the workspace symlinks. Always use yarn at the root, or yarn workspace ....
Anti-pattern: adding react or react-native to packages/shared-ui/devDependencies. With nmHoistingLimits: workspaces, this installs a physical copy at packages/shared-ui/node_modules/react/ which the web bundler resolves as a separate React instance, crashing the app with "Invalid hook call." Use only @types/react for typechecking in shared-ui. The runtime react is resolved via Metro's nodeModulesPaths (pointing to the expo app's node_modules) on all platforms.
Where new content goes
| You want to add… | Put it here |
|---|
| A new component | packages/shared-ui/components/<Name>/ |
| A new screen | packages/shared-ui/screens/<Name>/ + register in navigation/ |
| Content manifest | packages/shared-ui/data/content.json (overwrites existing) |
| A new data hook | packages/shared-ui/hooks/use<Name>.ts |
| Theme tokens | packages/shared-ui/theme/tokens.ts |
| Vega-only feature | apps/vega/src/<feature>/ |
Dangerous places — touch with care
apps/*/ios/ and apps/*/android/ — native projects, regenerated by expo prebuild. Manual edits get blown away. If you need a change, do it via Expo config plugins.
app.json plugins array — order matters; @react-native-tvos/config-tv must come before plugins that modify the manifest.
tsconfig.base.json — changing path aliases breaks Metro resolution. If you must, also update Metro config.
Quick sanity checks before any larger edit
yarn runs cleanly from the root.
yarn workspace expo-multi-tv tsc --noEmit passes.
- The file you're about to edit isn't auto-generated (check for "DO NOT EDIT" headers and prebuild output dirs).
If any of those fail before you start, fix that first. Don't pile new changes on a broken tree.