packages/expo/src/migrations/update-22-2-0/files/ai-instructions-for-expo-54.md
These instructions guide you through migrating an Nx workspace containing Expo projects from Expo SDK 53 to Expo SDK 54. Work systematically through each breaking change category.
Identify all Expo projects:
nx show projects --with-target start
Locate all Expo configuration files:
app.json or app.config.{js,ts}metro.config.{js,ts}project.json files for Expo-related configurationIdentify affected code:
expo-avexpo-file-systemStatusBar configuration@expo/vector-iconsCheck for Detox E2E testing projects:
detox in package.json dependenciesdetox.config.js or .detoxrc.js filesExpo SDK 54 does not currently support Detox for end-to-end testing. If your workspace includes Detox projects, be aware of the following:
detox in package.json dependencies or projects with detox.config.jsAction Required: If Detox projects are detected, you MUST ask the user before proceeding:
"Your workspace contains Detox E2E tests. Detox is not currently supported in Expo SDK 54. Do you want to proceed with the migration knowing that Detox tests will not work until Detox adds SDK 54 support?"
Alternatives to consider:
The expo-av package has been deprecated and split into two separate packages: expo-audio and expo-video.
Search Pattern: import.*from ['"]expo-av['"] with Audio usage
Changes Required:
// BEFORE (Expo SDK 53)
import { Audio } from 'expo-av';
const sound = new Audio.Sound();
await sound.loadAsync(require('./audio.mp3'));
await sound.playAsync();
// AFTER (Expo SDK 54)
import { useAudioPlayer } from 'expo-audio';
function AudioComponent() {
const player = useAudioPlayer(require('./audio.mp3'));
const play = () => player.play();
const pause = () => player.pause();
return <Button onPress={play} title="Play" />;
}
Action Items:
expo-audio: npx expo install expo-audioexpo-av if only used for audioAudio.Sound with useAudioPlayer hookAudio.Recording with useAudioRecorder hookplayAsync -> play, etc.)Search Pattern: import.*from ['"]expo-av['"] with Video usage
Changes Required:
// BEFORE (Expo SDK 53)
import { Video } from 'expo-av';
function VideoPlayer() {
return (
<Video
source={{ uri: 'https://example.com/video.mp4' }}
style={{ width: 300, height: 200 }}
useNativeControls
resizeMode="contain"
/>
);
}
// AFTER (Expo SDK 54)
import { VideoView, useVideoPlayer } from 'expo-video';
function VideoPlayer() {
const player = useVideoPlayer('https://example.com/video.mp4', (player) => {
player.loop = true;
player.play();
});
return (
<VideoView
player={player}
style={{ width: 300, height: 200 }}
nativeControls
contentFit="contain"
/>
);
}
Action Items:
expo-video: npx expo install expo-videoexpo-av if only used for videoVideo component with VideoView and useVideoPlayerresizeMode prop with contentFituseNativeControls prop with nativeControlsSearch Pattern: import.*from ['"]expo-file-system/next['"]
Changes Required:
// BEFORE (Expo SDK 53)
import { File, Directory } from 'expo-file-system/next';
// AFTER (Expo SDK 54)
import { File, Directory } from 'expo-file-system';
Action Items:
expo-file-system/next imports with expo-file-systemIn Expo SDK 54, the expo-status-bar configuration is now handled differently. The old userInterfaceStyle in app.json affects status bar theming.
Search Pattern: userInterfaceStyle in app.json or app.config.*
Changes Required:
// BEFORE (Expo SDK 53)
{
"expo": {
"userInterfaceStyle": "automatic",
"ios": {
"userInterfaceStyle": "light"
},
"android": {
"userInterfaceStyle": "dark"
}
}
}
// AFTER (Expo SDK 54)
// The userInterfaceStyle is now handled via expo-system-ui
// For programmatic control, use:
// AFTER (Expo SDK 54) - Programmatic control
import * as SystemUI from 'expo-system-ui';
// Set root view background color
SystemUI.setBackgroundColorAsync('#ffffff');
Action Items:
expo-system-ui: npx expo install expo-system-uiuserInterfaceStyle settings in app.jsonexpo-system-ui if programmatic control is neededExpo SDK 54 enables edge-to-edge display by default on Android. This means content can extend behind system bars (status bar and navigation bar).
Search Pattern: Android-specific styles, safe area handling, padding/margin adjustments
Changes Required:
// BEFORE (Expo SDK 53) - Implicit safe area
function App() {
return (
<View style={{ flex: 1 }}>
<Text>Content</Text>
</View>
);
}
// AFTER (Expo SDK 54) - Explicit safe area handling
import { SafeAreaView } from 'react-native-safe-area-context';
// or
import { useSafeAreaInsets } from 'react-native-safe-area-context';
function App() {
return (
<SafeAreaView style={{ flex: 1 }}>
<Text>Content</Text>
</SafeAreaView>
);
}
// Or with hooks for more control
function App() {
const insets = useSafeAreaInsets();
return (
<View
style={{ flex: 1, paddingTop: insets.top, paddingBottom: insets.bottom }}
>
<Text>Content</Text>
</View>
);
}
Action Items:
react-native-safe-area-context if not presentSafeAreaProviderSafeAreaView or use useSafeAreaInsets where content touches screen edgesExpo SDK 54 supports both Reanimated v3 and v4. If you're using New Architecture, you should upgrade to v4.
Search Pattern: react-native-reanimated in package.json, worklet functions
Reanimated v3 (stable):
npx expo install react-native-reanimated@3
Reanimated v4 (recommended for New Architecture):
npx expo install react-native-reanimated@4
Action Items:
Some icons in @expo/vector-icons may have been renamed or removed in SDK 54.
Search Pattern: import.*from ['"]@expo/vector-icons['"]
Action Items:
Expo SDK 54 uses React 19.1 and React Native 0.81.
Search Pattern: React.FC, deprecated lifecycle methods, legacy context API
Changes Required:
// BEFORE - React.FC (discouraged in React 19)
const MyComponent: React.FC<Props> = ({ title }) => {
return <Text>{title}</Text>;
};
// AFTER - Direct function typing
function MyComponent({ title }: Props) {
return <Text>{title}</Text>;
}
// Or with explicit return type
const MyComponent = ({ title }: Props): React.ReactElement => {
return <Text>{title}</Text>;
};
Action Items:
@types/react@~19.1.0)React.FC pattern (optional but recommended)Expo SDK 54 uses Metro 0.83 with updated configuration.
Search Pattern: metro.config.{js,ts}
Changes Required:
// BEFORE (Expo SDK 53)
const { getDefaultConfig } = require('@expo/metro-config');
const config = getDefaultConfig(__dirname);
// AFTER (Expo SDK 54) - Same API, but verify compatibility
const { getDefaultConfig } = require('@expo/metro-config');
const config = getDefaultConfig(__dirname);
// Ensure any custom transformers are compatible with Metro 0.83
Action Items:
@expo/metro-config to ~0.22.0metro-config to ~0.83.0metro-resolver to ~0.83.0Expo SDK 54 updates to babel-preset-expo@~14.0.0.
Search Pattern: babel.config.js, .babelrc
Action Items:
babel-preset-expo to ~14.0.0npx expo start --clear# Clear Expo cache
npx expo start --clear
# Clear Metro bundler cache
rm -rf node_modules/.cache/metro-*
# Clear Nx cache if needed
nx reset
# Test each project individually
nx run-many -t test -p PROJECT_NAME
# Run tests across all affected projects
nx affected -t test
# iOS
nx run PROJECT_NAME:run-ios
# Android
nx run PROJECT_NAME:run-android
expo-av usages migrated to expo-audio or expo-videoexpo-file-system/next imports updatedSolution: The new useAudioPlayer hook manages cleanup automatically. If you need persistent audio, consider using a context or state management solution.
Solution: Ensure you're using useVideoPlayer with VideoView. Check that the video source URL is correct and accessible.
Solution: Wrap your screen content with SafeAreaView from react-native-safe-area-context or use useSafeAreaInsets for custom padding.
Solution: Check the Expo Vector Icons directory for renamed icons: https://icons.expo.fyi/
Solution: Update @types/react to ~19.1.0 and check for deprecated patterns.
Solution: Clear all caches with npx expo start --clear and ensure Metro packages are at compatible versions.
Create a checklist of all files that need review:
# Configuration files
find . -name "app.json" -o -name "app.config.*"
find . -name "metro.config.*"
find . -name "babel.config.*"
# Files with expo-av imports
rg "from ['\"]expo-av['\"]" --type ts --type tsx --type js
# Files with expo-file-system/next imports
rg "from ['\"]expo-file-system/next['\"]" --type ts --type tsx --type js
# Files with vector-icons imports
rg "from ['\"]@expo/vector-icons['\"]" --type ts --type tsx --type js
# Safe area related files
rg "SafeAreaView|useSafeAreaInsets" --type ts --type tsx --type js
# Find all Expo projects
nx show projects --with-target start
# Run specific project
nx start PROJECT_NAME
# Test specific project after changes
nx test PROJECT_NAME
# Test all affected
nx affected -t test
# View project details
nx show project PROJECT_NAME --web
# Clear Nx cache if needed
nx reset
# Clear Expo cache
npx expo start --clear
When executing this migration: