Files
2nd/10_Wiki/Topics/Frontend/React_Native_Web_-_Desktop.md
T
2026-05-10 22:08:15 +09:00

6.0 KiB

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

// 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)

// 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)

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)

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)

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)

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)

// 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

🤖 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 패턴