posts

๐Ÿ“ฑ RN + Expo ์•ฑ ๊ตฌ์ถ• ์ด์ฒด์  ๊ณ ๋ ค์‚ฌํ•ญ

Oct 1, 2025 updated Oct 1, 2025 architectureexporeact-nativetypescript

0) ์ƒ์œ„ ์˜์‚ฌ๊ฒฐ์ • ํŠธ๋ฆฌ

์ฃผ์ œ ๊ฒฐ์ • ํฌ์ธํŠธ ๊ถŒ์žฅ์•ˆ ๋ฆฌ์Šคํฌ/๋Œ€์•ˆ
ํ”„๋กœ์ ํŠธ ํƒ€์ž… Expo Managed vs Dev Client vs Bare Managed๋กœ ์‹œ์ž‘ โ†’ ๋„ค์ดํ‹ฐ๋ธŒ ์š”๊ตฌ ์ƒ๊ธฐ๋ฉด Dev Client TLS Pinning/๊ณ ๊ธ‰ BLE/๋ฐฑ๊ทธ๋ผ์šด๋“œ ์—…๋กœ๋“œ ๊ฐ•ํ•˜๋ฉด Bare ๊ณ ๋ ค
๋ผ์šฐํŒ… React Navigation vs Expo Router React Navigation (์•ˆ์ •/ํ™•์žฅ) ํŒŒ์ผ ๊ธฐ๋ฐ˜ ์›ํ•˜๋ฉด Expo Router(+Navigation)
๋””์ž์ธ ์‹œ์Šคํ…œ ํ† ํฐ/Variant/์ ‘๊ทผ์„ฑ ์ „๋žต Restyle + RN Aria (gluestack ์ผ๋ถ€ ๋ณ‘ํ–‰ ๊ฐ€๋Šฅ) ํ’€ํ‚ท(Paper/Elements) ์˜์กด ์ปค์ง€๋ฉด ์ปค์Šคํ…€ ๋‚œ์ด๋„โ†‘
์ƒํƒœ์ „๋žต ๋กœ์ปฌ/์„œ๋ฒ„ ์ƒํƒœ ๋ถ„๋ฆฌ Zustand(Local) + React Query(Server) ์ „์—ญ ๋‚จ์šฉ ์ง€์–‘, ํ™”๋ฉด ๋‹จ์œ„ co-locate
๋ฐฐํฌ์ „๋žต OTA/์Šคํ† ์–ด ๋ฆด๋ฆฌ์Šค EAS Update + EAS Build/Submit ๋„ค์ดํ‹ฐ๋ธŒ ๋ณ€๊ฒฝ์€ ์ƒˆ ๋นŒ๋“œ ํ•„์š”
ํ™˜๊ฒฝ๋ถ„๋ฆฌ dev/stage/prod app.config.ts + EAS Secrets .env ์ง์ ‘ ์ฃผ์ž…/์œ ์ถœ ๊ธˆ์ง€

1) ๊ตฌ์กฐ/์•„ํ‚คํ…์ฒ˜

  • ํด๋” ๊ตฌ์กฐ(์˜ˆ: FSD or feature-first)
    • app/(screens, navigation), entities/, features/, shared/(ui, lib, api), processes/(์„ธ์…˜ ๋“ฑ)
    • shared/ui: Box/Text/Button ๋ฒ ์ด์Šค ์ปดํฌ๋„ŒํŠธ(ํ† ํฐ/Variant ๋ฐ˜์˜)
  • ๊ฒฝ๊ณ„ ๋ถ„๋ฆฌ
    • Server state(React Query) vs UI/Session state(Zustand) ๋ช…ํ™• ๋ถ„๋ฆฌ
    • API ๋ชจ๋“ˆ: fetcher(axios) + ์ธํ„ฐ์…‰ํ„ฐ(ํ† ํฐ/๋ฆฌํ”„๋ ˆ์‹œ)
  • ์˜์กด์„ฑ ๊ทœ์น™
    • ์ƒ์œ„ โ†’ ํ•˜์œ„ ๋ฐฉํ–ฅ(์ˆœํ™˜ ๊ธˆ์ง€), public API๋งŒ import

2) ์ ‘๊ทผ์„ฑ(A11y) ์ •์ฑ…

  • ํ„ฐ์น˜ ํƒ€๊นƒ 44dp ์ด์ƒ, ์ฝ˜ํŠธ๋ผ์ŠคํŠธ 4.5:1 ์ค€์ˆ˜
  • ๋ชจ๋“  ํ„ฐ์น˜ ์š”์†Œ์— accessibilityRole/Label/Hint ์ง€์ •
  • ํฌ์ปค์Šค ๊ด€๋ฆฌ: ๋ชจ๋‹ฌ/๋ฐ”ํ…€์‹œํŠธ ์˜คํ”ˆ ์‹œ ์ดˆ์  ์ด๋™/ํŠธ๋žฉ
  • ์Šคํฌ๋ฆฐ๋ฆฌ๋”(VoiceOver/TalkBack) ์ˆ˜๋™ ํ…Œ์ŠคํŠธ ํ”Œ๋กœ์šฐ ๋ฌธ์„œํ™”
  • ์ œ์Šค์ฒ˜๋งŒ์œผ๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š๋„๋ก ๋Œ€์ฒด ์กฐ์ž‘ ์ œ๊ณต
  • ํ•ต์‹ฌ ์ปดํฌ๋„ŒํŠธ์— RN Aria ํ›… ์šฐ์„  ์ ์šฉ (button, checkbox, slider ๋“ฑ)

3) ์„ฑ๋Šฅ/UX ๊ฐ€์ด๋“œ

  • ๋ฆฌ์ŠคํŠธ๋Š” ๊ธฐ๋ณธ FlatList โ†’ ์Šค์ผ€์ผ๋˜๋ฉด FlashList๋กœ ์ „ํ™˜
  • ์ด๋ฏธ์ง€: WebP/AVIF ์šฐ์„ , ์บ์‹œ(expo-file-system) + ํ”„๋ฆฌ๋กœ๋“œ ์ „๋žต
  • ์• ๋‹ˆ๋ฉ”์ด์…˜: Reanimated + Moti, FPS ๋“œ๋ž ๊ตฌ๊ฐ„(์Šคํฌ๋กค/์‹œํŠธ) ์šฐ์„  ์ตœ์ ํ™”
  • ํผํฌ๋จผ์Šค ์˜ˆ์‚ฐ ์ •์˜(์ฒซ ๋ Œ๋” < 1.5s, ๋ฉ”๋ชจ๋ฆฌ, JS bundle ํฌ๊ธฐ)
  • ์ธํ„ฐ๋ž™์…˜ ๋ ˆ์ดํ„ด์‹œ: ์ œ์Šค์ฒ˜-์ค‘์‹ฌ ํ™”๋ฉด(๋ฐ”ํ…€์‹œํŠธ/์บ๋Ÿฌ์…€)์—์„œ 16ms ํƒ€๊นƒ

4) ๋„คํŠธ์›Œํ‚น/์˜คํ”„๋ผ์ธ

  • Axios ์ธํ„ฐ์…‰ํ„ฐ: ํ† ํฐ ์ฃผ์ž…/401 ๋ฆฌํ”„๋ ˆ์‹œ/์žฌ์‹œ๋„(์ง€์ˆ˜๋ฐฑ์˜คํ”„)
  • React Query:
    • ์บ์‹œ ํƒ€์ž„/์Šคํ…Œ์ผ ํƒ€์ž„ ์ •์ฑ…, ์—๋Ÿฌ ๊ฒฝ๊ณ„ ์ฒ˜๋ฆฌ(ํ† ์ŠคํŠธ/๋‹ค์ด์–ผ๋กœ๊ทธ)
    • Persist: react-query-persist-client + AsyncStorage (์˜คํ”„๋ผ์ธ ์ฝ๊ธฐ)
  • ์˜คํ”„๋ผ์ธ ์ „๋žต: ์ฝ๊ธฐ ์บ์‹œ + ์“ฐ๊ธฐ ํ(๋„คํŠธ์›Œํฌ ๋ณต๊ตฌ ์‹œ ๋™๊ธฐํ™”)
  • ๋ฐ์ดํ„ฐ ๋ณดํ˜ธ: ๋ฏผ๊ฐ์ •๋ณด๋Š” SecureStore, ์ผ๋ฐ˜์€ AsyncStorage

5) ์•Œ๋ฆผ/๋งํฌ/๋”ฅ๋งํฌ

  • ํ‘ธ์‹œ ์•Œ๋ฆผ: expo-notifications
    • Android ์ฑ„๋„ ์‚ฌ์ „ ์ƒ์„ฑ(์ค‘์š”๋„, ์‚ฌ์šด๋“œ), ์นดํ…Œ๊ณ ๋ฆฌ(์•ก์…˜ ๋ฒ„ํŠผ) ์„ค๊ณ„
    • ํ† ํฐ ์ˆ˜์ง‘/ํ•ด์ง€/ํ† ํ”ฝ ๊ตฌ๋… ๋ฐฑ์—”๋“œ ์ŠคํŽ™ ๋ช…์„ธ
  • ๋กœ์ปฌ ์•Œ๋ฆผ: ์Šค์ผ€์ค„/๋ฐ˜๋ณต, ๊ถŒํ•œ ๊ฑฐ๋ถ€ UX ๋Œ€๋น„
  • ๋”ฅ๋งํฌ: expo-linking์œผ๋กœ ์Šคํ‚ด/์œ ๋‹ˆ๋ฒ„์„ค ๋งํฌ ์„ค์ •(๊ฒ€์ฆ ํŒŒ์ผ ํฌํ•จ)
    • ์™ธ๋ถ€ ์•ฑ ๋ฏธ์„ค์น˜/์‹คํŒจ ์‹œ Web Fallback ์ค€๋น„
  • ์ธ์•ฑ ๋ธŒ๋ผ์šฐ์ €: expo-web-browser(์„ธ์…˜ ์ฝœ๋ฐฑ/๋ฆฌ๋””๋ ‰์…˜ ์ฒ˜๋ฆฌ)

6) ๋ณด์•ˆ/๊ฐœ์ธ์ •๋ณด

  • ํ† ํฐ/์„ธ์…˜: SecureStore ์ €์žฅ, ๋งŒ๋ฃŒ/๊ฐฑ์‹  ๋กœ์ง ํ‘œ์ค€ํ™”
  • ์ „์†ก ์•”ํ˜ธํ™”: HSTS/์ตœ์‹  TLS, (๋งŒ์•ฝ ํ•„์š”ํ•œ ๊ฒฝ์šฐ) Pinning์€ Bare/Dev Client์—์„œ ๊ฒ€ํ† 
  • ๋กœ๊ทธ/๋ถ„์„์— PII ๊ธˆ์ง€. ๋งˆ์Šคํ‚น/ํ•ด์‹œ ๊ทœ์น™ ๋ฌธ์„œํ™”
  • ๊ถŒํ•œ ํ”„๋กฌํ”„ํŠธ ๋ฌธ๊ตฌ๋Š” ์ •ํ™•ํ•œ ์šฉ๋„์™€ ๊ฐ€์น˜๋ฅผ ์„ค๋ช…
  • ์Šคํฌ๋ฆฐ์ƒท ์ฐจ๋‹จ(๋ฏผ๊ฐํ™”๋ฉด) ํ•„์š” ์‹œ Android ํ”Œ๋ž˜๊ทธ/Bare ๊ฒ€ํ† 

7) ๋ถ„์„/๋ชจ๋‹ˆํ„ฐ๋ง/๋กœ๊ทธ

  • ํฌ๋ž˜์‹œ/์„ฑ๋Šฅ: @sentry/react-native(์†Œ์Šค๋งต ์—…๋กœ๋“œ ์ž๋™ํ™”)
  • ์ด๋ฒคํŠธ ๋ถ„์„: Segment/Amplitude/Firebase Analytics ์ค‘ ํ•˜๋‚˜
    • ์ด๋ฒคํŠธ ๋ช…์„ธ์„œ(์ด๋ฆ„/ํŒŒ๋ผ๋ฏธํ„ฐ/์ƒ˜ํ”Œ๋ง/PII ๊ทœ์น™) ์ž‘์„ฑ
  • ์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ: ์ธ์•ฑ ํ”ผ๋“œ๋ฐฑ/๋กœ๊ทธ ์ˆ˜์ง‘(๋™์˜/์˜ตํŠธ์•„์›ƒ ์ œ๊ณต)
  • ์›๊ฒฉ ํ”Œ๋ž˜๊ทธ: ๊ธฐ๋Šฅ ํ† ๊ธ€(LaunchDarkly/ConfigCat/์ž์ฒด)๋กœ ์•ˆ์ „ํ•œ ๋กค์•„์›ƒ

8) ๊ตญ์ œํ™”(i18n)/ํ˜„์ง€ํ™”

  • react-i18next + expo-localization
  • ๋ฌธ์ž์—ด ํ‚ค์ปจ๋ฒค์…˜/ํ”Œ๋ ˆ์ด์Šคํ™€๋” ๊ทœ์•ฝ, ๋ฒˆ์—ญ ์›Œํฌํ”Œ๋กœ์šฐ(Figma/Sheet โ†” JSON)
  • ๋‚ ์งœ/ํ†ตํ™” ํฌ๋งท, ์šฐ/์ขŒ ์ •๋ ฌ, ํ”Œ๋Ÿฌ๋Ÿด ๊ทœ์น™ ๋ฐ˜์˜

9) ๋นŒ๋“œ/๋ฐฐํฌ/๋ฆด๋ฆฌ์Šค

  • EAS Build/Submit ํŒŒ์ดํ”„๋ผ์ธ(GitHub Actions)
    • ์Šคํ† ์–ด ์„œ๋ช…/ํ”„๋กœ๋น„์ €๋‹ ์ž๋™ ๊ด€๋ฆฌ, ๋นŒ๋“œ ๋ฒˆํ˜ธ/๋ฒ„์ „ ์ฆ๊ฐ€ ์ž๋™ํ™”
  • EAS Update(OTA): ๋งˆ์ด๋„ˆ ์ˆ˜์ •์€ OTA, ๋„ค์ดํ‹ฐ๋ธŒ ๋ฐ”๋€Œ๋ฉด ์ƒˆ ๋นŒ๋“œ
  • ๋ฆด๋ฆฌ์Šค ํŠธ๋ž™: ๋‚ด๋ถ€ โ†’ ํ…Œ์ŠคํŠธ โ†’ ํ”„๋กœ๋•์…˜ ๋‹จ๊ณ„์  ๋กค์•„์›ƒ
  • ๋ฆด๋ฆฌ์Šค ๋…ธํŠธ ํ…œํ”Œ๋ฆฟ/์ฒดํฌ๋ฆฌ์ŠคํŠธ(Screenshots, ํ”„๋ผ์ด๋ฒ„์‹œ, ์‹ฌ์‚ฌ ๋Œ€์‘ Q&A)

10) ํ’ˆ์งˆ/ํ…Œ์ŠคํŠธ ์ „๋žต

  • ์œ ๋‹›/์ปดํฌ๋„ŒํŠธ: Jest + Testing Library(RN)
  • ํ†ตํ•ฉ/E2E: Maestro(Expo ์นœํ™”), ํ•„์š” ์‹œ Detox(Dev/Bare)
  • ์ ‘๊ทผ์„ฑ ํ…Œ์ŠคํŠธ: ์Šคํฌ๋ฆฐ๋ฆฌ๋” ์‹œ๋‚˜๋ฆฌ์˜ค ๋ฌธ์„œ + ์ˆ˜๋™ ํ…Œ์ŠคํŠธ
  • ๋””๋ฐ”์ด์Šค ๋งคํŠธ๋ฆญ์Šค: iOS(์ตœ์†Œ/์ตœ์‹ ) ร— Android(์ค‘์ €๊ฐ€ ํฌํ•จ) ์‹ค์ œ๊ธฐ๊ธฐ
  • ์„ฑ๋Šฅ ํšŒ๊ท€: ์Šคํฌ๋กค/๋ฐ”ํ…€์‹œํŠธ/๋ฆฌ์ŠคํŠธ FPS ์ธก์ • ์ฒดํฌ

11) ์„ค์ •/๊ถŒํ•œ/์•ฑ ๊ตฌ์„ฑ

  • app.config.ts์—์„œ env๋ณ„ ๋ฒˆ๋“คID/์Šคํ‚ด/์•„์ด์ฝ˜/์Šคํ”Œ๋ž˜์‹œ ๋ถ„๋ฆฌ
  • iOS infoPlist/Android permissions ๊ถŒํ•œ ์„ ์–ธ + ์‚ฌ์œ  ๋ฌธ๊ตฌ
  • ๋”ฅ๋งํฌ ์Šคํ‚ด ๋ช…๋ช… ๊ทœ์น™(์ค‘๋ณต ๋ฐฉ์ง€), ์œ ๋‹ˆ๋ฒ„์„ค ๋งํฌ ๋„๋ฉ”์ธ ์†Œ์œ  ๊ฒ€์ฆ

12) ์Šคํ† ์–ด ์ปดํ”Œ๋ผ์ด์–ธ์Šค/์ •์ฑ…

  • ๊ฐœ์ธ์ •๋ณด ์ฒ˜๋ฆฌ๋ฐฉ์นจ/์ด์šฉ์•ฝ๊ด€ ์•ฑ ๋‚ด ๋งํฌ ์ œ๊ณต
  • Sign in with Apple(iOS, ํƒ€ ์†Œ์…œ ๋กœ๊ทธ์ธ ์žˆ์„ ๋•Œ)
  • ์œ„์น˜/๋ฐฑ๊ทธ๋ผ์šด๋“œ/๋…น์Œ ๋“ฑ ๋ฏผ๊ฐ๊ถŒํ•œ ํ•„์š”์„ฑ ๋ช…ํ™•ํ™”(๊ธฐ๋Šฅ ์—†์œผ๋ฉด ์ œ๊ฑฐ)
  • ํ‘ธ์‹œ ์ด์šฉ ๋ชฉ์  ์„ค๋ช…/์˜ตํŠธ์ธ/์˜ตํŠธ์•„์›ƒ UX ์ œ๊ณต

13) ํŒ€ ์šด์˜/๋ ˆํฌ/CI

  • ๋ชจ๋…ธ๋ ˆํฌ(Turborepo + pnpm) ๊ณ ๋ ค: web/rn ๊ณตํ†ต ํŒจํ‚ค์ง€(ui, config, api)
  • ์ฝ”๋“œ ๊ทœ์น™: TypeScript strict, Biome(ํฌ๋งท/๋ฆฐํŠธ), commitlint + conventional commits
  • PR ํ…œํ”Œ๋ฆฟ(ํ…Œ์ŠคํŠธ ๋ฒ”์œ„/์Šคํฌ๋ฆฐ์ƒท/์ ‘๊ทผ์„ฑ ์ฒดํฌ ํฌํ•จ)
  • ๋ณ€๊ฒฝ ๋กœ๊ทธ ์ž๋™ํ™”(Changesets)

14) ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™”/์˜คํ”„๋ผ์ธ ์‹ฌํ™”(์„ ํƒ)

  • ์ถฉ๋Œ ํ•ด๊ฒฐ ์ •์ฑ…: Last-Write-Wins vs Merge vs ์„œ๋ฒ„ ๊ถŒํ•œ
  • ํ ์ €์žฅ: ์˜คํ”„๋ผ์ธ ๋ณ€๊ฒฝ๋ถ„ ๋กœ์ปฌ ํ โ†’ ์˜จ๋ผ์ธ ์‹œ ์žฌ์ƒ
  • ๋ถ€๋ถ„ ๋™๊ธฐํ™”: ๋ทฐ ๋‹จ์œ„ prefetch(React Query) + TTL

15) ์œ„ํ—˜์š”์ธ(Risk Register) ์˜ˆ์‹œ

๋ฆฌ์Šคํฌ ์˜ํ–ฅ ๋Œ€์‘
๊ถŒํ•œ ๊ฑฐ๋ถ€๋กœ ํ•ต์‹ฌ ๊ธฐ๋Šฅ ๋ถˆ๊ฐ€ ๊ธฐ๋Šฅ ๋ฏธ์‚ฌ์šฉ/์ดํƒˆ ์ตœ์ดˆ ์ง„์ž… ์˜จ๋ณด๋”ฉ์—์„œ ๊ฐ€์น˜ ์„ค๋ช… โ†’ ์„ค์ •ํ™”๋ฉด ์ด๋™ ๋ฒ„ํŠผ ์ œ๊ณต
์•ˆ๋“œ๋กœ์ด๋“œ ๊ธฐ๊ธฐ ํŽธ์ฐจ ํฌ๋ž˜์‹œ/UX ๋ถˆ์ผ์น˜ ์ค‘์ €๊ฐ€ ์‹ค์ œ๊ธฐ๊ธฐ ํ…Œ์ŠคํŠธ, ์ฑ„๋„/ํŒŒ์ผ/์Šคํ† ๋ฆฌ์ง€ ์ •์ฑ… ์‚ฌ์ „ ๊ฒ€์ฆ
์•Œ๋ฆผ ๋ฏธ๋„์ฐฉ/ํ† ํฐ ๋งŒ๋ฃŒ ํ•ต์‹ฌ ์ด๋ฒคํŠธ ๋ˆ„๋ฝ ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ๋กœ์ง/์„œ๋ฒ„ ๋ณด์ •, ์•Œ๋ฆผ ์ƒํƒœ self-check ํ™”๋ฉด
๋ฒˆ๋“ค ํฌ๊ธฐ ์ฆ๊ฐ€ ์„ค์น˜/์—…๋ฐ์ดํŠธ ์ดํƒˆ ์•„์ด์ฝ˜/์ด๋ฏธ์ง€ ์ตœ์ ํ™”, ์ฃฝ์€ ์˜์กด์„ฑ ์ œ๊ฑฐ, ์ฝ”๋“œ ์Šคํ”Œ๋ฆฟ ๊ณ ๋ ค
API ๋ณ€๊ฒฝ/์†๋„ ์ด์Šˆ ์žฅ์• /ํƒ€์ž„์•„์›ƒ ๋ฒ„์ „๋œ API ๊ณ„์•ฝ, ๋ฆฌํŠธ๋ผ์ด/๋ฐฑ์˜คํ”„/์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค

๋น ๋ฅธ ์‹คํ–‰ ์ฒดํฌ๋ฆฌ์ŠคํŠธ(DoD)

  • ๋””์ž์ธ ํ† ํฐ/Variant ์ดˆ์•ˆ ํ™•์ •(์ƒ‰/๊ฐ„๊ฒฉ/ํฐํŠธ/๋ผ์šด๋“œ/์ƒํƒœ)
  • RN Aria + Restyle ๊ธฐ๋ฐ˜ Button/Input/Card/Modal/Sheet ๋ฒ ์ด์Šค ๊ตฌ์ถ•
  • React Navigation + ๋”ฅ๋งํฌ/์ดˆ๊ธฐ ๋ฃจํŠธ ์„ธํŒ…
  • ๊ถŒํ•œ ํ”„๋กฌํ”„ํŠธ ๋ฌธ๊ตฌ/ํ๋ฆ„ ํ™•์ •(์นด๋ฉ”๋ผ/์•จ๋ฒ”/์œ„์น˜/์•Œ๋ฆผ)
  • ์•Œ๋ฆผ ์ฑ„๋„(Android) ๋ฐ Expo Push ํ† ํฐ ๋ฐœ๊ธ‰/์„œ๋ฒ„ ์—ฐ๋™
  • React Query ์บ์‹œ/์—๋Ÿฌ ๊ฒฝ๊ณ„/๋กœ๊น…(Sentry) ์ ์šฉ
  • EAS Build/Submit ํŒŒ์ดํ”„๋ผ์ธ + OTA(Update) ๊ตฌ์„ฑ
  • i18n ์„ธํŒ… + ๊ธฐ๋ณธ ๋ฒˆ์—ญ ํŒŒ์ผ
  • ํ…Œ์ŠคํŠธ: Jest/RTL + Maestro ์Šค๋ชจํฌ ํ”Œ๋กœ์šฐ
  • ์Šคํ† ์–ด ์ œ์ถœ ์ฒดํฌ๋ฆฌ์ŠคํŠธ/๊ฐœ์ธ์ •๋ณด ๋ฌธ์„œ ์ค€๋น„