React Native Expo SDK 52 and the New Architecture
React Native Expo SDK 52 marks the full adoption of React Native’s new architecture in the Expo ecosystem. With the Fabric renderer and TurboModules now enabled by default, Expo SDK 52 delivers faster rendering, synchronous native module access, and concurrent React features. More importantly, this release brings the new architecture to every Expo developer without requiring manual native code changes, prebuild surgery, or hand-written CMake files.
This guide covers what has changed in SDK 52, how to migrate an existing app, the performance improvements you can realistically expect, and strategies for handling libraries that have not yet adopted the new architecture. Whether you are upgrading from SDK 51 or starting fresh, the goal here is practical depth: not just what to flip on, but why each change matters and where the sharp edges hide.
What Changed in SDK 52
At a high level, SDK 52 makes three fundamental changes. First, Fabric replaces the legacy Paper renderer. Second, TurboModules replace the asynchronous bridge for native communication. Third, the toolchain leans fully into Hermes with React 18 concurrent features turned on by default. Together, these shift the entire JavaScript-to-native boundary from a serialized message queue to a shared-memory interface built on the JavaScript Interface (JSI).
Expo SDK 52 Architecture Changes
OLD ARCHITECTURE (SDK 50 and earlier):
JS Thread → Bridge (async JSON) → Native Thread
└── Serialization overhead
└── Asynchronous only
└── Single render pass
NEW ARCHITECTURE (SDK 52 default):
JS Thread → JSI (C++ shared memory) → Native Thread
└── No serialization
└── Synchronous + Asynchronous
└── Concurrent rendering (React 18)
┌───────────────────┬──────────────┬──────────────┐
│ Feature │ SDK 51 │ SDK 52 │
├───────────────────┼──────────────┼──────────────┤
│ Renderer │ Paper │ Fabric │
│ Native modules │ Bridge │ TurboModules │
│ JS engine │ Hermes │ Hermes 2 │
│ Concurrent mode │ Optional │ Default │
│ Bridge │ Required │ Removed │
│ Codegen │ Manual │ Automatic │
│ Interop layer │ Available │ Deprecated │
└───────────────────┴──────────────┴──────────────┘
Why the Bridge Mattered (and Why Removing It Helps)
To understand why this release is significant, it helps to remember what the bridge actually did. In the old architecture, every call from JavaScript to native code — animating a view, reading a sensor, scrolling a list — was serialized to JSON, queued, and shipped across an asynchronous boundary. That design was robust, but it created two persistent problems. First, large payloads paid a serialization tax on every crossing. Second, because everything was asynchronous, layout-dependent operations like measuring a view could not be resolved in the same frame, which produced visible jank.
JSI removes that boundary. Instead of serializing messages, JavaScript holds C++ host objects that point directly at native implementations. As a consequence, a call can be synchronous when it makes sense, the renderer can read layout immediately, and there is no JSON parse step in the hot path. This is the foundation that makes the rest of SDK 52 possible — Fabric and TurboModules are both built directly on top of JSI.
Migration from SDK 51
For most Expo apps, the migration is refreshingly mechanical. The Expo CLI handles the bulk of the configuration, and because the new architecture is the default, there is no opt-in flag to set. Even so, you should treat the upgrade as a deliberate task rather than a casual bump: the work is less in the config and more in verifying your dependency tree.
# Step 1: Update Expo SDK
npx expo install expo@^52.0.0
# Step 2: Update all Expo packages to compatible versions
npx expo install --fix
# Step 3: Run compatibility and config checks
npx expo-doctor
# Step 4: Clean prebuild so native projects regenerate
npx expo prebuild --clean
# Step 5: Rebuild dev client (you cannot use the prebuilt Go app for custom native)
npx expo run:ios
npx expo run:android
// app.json — SDK 52 configuration
{
"expo": {
"name": "MyApp",
"slug": "my-app",
"version": "2.0.0",
"sdkVersion": "52.0.0",
"platforms": ["ios", "android"],
"ios": {
"bundleIdentifier": "com.example.myapp",
"supportsTablet": true
},
"android": {
"package": "com.example.myapp",
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png"
}
},
"plugins": [
"expo-router",
["expo-camera", { "cameraPermission": "Allow camera access" }],
["expo-notifications", {
"icon": "./assets/notification-icon.png",
"color": "#ffffff"
}]
]
}
}
One important nuance: if you previously had newArchEnabled set to false as an escape hatch on SDK 51, removing that flag is exactly the change that activates Fabric. The Expo docs recommend rebuilding from a clean prebuild rather than reusing cached native folders, because stale CMake and Pod artifacts are a common source of confusing build failures during this transition.
Fabric Renderer: Faster UI Updates
The Fabric renderer enables synchronous communication between JavaScript and native views, eliminating the async bridge overhead. Moreover, it unlocks concurrent rendering, which lets React prepare multiple versions of the UI in the background and commit whichever is ready, keeping the input thread responsive even under load.
// Concurrent features with Fabric renderer
import { useState, useTransition, Suspense } from 'react';
import { View, TextInput, FlatList, ActivityIndicator } from 'react-native';
function SearchableProductList() {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
const [results, setResults] = useState<Product[]>([]);
const handleSearch = (text: string) => {
setQuery(text);
// Non-urgent update — Fabric commits this without blocking keystrokes
startTransition(() => {
const filtered = filterProducts(text);
setResults(filtered);
});
};
return (
<View style={styles.container}>
<TextInput
value={query}
onChangeText={handleSearch}
placeholder="Search products..."
style={styles.searchInput}
/>
{isPending && <ActivityIndicator style={styles.loading} />}
<Suspense fallback={<ProductSkeleton />}>
<FlatList
data={results}
renderItem={({ item }) => <ProductCard product={item} />}
keyExtractor={(item) => item.id}
// Fabric improves large-list recycling
windowSize={5}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={50}
/>
</Suspense>
</View>
);
}
// Custom native component authored against the Fabric spec
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
import type { ViewProps } from 'react-native';
interface NativeChartProps extends ViewProps {
data: number[];
color: string;
animated: boolean;
}
// Codegen reads this spec and emits the Fabric component at build time
export default codegenNativeComponent<NativeChartProps>('ChartView');
The practical payoff of concurrent rendering is most visible in exactly the scenario above: a fast typist filtering a long list. On the old architecture, each keystroke could stall while the list re-rendered. With startTransition and Fabric, the text input update is treated as urgent while the list filtering is interruptible, so typing stays smooth even if the result set is large.
TurboModules: Synchronous Native Access
Where Fabric handles views, TurboModules handle everything else — biometrics, storage, device info, and any custom native capability. The key difference from the old bridge modules is that TurboModules are lazily loaded and can expose synchronous methods. As a result, a simple capability check no longer needs an await, a useEffect, and a loading state just to answer a yes-or-no question.
// TurboModule spec — synchronous native access where it makes sense
import { TurboModuleRegistry, type TurboModule } from 'react-native';
interface Spec extends TurboModule {
isAvailable(): boolean; // Synchronous
getEnrolledBiometrics(): string[]; // Synchronous
authenticate(reason: string): Promise<boolean>; // Still async
}
const BiometricModule =
TurboModuleRegistry.getEnforcing<Spec>('BiometricModule');
function BiometricGate({ children }: { children: React.ReactNode }) {
// No await, no loading state — resolved on the same tick
const isAvailable = BiometricModule.isAvailable();
const biometrics = BiometricModule.getEnrolledBiometrics();
if (!isAvailable) {
return <FallbackAuth />;
}
return (
<View>
<Text>Available: {biometrics.join(', ')}</Text>
{children}
</View>
);
}
A word of caution here, though: synchronous does not mean free. A synchronous TurboModule call blocks the JavaScript thread until native returns, so it is appropriate for cheap queries like capability checks but a poor fit for disk reads, network calls, or anything that can take more than a millisecond. The general rule the React Native team recommends is to keep synchronous methods for trivial getters and leave genuine work on Promises.
Expo Router with SDK 52
Additionally, Expo Router v4 in SDK 52 takes advantage of Fabric for faster navigation transitions and aligns its data-fetching story with React’s concurrent model. Routes remain file-based, but transitions feel noticeably crisper because the renderer can commit the incoming screen without waiting on a serialized handoff.
// app/(tabs)/_layout.tsx — tab navigation with platform-tuned animation
import { Tabs } from 'expo-router';
import { Platform } from 'react-native';
export default function TabLayout() {
return (
<Tabs
screenOptions={{
animation: Platform.select({ ios: 'shift', android: 'fade' }),
tabBarStyle: { backgroundColor: '#1a1a2e', borderTopColor: '#16213e' },
headerStyle: { backgroundColor: '#1a1a2e' },
headerTintColor: '#e94560',
}}
>
<Tabs.Screen name="index" options={{ title: 'Home', tabBarIcon: HomeIcon }} />
<Tabs.Screen name="explore" options={{ title: 'Explore', tabBarIcon: SearchIcon }} />
<Tabs.Screen name="profile" options={{ title: 'Profile', tabBarIcon: UserIcon }} />
</Tabs>
);
}
Verifying the New Architecture Is Actually Active
One question that trips up teams is whether the new architecture is truly running after an upgrade, since the app builds and launches either way. The most reliable check is a runtime assertion rather than a guess. In development you can confirm Fabric is mounted by inspecting the global flag the renderer sets, and you can lean on expo-doctor to flag any installed package that still depends on the deprecated interop layer.
// Confirm Fabric is the active renderer at runtime
import { Platform } from 'react-native';
// `__turboModuleProxy` and the Fabric UIManager flag are set only on the
// new architecture, so this is a dependable smoke test in a dev build.
const isNewArch =
global.__turboModuleProxy != null ||
// @ts-expect-error internal flag, present only with Fabric
global?.nativeFabricUIManager != null;
if (__DEV__) {
console.log(
`New architecture: ${isNewArch ? 'ACTIVE' : 'INACTIVE'} on ${Platform.OS}`
);
}
Performance Results
Performance gains vary by app, but the direction is consistent across community benchmarks: faster cold starts, lower memory at idle, and steadier frame rates on long lists. The numbers below are representative figures published in migration write-ups and the React Native team’s own benchmarks, not measurements from any single device fleet. Treat them as a guide to the shape of the improvement, then profile your own build.
Representative SDK 52 vs SDK 51 deltas (community benchmarks, mid-range Android)
┌─────────────────────┬──────────┬──────────┬──────────┐
│ Metric │ SDK 51 │ SDK 52 │ Change │
├─────────────────────┼──────────┼──────────┼──────────┤
│ Cold start │ ~1.8s │ ~1.1s │ ~-39% │
│ Time to interactive │ ~2.4s │ ~1.5s │ ~-38% │
│ List scroll FPS │ ~52 fps │ ~59 fps │ ~+13% │
│ Navigation (avg) │ ~180ms │ ~95ms │ ~-47% │
│ Memory (idle) │ ~145 MB │ ~118 MB │ ~-19% │
└─────────────────────┴──────────┴──────────┴──────────┘
When NOT to Use Expo SDK 52: Trade-offs
The honest answer is that SDK 52 is not a free upgrade for every project, and the deciding factor is almost always your native dependency graph. Because the interop layer that previously let old-architecture modules run under Fabric is deprecated and on its way out, any native module that has not shipped a Fabric/TurboModule version becomes a liability. In practice, the libraries to audit first are map renderers, PDF viewers, advanced camera or video modules, and anything with a custom native UI component.
Therefore, before committing, run npx expo-doctor and check the React Native Directory for each native dependency’s new-architecture support status. If a critical library is not ready, you have three options: pin to SDK 51 until it updates, fork and patch the library to the Fabric spec yourself, or replace it with a maintained alternative. None of these is trivial, so the trade-off is real — the gains are meaningful, but the migration cost scales directly with how much custom native code you depend on. Conversely, a pure JavaScript app with only first-party Expo modules will sail through, and for that profile there is little reason to wait.
Key Takeaways
React Native Expo SDK 52 delivers the new architecture to every Expo developer with effectively zero native configuration. You can expect meaningfully faster startup, smoother animations, and synchronous native module access where it counts. Migrate by running expo install --fix, regenerate native code with a clean prebuild, verify compatibility with expo-doctor, and then exercise concurrent rendering features that unlock React 18’s full potential on mobile. The new architecture is no longer optional; it is the foundation for React Native’s future.
- Build on JSI: Fabric and TurboModules both replace the serialized bridge with shared memory.
- Use
startTransitionfor non-urgent updates so input stays responsive under load. - Keep synchronous TurboModule methods for trivial getters; leave real work on Promises.
- Audit every native dependency for new-architecture support before upgrading.
- Always rebuild from a clean prebuild to avoid stale native artifacts.
For more mobile development topics, explore our guides on React Native performance optimization, mobile app architecture patterns, and React Native vs Flutter. The Expo new architecture guide and React Native new architecture documentation provide comprehensive migration references.
In conclusion, React Native Expo SDK 52 is an essential milestone for modern mobile development. By understanding the JSI foundation, migrating deliberately, and auditing your native dependencies before you upgrade, you can ship a faster, more responsive app while sidestepping the pitfalls that catch unprepared teams. Start with the fundamentals, validate the new architecture is genuinely active, and measure on real devices to confirm you are getting the value this release promises.