Sik.limited Logo

Capacitor vs Tauri: 하나의 웹 코드베이스를 모바일·데스크톱 앱으로 확장하는 선택 기준

Capacitor와 Tauri를 설치 크기만으로 비교하지 않습니다. 웹 앱을 iOS·Android·Windows·macOS로 확장할 때, 공유할 코드와 분리할 네이티브 경계, 플러그인·보안·업데이트·배포의 실제 선택 기준을 정리했습니다.

Sik · ·

Capacitor vs Tauri: 하나의 웹 코드베이스를 모바일·데스크톱 앱으로 확장하는 선택 기준

웹 서비스를 만들었고, 다음 분기에 앱스토어와 데스크톱 설치 파일까지 내야 한다고 해봅시다. 이때 가장 위험한 말은 “웹 코드를 그대로 감싸면 되겠지”입니다.

초반에는 정말 그렇게 보입니다. 화면과 API 호출은 재사용되고, 빌드도 됩니다. 하지만 푸시 권한, 파일 선택, 딥링크, 오프라인 데이터, 업데이트가 들어오면 질문이 달라집니다. 같은 화면을 쓸 수 있나?가 아니라 어느 경계까지 공유하고, 어디부터 플랫폼의 책임으로 둘 것인가?를 정해야 합니다.

이 글은 Capacitor와 Tauri의 설치 크기만 비교하지 않습니다. 웹 앱을 iOS·Android·Windows·macOS로 넓힐 때, 어떤 출시 순서와 구조가 다시 쓰는 비용을 줄이는지 정리합니다.

기준은 세 가지입니다.

  • 모바일 출시가 먼저이고 팀의 중심 언어가 TypeScript라면 Capacitor를 기본값으로 둡니다.
  • 파일 시스템·트레이·전역 단축키·로컬 처리처럼 OS 통합이 제품 가치라면 Tauri를 먼저 검토합니다.
  • 모바일과 데스크톱을 동시에 내야 한다면, 도구 하나를 고르는 일보다 공유 코어와 플랫폼 어댑터를 분리하는 설계가 먼저입니다.

‘하나의 코드베이스’가 뜻하는 것

Capacitor는 기존 웹 프로젝트에 넣을 수 있는 모바일 네이티브 런타임입니다. 브라우저 표준에 가깝게 개발하면서 필요할 때 Swift·Java/Kotlin 플러그인으로 디바이스 기능에 접근합니다. Capacitor 공식 문서는 이를 웹 우선이면서 네이티브 SDK에 접근할 수 있는 구조로 설명합니다.

Tauri는 HTML·CSS·JavaScript 프론트엔드를 운영체제 WebView 위에서 실행하고, 필요한 시스템 기능을 Rust와 플러그인으로 연결합니다. Tauri 2는 데스크톱뿐 아니라 iOS·Android 대상도 지원합니다. 다만 모바일 플러그인은 Android에서 Kotlin/Java, iOS에서 Swift 구현을 갖습니다. Tauri 모바일 플러그인 문서의 기본 템플릿도 데스크톱 구현과 모바일 구현을 나눕니다.

즉 Tauri를 선택해도 네이티브 코드가 사라지지 않고, Capacitor를 선택해도 웹만 알아서는 끝나지 않습니다. 차이는 그 경계를 언제, 어떤 언어와 도구로 넘느냐입니다.

공유해야 하는 것은 보통 다음 네 가지입니다.

  1. 화면 컴포넌트와 디자인 토큰
  2. 도메인 로직·검증·상태 전이
  3. 서버 API 계약과 인증 흐름
  4. 플랫폼 기능을 부르는 얇은 인터페이스

반대로 권한 문구, 푸시 등록, 파일 경로, 백그라운드 제약, 스토어 배포와 코드 서명은 처음부터 분리합니다. 이 구분을 하지 않으면 if (isNative)가 화면 곳곳에 번지고, 하나의 코드베이스는 몇 달 뒤 조건문 모음이 됩니다.

구조는 런타임이 아니라 제품 기능에서 시작한다

플랫폼을 판별하는 코드를 컴포넌트 안에 직접 넣지 말고, 제품이 필요로 하는 기능을 먼저 정의합니다. 파일 내보내기, 알림 예약, 보안 저장소 같은 포트를 만들고 각 런타임이 구현하게 둡니다.

flowchart TD
  UI["공유 UI · 도메인 로직 · API 계약"] --> PORT["Platform Port<br/>파일 · 알림 · 저장소 · 링크"]
  PORT --> WEB["Web / PWA<br/>Browser API 구현"]
  PORT --> CAP["Capacitor<br/>iOS · Android 플러그인"]
  PORT --> TAU["Tauri<br/>Desktop Rust · Mobile Swift/Kotlin"]
  CAP --> STORE["App Store · Google Play"]
  TAU --> DESKTOP["Windows · macOS · Linux 배포"]

이 구조라면 Capacitor와 Tauri를 나중에 일부 바꿀 수도 있습니다. 모바일은 Capacitor로 먼저 출시하고, 데스크톱은 로컬 파일·트레이 요구가 확인된 뒤 Tauri 셸을 추가할 수 있습니다. UI와 비즈니스 규칙이 독립되어 있다면 교체 대상은 앱 전체가 아니라 어댑터와 빌드 파이프라인입니다.


10분 의사결정표: 기능보다 출시 순서로 고른다

질문Capacitor가 자연스러운 경우Tauri가 자연스러운 경우
첫 유료 사용자는 어디에 있나iOS·Android, 혹은 기존 웹 사용자의 모바일 전환Windows·macOS에서 일하는 사용자
팀의 네이티브 역량은 어디에 있나TypeScript 중심, Swift/Kotlin은 필요한 기능만 보강Rust를 제품 로직·로컬 처리에도 쓰고 싶음
가장 어려운 시스템 기능은 무엇인가카메라·푸시·공유·위치·모바일 권한파일 시스템·트레이·전역 단축키·로컬 프로세스
배포의 긴장점은 어디에 있나앱스토어 심사와 모바일 권한 UXOS별 설치·코드 서명·업데이터·WebView 호환성
‘한 코드’의 의미는 무엇인가웹·PWA·모바일 UI를 빠르게 같이 운영데스크톱과 모바일 앱 계층을 Rust 중심으로 통제

표의 빈칸이 많다면, 먼저 PWA 또는 반응형 웹을 완성하는 편이 낫습니다. BBC Children’s Games는 Capacitor로 iOS·Android·웹을 하나의 컴포넌트 세트에서 운영한 사례를 공개했습니다. 이 사례가 보장하는 것은 성과가 아니라 방향입니다. 웹이 제품의 중심일 때는 웹을 원본으로 두는 편이 유지보수 비용을 줄일 수 있다는 점입니다. BBC 사례 보기

Capacitor를 먼저 고를 때: 모바일이 제품의 현장일 때

Capacitor는 기존 웹 앱을 iOS·Android 앱으로 가져가는 경로가 짧습니다. 공식 문서 기준으로 기존 JavaScript 프로젝트에 추가할 수 있고, 웹·iOS·Android 사이에서 쓸 API와 플러그인 인터페이스를 제공합니다. Capacitor 시작하기

그래서 다음 제품에 특히 잘 맞습니다.

  • 모바일 알림, 카메라, 공유 시트가 재방문을 만드는 서비스
  • 모바일 웹과 앱의 정보 구조가 거의 같은 콘텐츠·커뮤니티·예약 제품
  • 웹팀이 먼저 작게 검증하고 필요한 네이티브 기능만 붙이려는 팀
  • 스토어 출시는 필요하지만 데스크톱 OS 통합이 아직 핵심이 아닌 경우

다만 웹을 감싼다는 말이 심사와 품질까지 대신해 주지는 않습니다. 로그인 리디렉션, 카메라 권한 거절, 키보드가 올라온 상태의 레이아웃, 안전 영역, 백그라운드 복귀는 브라우저와 다른 테스트 항목입니다.

SvelteKit을 쓴다면 서버 렌더링과 앱 번들을 같은 빌드로 억지로 맞추려 하지 말고, 앱에서 필요한 경로와 데이터를 분리해야 합니다. SvelteKit의 정적 어댑터는 완전히 정적화 가능한 앱을 대상으로 하며 SPA fallback을 쓸 때의 제약도 문서에 적혀 있습니다. SvelteKit 페이지 옵션

Capacitor의 장점은 네이티브를 피하는 데 있지 않습니다. 네이티브 코드를 제품 전체가 아니라 플러그인 경계에 가둘 수 있다는 데 있습니다. 웹에서는 Browser API, 앱에서는 네이티브 플러그인을 호출하더라도 UI는 share(text, url) 하나만 보게 만들면 됩니다.

Tauri를 먼저 고를 때: 데스크톱이 큰 화면이 아닐 때

Tauri의 매력은 작은 바이너리라는 숫자보다, 데스크톱 시스템 통합을 제품 구조 안에 둘 수 있다는 점입니다. Tauri는 시스템 WebView를 사용하며 프론트엔드는 JavaScript로, 필요한 애플리케이션 로직은 Rust로 구현할 수 있습니다. Tauri 공식 개요

다음 신호가 강하면 Tauri가 더 설득력 있습니다.

  • 사용자가 매일 파일을 읽고 쓰거나 로컬 DB·암호화된 캐시를 다룹니다.
  • 트레이, 전역 단축키, 여러 창, 인쇄, 백그라운드 작업이 핵심 흐름입니다.
  • 네트워크가 끊겨도 중요한 작업을 처리해야 합니다.
  • Rust로 묶을 수 있는 로컬 계산·동기화·보안 경계가 이미 제품 안에 있습니다.

Tauri 2는 모바일도 지원하므로 “그럼 한 도구로 끝나겠네”라고 생각하기 쉽습니다. 하지만 팀의 언어 경계까지 하나로 만들지는 않습니다. Tauri의 모바일 플러그인은 Kotlin/Java와 Swift 코드를 연결합니다. Tauri도 공식 릴리스 글에서 모바일 개발 경험을 계속 개선 중이라고 설명합니다. Tauri 2.0 모바일 지원

Tauri를 모바일까지 한 번에 쓰는 선택은 설치 파일을 작게 만들기 위해서가 아니라 Rust·Swift·Kotlin 경계를 관리할 이유와 역량이 있을 때 적합합니다. 로컬 우선 앱, 하드웨어 연동, 보안이 중요한 앱에는 이 비용이 정당할 수 있습니다. 그렇지 않다면 모바일의 첫 출시는 Capacitor, 데스크톱 고유 기능은 Tauri처럼 역할을 나누는 편이 더 빠를 수 있습니다.


플러그인 하나가 만드는 숨은 비용

도구 선택에서 가장 자주 빠지는 함정은 “플러그인이 있다”와 “우리 앱에서 안전하게 운영된다”를 같은 말로 보는 것입니다.

플러그인을 붙이기 전에는 네 가지를 확인하세요.

  1. iOS·Android·데스크톱 중 어디까지 실제로 지원하는가
  2. 권한 거절·제한·재요청 상태를 제품 UI가 처리하는가
  3. 웹 fallback이 있는가, 없다면 웹에서는 어떤 대체 경험을 보이는가
  4. 버전 업과 스토어 심사 때 책임질 사람이 있는가

Tauri는 이 경계를 권한·스코프·capability로 제한하도록 설계했습니다. 실행 중인 WebView가 코어 명령에 접근하는 범위는 애플리케이션 설정에서 제한합니다. Tauri 보안 모델플랫폼별 capability 예시는 데스크톱 기능을 모바일에 실수로 넓히지 않는 방법을 보여 줍니다.

Capacitor에서도 원칙은 같습니다. 직접 플러그인을 만들면 Swift와 Java/Kotlin 구현을 운영하게 되고, 기존 플러그인을 쓰면 호환성·권한·유지보수 상태를 함께 떠안습니다. 플러그인 수보다 제품이 의존하는 플러그인을 명확한 경계로 유지할 수 있는가가 더 중요한 질문입니다.

업데이트는 배포 기능이 아니라 신뢰 기능이다

웹은 배포하면 바로 바뀝니다. 앱은 그렇지 않습니다. 이 차이를 설계 초기에 받아들이지 않으면 긴급 수정 때 가장 취약한 우회로를 만들게 됩니다.

  • iOS·macOS App Store 앱은 스토어 정책과 심사 흐름 안에서 업데이트를 계획해야 합니다.
  • Apple은 앱이 기능을 바꾸는 코드를 내려받아 실행하는 방식을 제한하고, Mac App Store 배포 앱은 스토어 업데이트를 사용해야 한다고 명시합니다. Apple App Review Guidelines 2.5.2
  • Tauri의 데스크톱 업데이터는 업데이트 아티팩트를 만들고 확인하는 경로를 제공하지만, 코드 서명·업데이트 서버·채널 분리는 제품팀의 운영 책임입니다. Tauri Updater 문서
  • Android·iOS에서 웹 번들을 원격으로 바꾸는 도구를 도입한다면, 편의성보다 먼저 각 스토어 정책과 변경 범위를 검토해야 합니다.

“원격 업데이트가 가능한가?”보다 “어떤 변경이 승인된 바이너리 릴리스 없이 나가도 되는가?”를 문서로 합의하는 편이 안전합니다. 서버가 내려주는 콘텐츠·실험 설정과 앱 기능을 바꾸는 코드의 선을 분명히 그어 두세요.

보안과 QA는 WebView 버전 표에 넣는다

둘 다 웹 기술을 쓰지만 사용자가 실제로 만나는 엔진은 OS와 기기에 따라 다릅니다. Tauri는 Windows에서 WebView2, Android에서 시스템 WebView, macOS·iOS에서 WebKit을 사용한다고 문서화합니다. Tauri WebView 버전 문서

이 차이는 데스크톱에서 잘 됐다고 모바일에서 잘 된다는 뜻이 아님을 보여 줍니다. 특히 인증 팝업, 파일 다운로드, 비디오 재생, CSS 입력 처리, IndexedDB와 저장 공간은 실제 기기 행렬에서 확인해야 합니다.

릴리스 전 확인왜 필요한가
Android 구형 WebView + 최신 기기렌더링과 인증 흐름이 Chromium 버전에 영향받음
iPhone 실제 기기에서 키보드·안전 영역데모에서 보이지 않던 입력·스크롤 문제가 나오기 쉬움
Windows WebView2·macOS WebKit데스크톱도 같은 브라우저가 아님
권한 거절 후 재진입처음 한 번 허용된다는 가정이 실제 이탈을 만듦
오프라인·저장소 부족·업데이트 직후앱의 신뢰는 실패 순간에 결정됨

Tauri에서 CSP는 별도 설정해야 하며 외부 스크립트 같은 원격 콘텐츠는 공격면을 넓힐 수 있다고 공식 문서가 경고합니다. Tauri CSP 가이드의 권고대로 출처를 좁히고, 앱 내부 WebView를 “그냥 작은 브라우저”로 취급하지 않는 것이 좋습니다.


지금 시작한다면: 세 가지 현실적인 경로

모바일 검증이 먼저라면

웹/PWA를 원본으로 두고 Capacitor로 iOS·Android를 냅니다. 공유 UI·도메인 로직을 패키지로 빼고 네이티브 기능은 platform/* 아래 어댑터로 감쌉니다. 데스크톱 수요가 확인되기 전에는 설치 파일을 약속하지 않습니다.

로컬 우선 데스크톱이 먼저라면

Tauri로 데스크톱의 핵심 작업을 완성합니다. 파일·동기화·암호화·시스템 기능은 Rust 경계에서 제한하고, 프론트엔드는 WebView 밖에서도 실행 가능한 API 계약을 유지합니다. 모바일은 단순한 복제본이 아니라 어떤 작업을 가져갈지 정한 뒤 별도 PoC로 검증합니다.

두 플랫폼이 모두 첫날부터 중요하다면

공유 UI와 도메인 패키지는 하나로 두되, apps/web, apps/mobile, apps/desktop의 릴리스 책임은 분리합니다. 모바일에는 Capacitor, 데스크톱에는 Tauri를 쓰는 조합도 충분히 현실적입니다. 프레임워크가 두 개라서 실패하는 것이 아니라, 포트 인터페이스 없이 각 화면이 런타임을 직접 알 때 실패합니다.

커뮤니티에서 반복되는 경험도 이 판단을 뒷받침합니다. Tauri 모바일의 빌드·권한·디버깅이 거칠다는 의견이 있는 반면 데스크톱·웹 사이의 높은 코드 공유를 보고하는 사례도 있습니다. 이는 성능 수치가 아닌 현장 신호로만 봐야 합니다. 모바일 경험 토론, 웹·데스크톱 공유 사례, SvelteKit을 여러 대상으로 구성하려는 토론을 함께 읽으면 단일 코드베이스의 성공 조건이 프레임워크 이름보다 경계 설계에 있다는 점이 더 분명해집니다.

가장 작은 설치 파일보다 가장 작은 예외 목록

Capacitor와 Tauri 중 하나가 모든 웹팀의 정답은 아닙니다.

모바일을 빨리 검증해야 한다면 Capacitor가 자연스럽습니다. 데스크톱의 OS 통합과 로컬 처리 자체가 제품이라면 Tauri가 강합니다. 둘 다 필요한 경우에는 UI와 규칙은 공유하되, 권한·네이티브 모듈·서명·심사는 각 플랫폼의 일로 남겨야 합니다.

좋은 크로스플랫폼 구조의 목표는 코드 줄 수를 최소화하는 것이 아닙니다. 제품을 바꿀 때 어느 파일을 바꿔야 하는지, 실패했을 때 어느 플랫폼이 책임져야 하는지를 최소화하는 것입니다. 이 기준으로 보면 도구 선택은 덜 감정적이고, 다음 출시에도 흔들리지 않습니다.


참고한 자료

최신 글