콘텐츠 대표 이미지 - 자바스크립트와 다트의 대결: 리액트 네이티브 vs 플러터 모바일 앱 개발 프레임워크 성능 심층 분석
기타프로그램개발 · 크로스플랫폼

자바스크립트와 다트의 대결: 리액트 네이티브 vs 플러터 모바일 앱 개발 프레임워크 성능 심층 분석

런타임 구조부터 프레임 타임, 앱 용량, 시작 시간까지
두 진영의 실제 성능 차이를 엔진 레벨에서 해부합니다.

0. 들어가기: 왜 아직도 이 싸움이 끝나지 않는가

모바일 앱 개발자 커뮤니티에서 "리액트 네이티브냐 플러터냐"는 질문은 거의 탕수육 찍먹·부먹 급 논쟁입니다.
2015년 리액트 네이티브가 등장하고, 2017년 플러터가 정식 무대에 오른 뒤로 벌써 10년 가까운 시간이 흘렀는데도 결론이 나지 않았습니다.

그 이유는 단순합니다. 두 프레임워크가 서로 완전히 다른 철학으로 "성능"을 정의하기 때문입니다.

리액트 네이티브는 "플랫폼의 진짜 위젯을 쓰자"는 노선입니다.
플러터는 "화면 전체를 우리가 직접 그리자"는 노선입니다.

이 한 줄의 차이가 언어(자바스크립트 vs 다트), 렌더링 파이프라인, 스레드 모델, 빌드 산출물, 심지어 채용 시장까지 전부 갈라놓습니다.
이 글에서는 감성적인 "이게 더 좋아요"가 아니라, 측정 가능한 지표와 구조적 근거를 중심으로 파헤쳐 보겠습니다.

이 글에서 다루는 범위

① 언어 레벨 비교(JS vs Dart) → ② 런타임·컴파일 구조 → ③ 렌더링 파이프라인 → ④ 실측 성능 지표(프레임, 시작시간, 메모리, 용량) → ⑤ 상황별 선택 기준 → ⑥ 성능 최적화 실전 팁

1. 언어 라운드: 자바스크립트 vs 다트

1-1. 자바스크립트 — 세상에서 가장 많이 쓰이는 즉흥 연주자

자바스크립트는 1995년 브렌던 아이크가 열흘 만에 프로토타입을 만든 언어입니다.
그 급조된 출생 배경에도 불구하고, 지금은 ECMAScript 표준 아래 매년 진화하는 거대한 생태계의 중심이 되었습니다.

핵심 특성:

• 동적 타입 — 변수 타입이 실행 중에 결정됩니다.
• 단일 스레드 + 이벤트 루프 — 논블로킹 비동기 모델.
• JIT 컴파일 — V8/JavaScriptCore/Hermes 같은 엔진이 실행 중 기계어로 변환.
• 프로토타입 기반 객체 모델 — 클래스는 문법적 설탕(syntactic sugar).

리액트 네이티브에서는 여기에 TypeScript를 얹는 것이 사실상 표준이 되었습니다.
즉 현실의 RN 프로젝트는 "자바스크립트"라기보다 "타입스크립트로 작성되어 자바스크립트로 트랜스파일되는 코드"입니다.

1-2. 다트 — 클라이언트 최적화를 위해 설계된 정공법 언어

다트는 2011년 구글이 발표했습니다. 처음 목표는 "브라우저에서 JS를 대체하기"였고, 그건 실패했습니다.
그러나 플러터의 언어로 재발견되면서 완전히 다른 운명을 맞았습니다.

다트가 플러터에 선택된 이유는 매우 기술적입니다:

• JIT와 AOT를 모두 지원 — 개발 중엔 JIT로 핫 리로드, 릴리즈엔 AOT로 네이티브 기계어.
• 사운드 널 세이프티(Sound Null Safety) — 2.12부터 도입, 컴파일 타임에 null 참조를 차단.
• 세대별 GC + 가비지 수집 최적화 — 짧은 수명의 위젯 객체를 대량 생성/폐기하는 UI 패턴에 유리.
• Isolate 모델 — 메모리를 공유하지 않는 독립 실행 단위. 락(lock) 없이 병렬 처리.
• 단일 스레드 이벤트 루프 — JS와 마찬가지로 UI 코드는 예측 가능한 순서로 실행.

핵심 인사이트

다트의 가장 큰 무기는 "빠른 언어"라는 점이 아닙니다.
같은 언어로 JIT(개발 속도)와 AOT(실행 속도)를 동시에 얻는다는 구조적 이점입니다.
리액트 네이티브가 Hermes에서 AOT 바이트코드 프리컴파일을 도입한 것도 결국 이 이점을 좇는 방향이었습니다.

1-3. 언어 성능 벤치마크의 함정

"다트가 JS보다 N배 빠르다"는 주장은 대부분 무의미합니다.
왜냐하면 모바일 앱에서 순수 언어 연산이 병목이 되는 경우는 드물기 때문입니다.

실제 앱의 병목은 거의 항상 다음 셋입니다:

1) 레이아웃/렌더링 (프레임을 16.67ms 안에 못 만듦)
2) 네트워크 I/O (API 응답 대기)
3) 브리지/직렬화 오버헤드 (RN의 전통적 약점)

따라서 언어 자체보다 언어가 놓인 런타임 구조를 봐야 합니다.

언어 실행 경로 비교 JavaScript (React Native) TypeScript / JSX 소스 Babel / Metro 번들링 Hermes 바이트코드 (.hbc) JS 엔진 인터프리터 실행 Dart (Flutter) Dart 소스 (Null Safe) Dart AOT 컴파일러 ARM / x64 기계어 CPU 직접 실행 (인터프리터 없음)

2. 런타임 구조 라운드: 브리지의 시대와 그 이후

2-1. 리액트 네이티브의 옛 아키텍처 (Legacy Bridge)

2015년~2021년경까지의 리액트 네이티브는 세 개의 스레드로 돌아갔습니다.

• JS 스레드 — 비즈니스 로직, React 재조정(reconciliation) 수행
• Native/UI 스레드 — 실제 안드로이드 View / iOS UIView 조작
• Shadow 스레드 — Yoga 레이아웃 엔진이 Flexbox 계산 수행

문제는 이 스레드들 사이의 통신이 비동기 + JSON 직렬화 기반 브리지였다는 점입니다.

결과적으로 이런 현상이 생겼습니다:

1) 동기 호출 불가 — 네이티브 값을 즉시 읽어올 수 없음.
2) 대량 데이터 전송 시 병목 — 긴 리스트 스크롤, 제스처 추적에서 프레임 드롭.
3) "빈 셀" 문제 — FlatList를 빠르게 스크롤하면 하얀 공백이 잠깐 보이는 그 현상.

실무에서 유명했던 증상

드래그 앤 드롭이나 스와이프 애니메이션을 JS 스레드에서 처리하면, 손가락과 UI가 어긋나는 느낌이 났습니다.
이 때문에 react-native-reanimated가 등장해 애니메이션 로직을 UI 스레드에서 실행하는 우회책을 제공했습니다.

2-2. New Architecture — JSI, Fabric, TurboModules

리액트 네이티브 0.68부터 점진 도입되어 0.76에서 기본값이 된 새 아키텍처는 구조를 근본적으로 바꿨습니다.

JSI (JavaScript Interface)
C++ 레이어로, JS 객체가 네이티브 객체의 참조를 직접 보유할 수 있게 합니다.
JSON 직렬화 없이 동기적 함수 호출이 가능해졌습니다. 브리지의 종말입니다.

Fabric
새 렌더러입니다. C++로 작성된 Shadow Tree를 공유해, JS와 네이티브가 같은 트리를 참조합니다.
동기 레이아웃 측정, 뷰 평탄화(view flattening), 우선순위 기반 업데이트가 가능해졌습니다.

TurboModules
네이티브 모듈을 지연 로딩(lazy load)합니다. 앱 시작 시 모든 모듈을 초기화하지 않으므로 콜드 스타트가 개선됩니다.

Codegen
TypeScript 타입 정의에서 C++ 바인딩을 자동 생성해 타입 안정성을 확보합니다.

즉, "리액트 네이티브는 브리지 때문에 느리다"는 말은 2025년 기준으로 상당 부분 낡은 비판입니다.
다만 서드파티 라이브러리 생태계가 New Architecture로 완전히 이전되기까지는 시간이 걸렸고, 레거시 프로젝트는 여전히 옛 구조로 돌아갑니다.

2-3. Hermes — RN 성능 개선의 숨은 주역

Hermes는 Meta가 React Native 전용으로 만든 JS 엔진입니다. 0.70부터 기본 엔진입니다.

기존 JSC(JavaScriptCore)와의 결정적 차이:

항목JavaScriptCoreHermes
파싱 시점런타임 파싱빌드 타임 프리컴파일
실행 방식JIT 중심바이트코드 인터프리터
TTI(첫 화면)느림빠름
메모리 사용많음적음
장기 연산 처리량유리상대적 불리

Hermes는 JIT을 일부러 제외했습니다. 모바일 앱은 "긴 시간 계산"보다 "빨리 켜지는 것"이 중요하다는 판단이었죠.
그래서 시작 시간과 메모리는 크게 개선되었지만, 무거운 반복 연산은 JIT 엔진보다 느릴 수 있습니다.

2-4. 플러터의 런타임 — 스킵된 중간 다리

플러터는 브리지 논쟁 자체가 성립하지 않습니다. 이유는 간단합니다.

플러터는 플랫폼 위젯을 쓰지 않습니다.
안드로이드에서든 iOS에서든 플러터가 받는 것은 사실상 "픽셀을 그릴 캔버스 하나"뿐입니다.

구조는 이렇습니다:

• Framework 레이어 (Dart) — Widget → Element → RenderObject 트리
• Engine 레이어 (C++) — Skia 또는 Impeller 렌더러, 텍스트 셰이핑, 플랫폼 채널
• Embedder 레이어 — 각 OS의 창(surface), 입력 이벤트, 스레드 설정

Dart 코드는 AOT 컴파일되어 네이티브 기계어로 실행되고, 엔진도 C++입니다.
즉 UI를 그리는 전 경로에 인터프리터나 직렬화가 없습니다.

다만 카메라, 결제, 블루투스 같은 OS 기능은 Platform Channel로 호출해야 하며, 이때는 메시지 직렬화가 발생합니다.
그래서 플러터도 "플랫폼 연동이 많은 앱"에서는 채널 오버헤드를 신경 써야 합니다.
이를 줄이기 위해 Dart FFI와 Pigeon(타입 안전 코드 생성) 같은 도구가 제공됩니다.

3. 렌더링 라운드: 네이티브 위젯 vs 자체 캔버스

렌더링 파이프라인 구조 React Native · Fabric React 컴포넌트 (JS) C++ Shadow Tree Yoga 레이아웃 엔진 플랫폼 View 마운트 UIKit / Android View 렌더 OS가 최종 픽셀을 그린다 Flutter · Impeller Widget 트리 (Dart) Element 트리 (상태 보존) RenderObject 레이아웃/페인트 Layer Tree → 엔진 전달 Metal / Vulkan GPU 직접 호출 프레임워크가 최종 픽셀을 그린다

3-1. Skia에서 Impeller로 — 플러터의 셰이더 컴파일 전쟁

플러터는 오랫동안 Skia(구글의 2D 그래픽 라이브러리, 크롬에도 쓰임)를 사용했습니다.
강력했지만 한 가지 치명적 약점이 있었습니다: 셰이더 컴파일 지터(shader compilation jank).

새로운 애니메이션이나 그래픽 효과가 처음 등장할 때, GPU 셰이더를 런타임에 컴파일해야 했습니다.
이 컴파일이 수십~수백 ms를 잡아먹으면서 "첫 실행 시에만 버벅이는" 현상이 생겼습니다.
특히 iOS에서 악명이 높았습니다.

구글은 이를 해결하기 위해 Impeller를 만들었습니다. 핵심 전략은:

• 셰이더 사전 컴파일(빌드 타임) — 런타임 컴파일 제거
• Metal(iOS) / Vulkan(Android) 직접 타겟팅
• 테셀레이션 예측 가능성 확보 — 프레임 타임의 분산 축소

Impeller는 Flutter 3.10에서 iOS 기본값, 이후 안드로이드로도 확대되었습니다.
결과적으로 평균 FPS보다 "프레임 타임의 일관성(P99 지터)"이 크게 개선되었습니다.

왜 평균 FPS보다 P99가 중요한가?

사람의 눈은 "평균 59fps"를 칭찬하지 않습니다.
대신 1초에 한 번 튀는 120ms 프레임을 즉시 "구리다"고 인식합니다.
그래서 성능 평가는 평균이 아니라 99퍼센타일 프레임 타임과 드롭 프레임 수로 해야 합니다.

3-2. 네이티브 위젯 방식의 장점과 대가

리액트 네이티브가 실제 UIView/View를 쓰는 것은 명확한 이점이 있습니다.

장점
• OS 업데이트 시 디자인 언어가 자동 반영
• 접근성(TalkBack, VoiceOver) 지원이 기본적으로 정확
• 텍스트 입력, IME(한글 조합), 복사·붙여넣기 등 세밀한 플랫폼 동작이 자연스러움
• 스크롤 물리(iOS 바운스 등)가 진짜 그 플랫폼의 것

대가
• 플랫폼 간 미세한 렌더링 차이 발생 (같은 코드, 다른 그림)
• 복잡한 커스텀 UI를 만들 때 네이티브 뷰 계층이 깊어져 성능 저하
• 뷰 계층 조작이 스레드 경계를 넘어야 함

반대로 플러터는 모든 픽셀을 직접 그리므로 픽셀 단위 일관성을 보장합니다.
그러나 "iOS 네이티브 느낌"을 100% 재현하려면 Cupertino 위젯을 쓰고도 미세한 이질감이 남을 수 있고,
접근성·텍스트 입력 같은 영역은 플러터 팀이 직접 구현해야 하므로 플랫폼 신기능 반영이 한 박자 늦습니다.

4. 실측 성능 라운드: 숫자로 보는 차이

아래 수치는 다수의 공개 벤치마크(커뮤니티 측정, 프레임워크 공식 문서, 기기별 프로파일링)에서 반복적으로 관찰되는 경향을 정리한 것입니다.
기기·버전·코드 품질에 따라 편차가 크므로 절대값이 아닌 상대적 경향으로 읽어주세요.

4-1. 앱 시작 시간 (Cold Start)

구분React Native (Hermes)Flutter (AOT)
최소 Hello World보통빠름
중형 앱(수십 화면)보통 ~ 느림빠름
주요 비용 요소JS 번들 로드 + 네이티브 모듈 초기화엔진 초기화 + 기계어 로드

플러터는 Dart 코드가 이미 기계어라서 파싱 단계가 없습니다.
RN은 Hermes 바이트코드로 파싱 비용을 줄였지만, 번들 크기가 커지면 로드 시간이 선형으로 증가합니다.

실무 대응책:
• RN → 인라인 리콰이어(inline requires) + TurboModules 지연 초기화
• 플러터 → deferred components(안드로이드) 로 초기 로드 축소

4-2. 애니메이션 · 스크롤 프레임 안정성

이 영역은 전통적으로 플러터가 우세했습니다.
JS 스레드를 거치지 않고 렌더 트리를 직접 조작하기 때문입니다.

그러나 상황은 변했습니다:

• RN + Reanimated 조합은 애니메이션 워크렛을 UI 스레드에서 실행합니다.
• Fabric 도입 후 동기 레이아웃이 가능해져 제스처 반응성이 개선됐습니다.
• FlashList 같은 고성능 리스트 라이브러리가 FlatList의 빈 셀 문제를 크게 완화했습니다.

결론적으로 "제대로 세팅한 RN"과 "제대로 세팅한 플러터"의 체감 차이는 많이 좁혀졌습니다.
다만 기본 설정 상태(out of the box)에서는 여전히 플러터가 안정적입니다.
즉 플러터는 "잘 안 망가지고", RN은 "잘 튜닝하면 따라잡는" 성격입니다.

4-3. 메모리 사용량

메모리는 흥미롭게 갈립니다.

• 플러터: 렌더 오브젝트 트리와 이미지 캐시, GPU 텍스처를 자체 관리하므로 화면이 복잡하면 메모리가 빠르게 증가합니다. 특히 대형 이미지 리스트에서 cacheWidth 지정을 빼먹으면 원본 해상도 디코딩으로 메모리가 폭발합니다.
• RN(Hermes): JS 힙 자체는 작습니다. 그러나 네이티브 뷰 인스턴스가 많아지면 플랫폼 뷰 메모리가 늘어납니다.

일반적으로 단순 앱에서는 RN+Hermes가 더 적은 메모리, 그래픽 무거운 앱에서는 플러터가 더 예측 가능한 경향입니다.

4-4. 앱 바이너리 크기

구분Android (arm64, release)iOS (release)
Flutter 최소 앱대략 7~9MB 수준대략 12~15MB 수준
React Native 최소 앱대략 6~8MB 수준대략 10~13MB 수준

둘 다 "순수 네이티브 최소 앱(1~2MB)"보다는 큽니다.
플러터는 엔진(Skia/Impeller + Dart 런타임)을 통째로 포함하므로 기본 오버헤드가 있습니다.
RN은 Hermes 엔진과 네이티브 모듈들이 용량을 차지합니다.

실무에서는 앱이 커질수록 프레임워크 오버헤드의 비중은 작아지므로 이 항목만으로 선택하는 건 비합리적입니다.
(단, 신흥 시장 타겟의 초경량 앱이라면 의미 있는 기준이 됩니다.)

영역별 상대 강점 (개념적 비교) 0 중 상 프레임 안정성 시작 속도 생태계 규모 플랫폼 일치감 React Native Flutter

5. 코드로 보는 차이: 같은 화면, 다른 문법

5-1. 리액트 네이티브 (TypeScript)

import React, { useState, useCallback } from 'react';
import { View, Text, Pressable, FlatList } from 'react-native';

type Item = { id: string; label: string };

export default function CounterList() {
  const [count, setCount] = useState<number>(0);

  const data: Item[] = Array.from({ length: 200 }, (_, i) => ({
    id: String(i),
    label: `아이템 ${i + 1}`,
  }));

  const increase = useCallback(() => setCount(c => c + 1), []);

  return (
    <View style={{ flex: 1, padding: 16 }}>
      <Text>탭 횟수: {count}</Text>
      <Pressable onPress={increase}>
        <Text>증가</Text>
      </Pressable>
      <FlatList
        data={data}
        keyExtractor={item => item.id}
        renderItem={({ item }) => <Text>{item.label}</Text>}
        initialNumToRender={10}
        windowSize={5}
        removeClippedSubviews
      />
    </View>
  );
}

5-2. 플러터 (Dart)

import 'package:flutter/material.dart';

class CounterList extends StatefulWidget {
  const CounterList({super.key});
  @override
  State<CounterList> createState() => _CounterListState();
}

class _CounterListState extends State<CounterList> {
  int _count = 0;

  void _increase() => setState(() => _count++);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        children: [
          Text('탭 횟수: $_count'),
          ElevatedButton(
            onPressed: _increase,
            child: const Text('증가'),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: 200,
              itemExtent: 48, // 높이 고정 → 레이아웃 계산 비용 절감
              itemBuilder: (context, i) => Text('아이템 ${i + 1}'),
            ),
          ),
        ],
      ),
    );
  }
}

코드 관점의 차이

• RN은 선언형 + 훅 기반으로 함수 컴포넌트가 중심. 웹 개발자에게 즉시 익숙합니다.
• 플러터는 위젯 합성(composition) 중심으로, 중첩이 깊어지는 대신 모든 UI 요소가 명시적입니다.
• 플러터의 const 위젯은 리빌드 시 재생성되지 않아 성능에 직접 기여합니다. RN의 useMemo·React.memo와 목적이 비슷합니다.

6. 개발자 경험(DX)과 생태계

6-1. 핫 리로드 / 패스트 리프레시

둘 다 훌륭합니다. 체감상 큰 차이는 없습니다.
플러터의 Hot Reload는 Dart VM의 JIT 모드를 활용해 상태를 유지한 채 위젯 트리만 갱신합니다.
RN의 Fast Refresh는 Metro 번들러가 변경 모듈만 교체합니다.

다만 플러터는 단일 공식 툴체인(flutter CLI, DevTools)이 통합되어 있어 초기 설정 스트레스가 적습니다.
RN은 선택지가 많은 대신, 네이티브 빌드 설정 이슈로 하루를 태우는 경험이 흔했습니다.
이 문제를 크게 줄인 것이 Expo이며, 현재는 RN 공식 문서도 Expo를 권장 경로로 안내합니다.

6-2. 패키지 생태계

• npm — 수백만 패키지. 다만 상당수가 웹 전용이며 모바일 호환 여부를 확인해야 합니다.
• pub.dev — 상대적으로 규모는 작지만, Flutter Favorite 인증과 점수 체계로 품질 판별이 쉽습니다.

흥미로운 점은 "양"은 RN, "정돈"은 플러터라는 것입니다.
RN은 원하는 기능이 거의 다 있지만 유지보수가 끊긴 패키지 문제가 잦고,
플러터는 없으면 직접 만들어야 하지만 있는 건 대체로 잘 동작합니다.

6-3. 인력 시장과 학습 곡선

이건 기술보다 조직 현실의 문제입니다.

웹 프론트엔드 개발자가 사내에 이미 있다면, RN 전환 비용은 극도로 낮습니다.
React 지식, TypeScript 지식, 상태관리 패턴(Zustand, Redux, TanStack Query)이 거의 그대로 이전됩니다.

반면 다트는 새로 배워야 합니다. 그러나 문법이 Java/C#/TypeScript와 유사해 러닝커브는 완만합니다.
실무 개발자들 경험상 2주 정도면 생산성 궤도에 오릅니다.
진짜 학습 비용은 언어가 아니라 위젯 트리 사고방식과 상태관리(Riverpod, Bloc) 선택에 있습니다.

이런 전환기의 학습 격차 때문에, 실제 실무에서는 경험자 멘토링이 큰 도움이 됩니다.
재능넷 같은 재능 공유 플랫폼에서 크로스플랫폼 코드 리뷰나 아키텍처 컨설팅을 단기간 받는 팀이 늘어난 것도 이 흐름과 맞물립니다.

7. 성능 최적화 실전 체크리스트

7-1. 리액트 네이티브 최적화

① 리스트는 FlashList를 고려하라
FlatList의 가상화는 뷰를 언마운트/마운트하지만, FlashList는 뷰 재활용(recycling)을 합니다. 스크롤 지터가 크게 줄어듭니다.

② 애니메이션은 반드시 Reanimated 워크렛으로
Animated API를 JS 드라이버로 돌리면 프레임을 놓칩니다. useNativeDriver: true 또는 Reanimated를 쓰세요.

③ 리렌더 범위를 좁혀라
전역 상태를 한 덩어리로 구독하면 화면 전체가 리렌더됩니다.
선택자(selector) 기반 구독, React.memo, 안정적인 key를 활용하세요.

④ 이미지 최적화
react-native-fast-image 또는 Expo Image로 캐싱·디코딩을 개선합니다.

⑤ 인라인 리콰이어와 Hermes 프로파일링
Hermes 샘플링 프로파일러로 JS 스레드 병목을 찾고, 무거운 모듈을 지연 로드합니다.

⑥ New Architecture 활성화
0.76 이상에서는 기본값입니다. 레거시 라이브러리 호환성만 점검하세요.

7-2. 플러터 최적화

① const 위젯을 아끼지 말라
const Text('제목') 하나가 수천 번의 불필요한 리빌드를 막습니다.

② setState의 범위를 최소화
거대한 build() 전체를 다시 호출하지 말고, 변하는 부분만 별도 위젯으로 분리하거나 ValueListenableBuilder를 사용하세요.

③ ListView.builder + itemExtent
높이를 알려주면 레이아웃 계산량이 급감합니다. ListView(children: [...])로 수백 개를 한 번에 만드는 건 금물입니다.

④ 이미지 디코딩 크기 지정
Image.network(url, cacheWidth: 300) 처럼 표시 크기에 맞춰 디코딩해야 메모리가 안전합니다.

⑤ RepaintBoundary 활용
애니메이션 영역을 격리해 불필요한 재페인트를 차단합니다. 단, 남용하면 레이어가 늘어나 역효과입니다.

⑥ 무거운 연산은 Isolate로
JSON 대용량 파싱, 이미지 처리 등은 compute()나 Isolate로 넘겨 UI 스레드를 비워둡니다.

⑦ Impeller 사용 및 --profile 모드 측정
디버그 모드 성능은 무조건 느립니다. 반드시 profile 또는 release 빌드로 측정하세요. 이걸 몰라서 "플러터 느리다"고 오해하는 경우가 매우 많습니다.

가장 흔한 벤치마크 오류 3종

1) 디버그 빌드로 측정 (둘 다 실제보다 수 배 느림)
2) 에뮬레이터/시뮬레이터로 측정 (GPU 경로가 실기기와 다름)
3) 프레임워크 기본 컴포넌트만 비교 (실제 앱의 병목은 개발자 코드)

8. 그래서 무엇을 선택해야 하는가

성능만으로 결정하면 오답이 나옵니다. 아래 기준으로 판단하세요.

8-1. 리액트 네이티브가 유리한 경우

• 이미 React 웹 서비스가 있고 코드·인력을 공유하고 싶다
• 웹·앱 간 비즈니스 로직 재사용 비중이 크다
• OTA 업데이트(CodePush 계열, Expo Updates)로 스토어 심사 없이 배포하고 싶다
• 플랫폼 표준 UI를 그대로 쓰는 앱(커머스, 콘텐츠 피드, 사내 앱)
• 네이티브 모듈을 직접 작성할 수 있는 팀 역량이 있다

8-2. 플러터가 유리한 경우

• 브랜드 고유의 커스텀 UI/모션이 제품의 핵심 가치다
• 안드로이드/iOS 픽셀 단위 동일성이 요구된다
• 고밀도 애니메이션, 차트, 인터랙티브 그래픽이 많다
• 모바일 + 데스크톱(윈도우/맥/리눅스) + 임베디드까지 확장 계획이 있다
• 팀 규모가 작고 단일 통합 툴체인의 안정성이 중요하다

8-3. 둘 다 부적합한 경우

솔직하게 말하면, 다음 상황은 네이티브(Kotlin/Swift)가 정답입니다.

• 실시간 영상 처리, AR, 고성능 카메라 파이프라인
• 저지연 오디오(음악 앱, 악기 앱)
• 시스템 위젯, 워치 앱, 딥한 OS 통합(포커스 모드, 라이브 액티비티)
• 게임 (이건 Unity/Unreal 영역)

현실적 조언

크로스플랫폼 프레임워크는 "네이티브를 안 배워도 되는 도구"가 아닙니다.
네이티브를 알면 훨씬 잘 쓰는 도구입니다.
결국 프로덕션 규모로 가면 양쪽 모두 네이티브 지식을 요구합니다. 이 점은 반드시 감안하세요.

9. 2025년 이후의 판도

리액트 네이티브 쪽
New Architecture 정착이 끝났고, Expo가 사실상 표준 개발 환경이 되었습니다.
React Server Components 실험, Static Hermes(AOT 네이티브 컴파일 연구) 같은 방향이 논의되고 있습니다.
Static Hermes가 성숙하면 "JS도 AOT로 기계어"가 되어 다트의 구조적 우위가 축소될 수 있습니다.

플러터 쪽
Impeller 전면 적용, Wasm 기반 웹 타겟(dart2wasm), 데스크톱 성숙도 향상이 진행 중입니다.
구글 내부 조직 변화로 우려의 목소리가 나온 시기도 있었지만, 커뮤니티와 기업 채택은 계속 확대되고 있습니다.

제3의 변수
Kotlin Multiplatform(KMP)과 Compose Multiplatform이 빠르게 올라오고 있습니다.
"로직은 공유, UI는 각 플랫폼 네이티브"라는 절충 전략은 대형 조직에 매우 매력적입니다.
따라서 미래의 질문은 "RN vs 플러터"가 아니라 "공유의 경계를 어디에 두는가"로 이동하고 있습니다.

코드 공유 경계 스펙트럼 100% 네이티브 Swift / Kotlin 로직만 공유 Kotlin Multiplatform UI+로직 공유 React Native 전체 자체 렌더 Flutter 플랫폼 충실도 높음 개발 효율 높음

10. 자주 나오는 질문 정리

Q. 플러터가 무조건 더 빠른가요?

A. 아니요. 기본 상태에서 프레임 안정성이 높다는 것이 정확한 표현입니다.
잘 튜닝된 RN 앱과 잘못 만든 플러터 앱을 비교하면 RN이 압도적으로 빠릅니다.
프레임워크보다 개발자 코드의 품질이 더 큰 변수입니다.

Q. RN의 브리지 문제는 이제 끝났나요?

A. New Architecture 기준으로는 해결되었습니다. JSI로 동기 호출이 가능합니다.
다만 프로젝트가 레거시 라이브러리를 쓰면 브리지 경로가 남아 있을 수 있습니다.

Q. 다트를 배우면 다른 곳에도 쓸 수 있나요?

A. 제한적입니다. 다트는 실질적으로 플러터 생태계에 강하게 묶여 있습니다.
반면 자바스크립트/타입스크립트는 웹, 서버(Node), 스크립팅까지 전방위로 활용됩니다.
기술 투자의 범용성 측면에서는 JS가 유리합니다.

Q. 앱 스토어 심사나 리젝 위험 차이가 있나요?

A. 프레임워크 자체로 리젝되는 일은 없습니다.
단, RN의 OTA 업데이트는 "핵심 기능을 우회 변경"하면 정책 위반이 될 수 있으니 범위를 지켜야 합니다.

Q. 성능 측정은 어떤 도구로 해야 하나요?

A. 플러터는 Flutter DevTools의 Performance/Timeline과 --profile 빌드.
RN은 Flipper/React DevTools, Hermes 프로파일러, 그리고 Android Perfetto · iOS Instruments를 병행하세요.
최종 판단은 항상 실기기 + 릴리즈 빌드 기준입니다.

마무리: 승자는 "질문을 잘 정의한 팀"

자바스크립트와 다트, 리액트 네이티브와 플러터는 서로를 죽이려는 경쟁자가 아닙니다.
사실 둘은 모바일 개발의 비용 구조를 함께 바꿔놓은 동맹군입니다.
10년 전만 해도 앱 하나를 만들려면 iOS 팀과 안드로이드 팀을 각각 꾸려야 했으니까요.

성능 차이는 이제 "프레임워크 선택"보다 "구현 품질"에 더 크게 좌우됩니다.
그래서 정말 중요한 질문은 이렇습니다.

• 우리 팀이 이미 가진 역량은 무엇인가?
• 우리 제품의 UI는 플랫폼 표준을 따르는가, 브랜드 고유성을 추구하는가?
• 앞으로 3년간 어느 플랫폼으로 확장할 것인가?
• 성능 병목이 생겼을 때 그것을 측정하고 수정할 수 있는 사람이 있는가?

이 네 질문에 답할 수 있다면 어느 쪽을 골라도 성공할 확률이 높습니다.
답할 수 없다면, 어느 쪽을 골라도 같은 지점에서 막힙니다.

한 줄 요약

플러터는 예측 가능한 성능을 기본으로 주고, 리액트 네이티브는 기존 웹 자산과 유연성을 준다.
성능은 프레임워크가 아니라 프로파일러를 켜는 습관에서 나온다.

기타프로그램개발 영역에서 크로스플랫폼은 이제 선택지가 아니라 기본 소양이 되었습니다.
더 깊은 실전 노하우나 아키텍처 설계 리뷰가 필요하다면, 재능넷의 '지식인의 숲'과 전문가 매칭을 함께 활용해보시길 권합니다.

측정하지 않은 성능은 신념일 뿐입니다. 프로파일러를 켜세요.

댓글 작성

이 글에 대한 여러분의 생각을 들려주세요

댓글 0
댓글을 불러오는 중입니다.

아직 댓글이 없습니다.