---
id: wiki-2026-0508-react-native-web-desktop
title: React Native Web — Desktop
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [React Native Web Desktop, RNW Desktop, Universal RN]
duplicate_of: none
source_trust_level: A
confidence_score: 0.85
verification_status: applied
tags: [react-native, react-native-web, desktop, cross-platform, frontend]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
  language: TypeScript
  framework: React Native Web
---

# React Native Web — Desktop

## 매 한 줄
> **"매 한 codebase 의 mobile + desktop"**. React Native Web (RNW) 매 RN component 를 DOM 으로 render — Expo Web 또는 standalone Vite/Next.js 셋업으로 매 desktop browser target. 2026 기준 Expo Router v4 + RN 0.76 New Architecture 매 stable, Bridgeless 매 web 의 hot reload 빠름. Tamagui / NativeWind 매 styling 의 cross-platform.

## 매 핵심

### 매 왜 desktop 의 RNW
- 매 mobile-first 의 enterprise dashboard / electron 대체.
- 매 single team / single component library.
- Server-side render (Next.js + RNW 0.19+) 매 SEO 가능.
- Tablet / iPad split-view 의 fluid layout.

### 매 vs alternatives
- **Electron**: 매 heavy bundle, native shell, 매 different mental model.
- **Tauri**: 매 Rust + WebView — 매 React but 매 RN component X.
- **PWA + responsive**: 매 web-first, mobile compromise.
- **RNW**: 매 mobile-first, desktop adapt.

### 매 응용
1. Expo Router universal app — iOS + Android + Web (single team).
2. Internal tool / admin — RN component library 재사용.
3. Linear/Notion-style desktop app — RNW + Tauri shell.
4. Marketing site + app — same brand component.

## 💻 패턴

### Expo Router universal entry
```tsx
// app/_layout.tsx
import { Stack } from "expo-router";
import { useDeviceOrientation } from "@react-native-community/hooks";

export default function Layout() {
  return (
    <Stack
      screenOptions={{
        headerShown: true,
        contentStyle: { maxWidth: 1280, alignSelf: "center", width: "100%" },
      }}
    />
  );
}
```

### Platform-specific file (`.web.tsx`)
```tsx
// FilePicker.tsx        (mobile)
// FilePicker.web.tsx    (desktop)

// FilePicker.web.tsx
import { useRef } from "react";
import { Pressable, Text } from "react-native";

export function FilePicker({ onPick }: { onPick: (f: File) => void }) {
  const inputRef = useRef<HTMLInputElement>(null);
  return (
    <>
      <Pressable onPress={() => inputRef.current?.click()}>
        <Text>Pick file</Text>
      </Pressable>
      <input
        ref={inputRef}
        type="file"
        style={{ display: "none" }}
        onChange={(e) => e.target.files?.[0] && onPick(e.target.files[0])}
      />
    </>
  );
}
```

### Responsive layout (useWindowDimensions)
```tsx
import { View, useWindowDimensions } from "react-native";

export function Layout({ children }: { children: React.ReactNode }) {
  const { width } = useWindowDimensions();
  const isDesktop = width >= 1024;
  return (
    <View style={{ flexDirection: isDesktop ? "row" : "column", flex: 1 }}>
      {isDesktop && <Sidebar />}
      <View style={{ flex: 1 }}>{children}</View>
    </View>
  );
}
```

### Keyboard shortcuts (web only)
```tsx
import { useEffect } from "react";
import { Platform } from "react-native";

export function useShortcut(key: string, handler: () => void) {
  useEffect(() => {
    if (Platform.OS !== "web") return;
    const fn = (e: KeyboardEvent) => {
      if ((e.metaKey || e.ctrlKey) && e.key === key) {
        e.preventDefault();
        handler();
      }
    };
    window.addEventListener("keydown", fn);
    return () => window.removeEventListener("keydown", fn);
  }, [key, handler]);
}
```

### Hover state (`Pressable` web-only)
```tsx
import { Pressable, Text } from "react-native";

<Pressable
  style={({ hovered, pressed }: any) => ({
    backgroundColor: pressed ? "#e0e0e0" : hovered ? "#f0f0f0" : "white",
    padding: 12,
    borderRadius: 8,
  })}
>
  {() => <Text>Hover me (web)</Text>}
</Pressable>
```

### Tamagui styling (cross-platform)
```tsx
import { Button, Stack, Text } from "tamagui";

export function Card() {
  return (
    <Stack padding="$4" borderRadius="$4" backgroundColor="$background" hoverStyle={{ backgroundColor: "$backgroundHover" }}>
      <Text fontSize="$6">Title</Text>
      <Button theme="active">Action</Button>
    </Stack>
  );
}
```

### Next.js + RNW (SSR setup)
```js
// next.config.js
const { withExpo } = require("@expo/next-adapter");
module.exports = withExpo({
  transpilePackages: ["react-native", "react-native-web", "expo"],
  experimental: { forceSwcTransforms: true },
});
```

## 매 결정 기준
| 상황 | Approach |
|---|---|
| Mobile + desktop, same product | Expo Router universal |
| Native desktop window/menu | Electron / Tauri (RNW inside Tauri OK) |
| Heavy animations | Reanimated 4 (web target supported) |
| Styling | Tamagui / NativeWind |
| SSR/SEO | Next.js + RNW |
| Mobile only | plain RN (no RNW) |

**기본값**: Expo Router + Tamagui — 매 mobile-first, desktop 매 responsive breakpoint.

## 🔗 Graph
- 부모: [[React Native]]
- 변형: [[Expo Router]]
- 응용: [[Universal-App]]
- Adjacent: [[Tauri]] · [[Electron]]

## 🤖 LLM 활용
**언제**: mobile-first product 의 desktop site, internal tool with RN library, universal team.
**언제 X**: desktop-only 의 native window/menu/system tray — 매 Electron/Tauri.

## ❌ 안티패턴
- **`document.getElementById` 직접 사용**: 매 mobile crash. Platform.OS gate 필수.
- **Mobile-only 라이브러리 import**: 매 web bundle 의 fail. `.web.tsx` split.
- **Fixed pixel layout**: 매 desktop wide screen 매 broken. Flex / max-width.
- **No keyboard navigation**: 매 desktop a11y 폭망.

## 🧪 검증 / 중복
- Verified (Expo SDK 51+ docs, react-native-web 0.19+ docs, Tamagui docs).
- 신뢰도 A.

## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — Expo Router, .web.tsx, hover/shortcut, Tamagui, Next adapter 패턴 |
