Scaling React Applications at Meta: Lessons from Reality Labs
During my time at Meta’s Reality Labs Research, I worked on architecting full-stack applications that needed to scale to millions of users. Here are the key lessons I learned.
The Meta Stack
Meta’s web applications use a unique but powerful stack:
- React: Component-based UI
- Relay: GraphQL client with automatic data fetching
- Hack/PHP: Backend services (yes, really!)
- Python: ML pipelines and data processing
- Rust/Thrift: High-performance microservices
Key Learnings
1. Colocate Data Requirements
One of Relay’s superpowers is colocating data requirements with components:
function ProfileCard() {
const data = useFragment(
graphql`
fragment ProfileCard_user on User {
name
avatar
bio
}
`,
userRef,
);
return <div>{/* render with data */}</div>;
}
Benefits:
- Components declare exactly what data they need
- No over-fetching or under-fetching
- Automatic request deduplication
- Easy refactoring (delete component = remove unused queries)
2. Incremental Loading for Large Datasets
For annotation platform with 10K+ items:
const { loadNext, hasNext } = usePaginationFragment(
graphql`
fragment List_data on Query {
items(first: $count, after: $cursor) @connection(key: "List_items") {
edges {
node {
id
...ItemCard_item
}
}
}
}
`,
data,
);
This pattern:
- Loads data incrementally as user scrolls
- Maintains connection state automatically
- Works seamlessly with server pagination
3. Optimistic Updates for Better UX
When users interact with the app, show changes immediately:
function likePost(postId) {
commitMutation(environment, {
mutation: graphql`mutation LikePost($id: ID!) { ... }`,
variables: { id: postId },
optimisticResponse: {
likePost: {
id: postId,
isLiked: true,
likeCount: post.likeCount + 1,
},
},
});
}
The UI updates instantly, then reconciles with server response.
4. Monorepo Benefits
Meta uses a massive monorepo with:
- Buck2: Fast, scalable build system
- Sapling: Version control optimized for monorepos
- Shared Components: Reuse across teams
Advantages:
- Atomic changes across services
- Easy dependency management
- Consistent tooling and practices
5. Mobile Integration
Integrated React web apps with native Android:
- React Native Web: Shared components between web and mobile
- JNI Bridges: Call into C++ from Kotlin
- Proto/Thrift: Consistent data models
Performance at Scale
Key metrics we tracked:
- Time to Interactive (TTI): < 2s on 3G
- First Contentful Paint (FCP): < 1s
- Bundle Size: < 200KB initial JS (code-split!)
Techniques:
- Route-based code splitting
- Image optimization (WebP, lazy loading)
- Relay store normalization (no duplicate data)
- Service workers for offline support
Testing Strategy
// Component tests with Relay Mock Environment
const environment = createMockEnvironment();
test('renders user profile', () => {
render(<Profile />, { relayEnvironment: environment });
expect(screen.getByText('Loading...')).toBeInTheDocument();
act(() => {
environment.mock.resolveMostRecentOperation({
data: { user: { name: 'Alice', avatar: '...' } },
});
});
expect(screen.getByText('Alice')).toBeInTheDocument();
});
Layers:
- Unit: Component behavior
- Integration: Relay + GraphQL queries
- E2E: Full user flows with Playwright
Takeaways
Building at Meta taught me:
- Data-driven UI: Colocate data requirements
- Performance Matters: Every millisecond counts at scale
- Incremental Loading: Essential for large datasets
- Optimistic UI: Make interactions feel instant
- Monorepos Work: When tooling is built for it
These patterns aren’t Meta-specific—you can apply them to any React application that needs to scale.
Have questions about scaling React or working with Relay? Let’s chat!