Expo

Expo React Native Web 사용법: 앱을 웹으로 확장하기

2026.04.22·수정 2026.05.12·약 16분

이 글에서 정리하는 내용

react-native-web이 무엇인지, React Native와 React DOM 사이에서 어떤 역할을 하는지, 기본 컴포넌트와 스타일 작성 방식, 웹 전용 분기 방법, Expo와 함께 웹까지 확장하는 흐름을 한 번에 정리합니다. 끝까지 읽으면 어떤 코드는 공용으로 가져가고 어떤 부분은 웹 전용으로 분리해야 하는지 판단 기준도 함께 잡을 수 있습니다.

react-native-web이 무엇인지 먼저 정리하기

React Native Web 사용법: Expo 앱을 웹으로 확장하는 기준 핵심 개념을 설명하는 첫 번째 본문 이미지

react-native-web은 React Native에서 쓰는 컴포넌트와 일부 API를 웹에서도 사용할 수 있게 이어주는 호환 계층입니다. 그래서 이름만 보면 웹뷰처럼 느껴질 수 있지만, 실제로는 브라우저 안에서 React DOM과 연결되어 동작하는 방식에 가깝습니다. 즉 앱 화면을 웹페이지 안에 띄우는 개념이 아니라, React Native 문법으로 작성한 UI를 웹에서도 최대한 비슷한 방식으로 렌더링하도록 돕는 역할이라고 이해하면 흐름이 훨씬 편해집니다.

이 개념을 먼저 잡아두면 왜 웹에서도 div 대신 View, p 대신 Text 같은 형태를 쓰는지 자연스럽게 연결됩니다. 공용 코드 재사용이 목표일 때는 React Native 컴포넌트를 기준으로 화면을 짜는 편이 유리하고, 웹 전용 기능이 많을수록 React DOM 요소를 직접 쓰는 편이 더 단순할 수 있습니다. 공식 문서도 react-native-web을 웹 전용 앱과 멀티플랫폼 앱 모두에서 쓸 수 있는 호환 계층으로 설명하고 있으므로, 반드시 앱 코드가 있어야만 도입할 수 있는 기술이라고 볼 필요는 없습니다. 결국 react-native-web의 핵심은 웹만을 위한 대체 문법이 아니라, 여러 플랫폼에서 같은 구조를 최대한 유지하려는 선택지라는 점입니다.

React DOM과의 관계를 가장 짧게 보면

// React Native 방식으로 컴포넌트를 가져옵니다.
import { View, Text } from 'react-native'; // react-native-web 환경에서는 아래 JSX가 웹에서도 렌더링됩니다.
export default function IntroCard() { return ( <View> <Text>React Native 문법으로 작성한 화면</Text> </View> );
}

이 예시는 문법만 보면 모바일 앱 코드처럼 보이지만, 웹 환경에서는 내부적으로 웹이 이해할 수 있는 형태로 연결되어 렌더링됩니다. 그래서 공용 컴포넌트를 만들 때는 먼저 React Native 기준으로 생각하고, 정말 웹 전용이어야 하는 부분만 나중에 분리하는 흐름이 실무에서 자주 쓰입니다.

기본 컴포넌트와 스타일 작성 방식 이해하기

react-native-web을 사용할 때 가장 먼저 익숙해져야 하는 부분은 HTML 태그 중심 사고에서 React Native 컴포넌트 중심 사고로 옮겨가는 것입니다. 웹에서는 보통 div, span, button, img 같은 태그를 직접 떠올리지만, 여기서는 View, Text, Pressable, Image 같은 공용 컴포넌트를 먼저 사용합니다. 덕분에 모바일과 웹에서 비슷한 구조를 유지하기 쉬워집니다.

스타일 방식도 다릅니다. CSS 파일에 클래스를 만들고 을 붙이는 흐름이 아니라, style prop에 자바스크립트 객체를 넣거나 StyleSheet.create로 스타일을 등록하는 방식이 기본입니다. 속성 이름은 익숙한 편이지만 작성 규칙과 합성 순서는 CSS와 완전히 같지 않으므로, 처음에는 “CSS를 자바스크립트 객체로 옮겼다” 정도로 이해하고 시작하는 편이 좋습니다. 공식 스타일링 문서 기준으로 react-native-web은 스타일을 자바스크립트로 작성한 뒤 최적화된 CSS로 변환하며, long-form CSS 속성은 폭넓게 지원하지만 @ 규칙, 셀렉터가상 선택자가상 요소를 직접 쓰는 방식은 기본 패턴이 아닙니다.

기본 화면 구성 예시

// 공용 화면 컴포넌트를 작성하는 예시입니다.
import { Pressable, StyleSheet, Text, View } from 'react-native'; export default function HomeScreen() { return ( <View style={styles.container}> <Text style={styles.title}>React Native Web</Text> <Text style={styles.description}> 같은 코드 구조를 웹과 앱에서 함께 가져갈 수 있습니다. </Text> <Pressable style={styles.button}> <Text style={styles.buttonText}>버튼 눌러보기</Text> </Pressable> </View> );
} const styles = StyleSheet.create({ container: { flex: 1, padding: 24, justifyContent: 'center'}, title: { fontSize: 24, fontWeight: '700', marginBottom: 12}, description: { fontSize: 16, lineHeight: 24, marginBottom: 20}, button: { paddingVertical: 14, paddingHorizontal: 16, borderRadius: 12, backgroundColor: '#222'}, buttonText: { color: '#fff', textAlign: 'center'}});

이 코드에서 중요한 점은 HTML 태그가 거의 보이지 않는다는 것입니다. 화면 구조도, 텍스트도, 버튼도 모두 React Native 쪽 컴포넌트로 구성되어 있습니다. 이렇게 작성하면 같은 코드베이스를 iOS, Android, 웹에서 함께 가져가기 쉬워집니다.

style 합성은 배열로 자주 처리합니다

// 조건에 따라 스타일을 덮어쓰는 예시입니다.
import { StyleSheet, Text, View } from 'react-native'; export default function StatusBadge({ active }: { active: boolean }) { return ( <View style={[styles.badge, active && styles.activeBadge]}> <Text style={[styles.label, active && styles.activeLabel]}> {active ? '활성 상태' : '기본 상태'} </Text> </View> );
} const styles = StyleSheet.create({ badge: { paddingHorizontal: 12, paddingVertical: 8, borderRadius: 999, backgroundColor: '#eee'}, activeBadge: { backgroundColor: '#222'}, label: { color: '#333'}, activeLabel: { color: '#fff'}});

CSS에서는 클래스 조합으로 푸는 문제를 여기서는 배열 합성으로 해결하는 경우가 많습니다. 왼쪽에서 오른쪽 순서로 스타일이 합쳐진다고 생각하면 읽기가 쉽습니다. 이 방식에 익숙해지면 버튼 상태, 선택 상태에러 상태 같은 UI를 깔끔하게 나누기 좋아집니다.

비교 항목 react-native-web에서 보는 방식
기본 구조 div 대신 View, 텍스트는 Text 안에서 관리
스타일 작성 보다 style 객체와 StyleSheet.create 사용이 중심

웹에서 부딪히는 차이점과 분기 방법 보기

// 작은 차이는 Platform 분기로 처리할 수 있습니다.
import { Platform, StyleSheet, View } from 'react-native'; export default function HeroSection() { return <View style={styles.box} />;
} const styles = StyleSheet.create({ box: { height: Platform.OS === 'web' ? 320 : 260, width: '100%'}});

react-native-web을 쓴다고 해서 모든 기능이 완전히 똑같이 보장되는 것은 아닙니다. 공식 호환성 문서도 React Native의 자바스크립트 API 대부분과 호환된다고 설명하지만, 일부 컴포넌트는 제약이 남아 있습니다. 예를 들어 와 StatusBar는 웹에서 사실상 mock 성격에 가깝고, RefreshControl은 아직 시작되지 않은 항목으로 안내됩니다. 작은 차이는 Platform.OS 분기로 해결할 수 있습니다. 예를 들어 웹에서는 더 넓은 레이아웃이 필요하거나, hover처럼 웹다운 상호작용을 더 고려해야 하는 경우가 있습니다. 이런 정도의 차이라면 굳이 파일을 나누지 않고 한 컴포넌트 안에서 처리해도 됩니다.

반대로 차이가 더 커지면 아예 플랫폼별 파일을 나누는 편이 읽기 쉽습니다. 예를 들어 웹에서는 표, 긴 폼, SEO용 구조, 데스크톱 전용 상호작용이 중요하고, 모바일 앱에서는 터치와 네이티브 UX가 더 중요할 수 있습니다. 이런 경우는 억지로 한 파일에 다 넣기보다 .web.tsx 같은 파일로 분리하는 것이 유지보수에 유리합니다. 또한 컴포넌트뿐 아니라 사용하는 라이브러리 자체가 웹을 지원하는지도 함께 확인해야 합니다. React Native Web 문서는 서드파티 패키지의 웹 지원 여부를 React Native Directory에서 먼저 확인하는 방향을 안내합니다.

플랫폼 전용 파일을 나누는 예시

// 같은 import 문을 사용해도 플랫폼에 따라 다른 파일을 불러옵니다.
// ProfileCard.tsx 파일 안에서 직접 분기하지 않아도 됩니다. // ProfileCard.web.tsx
export default function ProfileCard() { return <div>웹 전용 카드 구조</div>;
} // ProfileCard.tsx 또는 ProfileCard.native.tsx
import { Text, View } from 'react-native'; export default function ProfileCard() { return ( <View> <Text>앱 전용 카드 구조</Text> </View> );
}

실무에서는 여기서 한 가지를 더 기억하면 좋습니다. react-native-web은 공용화 비율을 높여주는 도구이지, 무조건 모든 파일을 하나로 합쳐야 한다는 뜻은 아닙니다. 공용 컴포넌트는 최대한 공용으로 두고, 플랫폼 감성이 강한 화면은 필요한 만큼만 나누는 쪽이 더 안정적입니다.

Expo와 함께 웹까지 확장하는 흐름

React Native Web 사용법: Expo 앱을 웹으로 확장하는 기준 적용 흐름을 설명하는 두 번째 본문 이미지

기존 React Native 프로젝트에 웹을 붙이는 방법 자체는 여러 가지가 있지만, 실무에서는 Expo를 함께 쓰는 흐름이 훨씬 단순합니다. React Native Web의 멀티플랫폼 문서도 기존 React Native 코드베이스에 웹을 붙일 때 Expo 사용을 강하게 권장합니다. 웹 관련 설정을 직접 많이 만지지 않아도 되고, 개발 서버 실행, 번들링, 웹 빌드까지 비교적 한 방향으로 가져갈 수 있기 때문입니다. 특히 여러 플랫폼을 함께 다루는 프로젝트라면 웹 설정을 직접 구성하는 부담을 줄여주는 장점이 큽니다.

또한 Expo 쪽 웹 문서는 react-native-web을 공용화 수단으로 활용하면서도, 필요하면 웹 전용 React DOM 컴포넌트를 섞어 쓰는 방식까지 열어두고 있습니다. 즉 react-native-web만 써야 한다는 뜻이 아니라, 공용화가 유리한 부분은 RNW를 쓰고 웹 고유의 요구가 강한 부분은 웹 전용 구현을 선택하는 방식이 가능합니다.

Expo에서 웹 실행과 빌드 흐름

# 웹 실행에 필요한 패키지를 설치합니다.
npx expo install react-dom react-native-web @expo/metro-runtime # 개발 중 브라우저에서 바로 실행합니다.
npx expo start --web # 웹 결과물을 내보낼 때 사용합니다.
npx expo export --platform web

이 흐름은 “앱 프로젝트인데 웹도 열어볼 수 있다” 수준을 넘어서, 실제로 웹까지 함께 운영하는 출발점이 됩니다. Expo 웹 문서 기준으로 웹은 정적 렌더링과 클라이언트 렌더링을 모두 지원하며, Expo Router에서는 SEO가 필요할 때 static rendering을 활성화하라고 안내합니다. 이때 app.json의 expo.web.output 값을 static으로 두고, 빌드 결과는 expo export –platform web 명령으로 dist 디렉터리에 내보내는 흐름을 사용합니다. 다만 SEO가 아주 중요한 마케팅 페이지나 웹 전용 접근성이 핵심인 화면은 공용 코드와 웹 전용 코드를 어떻게 나눌지 처음부터 기준을 잡는 편이 좋습니다.

언제 react-native-web이 특히 잘 맞는가

// 공용 레이아웃과 공용 컴포넌트가 많은 경우에 특히 잘 맞습니다.
import { Image, Text, View } from 'react-native'; export default function ProductCard() { return ( <View> <Image source={{ uri: 'https://example.com/card.png' }} style={{ width: 120, height: 120 }} /> <Text>여러 플랫폼에서 재사용할 카드 컴포넌트</Text> </View> );
}

상품 카드, 프로필 카드, 피드 아이템, 설정 화면처럼 구조가 크게 다르지 않은 UI는 react-native-web의 장점을 체감하기 좋습니다. 반대로 복잡한 문서 레이아웃, 웹 전용 폼 UX, DOM 제어가 많은 화면은 웹 전용 설계가 더 나을 수 있습니다. 그래서 도입 판단은 기술 유행보다 화면 성격을 기준으로 보는 편이 정확합니다.

정리

react-native-web은 React Native 코드를 웹에서도 최대한 재사용할 수 있게 해 주는 호환 레이어입니다. 핵심은 “웹도 앱처럼 만든다”가 아니라, 여러 플랫폼이 공유할 수 있는 UI 기준점을 React Native 컴포넌트로 맞춘다는 데 있습니다.

실무에서는 모든 코드를 억지로 하나로 합치기보다, 공용 컴포넌트는 RNW 기준으로 정리하고 웹 차이가 커지는 지점만 Platform 분기나 .web.tsx 파일로 나누는 방식이 가장 현실적입니다. Expo와 함께 쓰면 웹 실행과 배포 흐름도 훨씬 단순해지므로, 모바일 앱과 웹을 함께 운영하려는 프로젝트라면 충분히 검토할 가치가 있습니다.

많이 받는 질문

Q. react-native-web은 웹뷰와 같은 개념인가요?
아닙니다. 웹페이지를 앱 안에 띄우는 WebView와 다르고, React Native 컴포넌트와 API 일부를 웹에서도 사용할 수 있게 연결하는 호환 계층에 가깝습니다.

Q. 웹에서는 div나 p를 아예 못 쓰나요?
그렇지는 않습니다. 공용 코드 재사용을 늘리려면 View와 Text 같은 React Native 컴포넌트를 주로 쓰는 편이 좋지만, 필요하면 웹 전용 React DOM 요소를 함께 사용하는 것도 가능합니다.

Q. 모든 React Native 라이브러리가 웹에서도 그대로 되나요?
항상 그렇지는 않습니다. 라이브러리마다 웹 지원 범위가 다르므로, 공용 프로젝트를 잡을 때는 사용 라이브러리의 웹 지원 여부를 먼저 확인하는 편이 안전합니다.

같이 읽으면 좋은 글

이 글이 마음에 드세요?

RSS 피드를 구독하세요!

댓글 남기기