TL;DR: 하이브리드 앱은 웹 기술 + 네이티브 셸(WebView) 구조다. 웹의 생산성과 네이티브 접근성을 절충한다. 카메라·GPS 등은 브리지로 호출한다. 고성능 그래픽·초저지연은 네이티브가 유리하다.
| 방식 | 기술 스택 | 배포 | 강점 | 약점 | 적합 사례 |
|---|---|---|---|---|---|
| 웹 앱 | HTML/CSS/JS, React/Vue | 웹 | 개발·배포 빠름 | 권한·오프라인 제약 | 랜딩, 대시보드 |
| 네이티브 앱 | Kotlin/Swift | 스토어 | 성능·UX 최고 | 비용·이중코드 | AR, 3D, 고난도 센서 |
| 하이브리드 앱 | 웹 + 네이티브 셸(WebView) | 스토어 | 재사용·권한 접근 | 브리지 오버헤드 | B2B, 폼·미디어 업로드 |
┌───────────────────────────────┐
│ Native App Shell │ (Android/iOS 프로젝트)
│ ┌─────────────────────────┐ │
│ │ WebView │ │
│ │ (React/Vue 빌드 결과) │ │
│ └─────────────────────────┘ │
│ ↑ JS-Native Bridge ↓ │
│ Plugins (Camera, GPS, Push) │
└───────────────────────────────┘
앱 셸 실행 → 내부 WebView가 웹 자원을 로드
https://app.example.com) 또는 앱에 내장된 정적 파일UI 상호작용은 DOM + JS로 처리
카메라·GPS 등 네이티브 기능 요청 시 JS가 플러그인 API를 호출
브리지가 네이티브 권한·API를 실행하고 결과를 JS로 반환
일반 렌더링은 브라우저와 동일. 권한·센서만 브리지를 거친다.
하이브리드를 넓게 보면 3갈래로 나뉜다.
본 글의 “하이브리드”는 1) WebView 래퍼형을 지칭한다.
코드 재사용: iOS/Android 동일 웹 코드로 동시 대응
권한 접근: 카메라·GPS·파일·푸시 등 플러그인으로 호출
배포 경로: 스토어 등록. 릴리스·버전코드 관리 필요
업데이트:
하이브리드가 유리
네이티브가 유리
// Camera
import { Camera, CameraResultType } from '@capacitor/camera';
const photo = await Camera.getPhoto({ resultType: CameraResultType.Uri, quality: 80 });
// Geolocation
import { Geolocation } from '@capacitor/geolocation';
const pos = await Geolocation.getCurrentPosition();
// Secure Preferences
import { Preferences } from '@capacitor/preferences';
await Preferences.set({ key: 'accessToken', value: '...' });
JS에서 플러그인 API를 호출하면, 내부적으로 네이티브 권한 + 기능이 실행되고 결과가 Promise로 반환된다.
| 전략 | 장점 | 단점 | 추천 상황 |
|---|---|---|---|
| 원격 URL 로딩 | 즉시 업데이트, 핫픽스 용이 | 네트워크 의존 | 상시 온라인 서비스 |
| 내장(번들) 로딩 | 오프라인 보장, 빠른 첫 로드 | 앱 업데이트 필요 | 오프라인 우선 |
| 혼합 | 둘의 장점 결합 | 구현 복잡 | 대부분 실무 권장 |
TL;DR: 하이브리드 앱은 “웹을 앱처럼 감싸는 과정”이다.
개발 순서는 웹 완성 → Capacitor로 래핑 → 플러그인 연결 → 테스트 → 스토어 등록.
React·Vue·Spring Boot 조합에서도 완벽히 호환된다.
하이브리드 앱 개발은 크게 6단계로 나뉜다.
| 단계 | 목표 | 도구 |
|---|---|---|
| ① 웹앱 구현 | React/Vue로 UI 개발 | VSCode, Vite/CRA |
| ② 백엔드 API | Spring Boot로 REST API 구현 | IntelliJ |
| ③ Capacitor 초기화 | 웹을 네이티브 셸로 감싸기 | Capacitor CLI |
| ④ 플러그인 연동 | 카메라·위치 등 권한 기능 추가 | Capacitor Plugins |
| ⑤ 빌드·테스트 | APK/AAB 생성 후 테스트 | Android Studio |
| ⑥ 스토어 배포 | Google Play 등록 | Play Console |
React 기준:
npm install
npm run build # 결과물: build/ 폴더
npm install @capacitor/core @capacitor/cli
npx cap init myapp com.example.myapp --web-dir=build
npx cap add android
npx cap add ios # 선택
생성된 /android, /ios 폴더는 네이티브 프로젝트다.
import { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.example.myapp',
appName: 'My Hybrid App',
webDir: 'build',
server: {
url: 'https://app.example.com', // 원격 웹 로딩 (추천)
cleartext: false
}
};
export default config;
개발 단계에서는
url을http://192.168.x.x:5173로 설정하면 실기기에서 바로 연결 가능.
npm install @capacitor/camera
npx cap sync
import { Camera, CameraResultType } from '@capacitor/camera';
const photo = await Camera.getPhoto({
resultType: CameraResultType.Uri,
quality: 80
});
npm install @capacitor/geolocation
npx cap sync
import { Geolocation } from '@capacitor/geolocation';
const pos = await Geolocation.getCurrentPosition();
console.log(pos.coords.latitude, pos.coords.longitude);
npm install @capacitor/preferences
npx cap sync
import { Preferences } from '@capacitor/preferences';
await Preferences.set({ key: 'token', value: 'abcdef' });
const { value } = await Preferences.get({ key: 'token' });
모든 플러그인은
Promise기반이며, 네이티브에서 실행 후 결과를 JS로 반환한다.
Spring Boot는 기존 REST API 형태 그대로 사용한다.
@PostMapping("/upload")
public ResponseEntity<?> upload(@RequestPart("file") MultipartFile file) {
// 저장 로직
return ResponseEntity.ok().build();
}
React(프론트)에서 업로드:
const form = new FormData();
form.append('file', await (await fetch(photo.webPath!)).blob(), 'photo.jpg');
await fetch(`${import.meta.env.VITE_API_BASE}/upload`, {
method: 'POST',
body: form
});
@Bean
CorsConfigurationSource cors() {
var c = new CorsConfiguration();
c.setAllowedOrigins(List.of("http://localhost:5173", "capacitor://localhost"));
c.setAllowedMethods(List.of("*"));
c.setAllowedHeaders(List.of("*"));
c.setAllowCredentials(true);
var s = new UrlBasedCorsConfigurationSource();
s.registerCorsConfiguration("/**", c);
return s;
}
npm run build
npx cap copy
npx cap open android
Build > Build APK(s) → app-debug.apkadb install app-debug.apkimport { App } from '@capacitor/app';
App.addListener('backButton', ({ canGoBack }) => {
if (canGoBack) window.history.back();
else App.exitApp();
});
Android Studio →
Build > Build Bundle(s) / APK(s) > Build Bundle(s)
→ app-release.aab
Build > Generate Signed Bundle / APK.jks 키스토어 생성 후 서명| 항목 | 전략 |
|---|---|
| 버그 수정 | 웹 원격 로딩 시 즉시 반영 |
| 새 권한 추가 | 스토어 재배포 필요 |
| 데이터베이스/API | Spring Boot 배포 자동화(Docker·CI/CD) |
| 성능 모니터링 | Android Studio Profiler / WebView Inspector |
| 크래시 리포트 | Firebase Crashlytics 연동 |
CameraResultType.Uri + Blob 변환@capacitor/preferences 또는 외부 secure-storage| 항목 | 핵심 요약 |
|---|---|
| 환경 구성 | React/Vue 빌드 → Capacitor init → 플랫폼 추가 |
| 네이티브 기능 | 플러그인 설치 후 JS 호출 |
| 테스트·빌드 | Android Studio로 APK/AAB 생성 |
| 배포 | Play Console 내부 테스트 → 프로덕션 릴리스 |
| 유지보수 | 웹 수정은 즉시 반영, 권한·플러그인은 앱 업데이트 필요 |