d8a80f6272
이름만 다른(표기 변형) [[위키링크]]를 대상 문서의 canonical 제목으로 치환해 끊겼던 1,200개 링크를 연결. 제목/파일명 정규화 일치만 적용하고 별칭 매칭은 과병합 위험으로 제외(애매성 가드). 원본은 _link_reconcile_backup/ 에 백업. 도구: Datacollect/scripts/link_reconcile_apply.mjs Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.9 KiB
5.9 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 |
|
none | A | 0.85 | applied |
|
2026-05-10 | pending |
|
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.
매 응용
- Expo Router universal app — iOS + Android + Web (single team).
- Internal tool / admin — RN component library 재사용.
- Linear/Notion-style desktop app — RNW + Tauri shell.
- 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
- 부모: 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.tsxsplit. - 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 패턴 |