한 코드베이스, 세 채널 동시 출격: Next.js + Capacitor

한 문장 요약
하나의 코드로 웹과 앱(안드로이드/아이폰)을 동시에 만듭니다. 유지보수는 한 곳, 배포는 세 채널.
배경지식 체크: 아래 용어가 낯설면 ‘용어 30초 요약’을 먼저 읽어보세요.
용어 30초 요약
- PWA: 앱처럼 설치되는 웹
- SSR: 서버가 미리 화면을 만들어 속도/검색에 유리
- ISR: 캐시된 화면을 일정 시간마다 새로고침
- Capacitor: 웹을 앱으로 감싸 스토어에 올리게 해주는 도구
- 딥링크/유니버설 링크: 링크로 앱을 바로 여는 방법
왜 단일 코드베이스인가
PWA만으로는 스토어 신뢰·인앱결제·푸시를 모두 담기 어렵습니다. 웹 팀만으로 앱 생태계에 빠르게 진입해야 했습니다.
그림으로 이해
src (web)
↓ build (SSR | export)
out (정적 결과)
↓ copy
apps/www (앱에 탑재)
↓ 래퍼(Android/iOS)
Google Play / App Store (배포)작동 방식(3단계)
- 웹을 빌드합니다(SSR 또는 정적 export).
- 빌드 결과를 앱(www) 폴더로 복사합니다.
- Capacitor로 Android/iOS 래퍼를 열어 스토어에 올립니다.
기술적 의사결정
- Next.js + Capacitor: 웹 코드 90% 재사용, 네이티브 권한만 최소 래핑 → 채택
- BUILD_TARGET=web|export 로 SSR/Static 빌드 분리
- 의사결정 기준: 팀 규모 대비 ‘신뢰 지표’ 상승폭
언제 좋은가
- 소수 인원으로 웹·앱을 동시에 내야 할 때
- 웹과 앱의 화면/경험을 거의 동일하게 유지하고 싶을 때
- 시장 반응을 빠르게 보고 자주 업데이트해야 할 때
주의할 점
- 고성능 네이티브(대형 3D/AR/초고빈도 센서)는 별도 네이티브 고려
- 스토어 심사 요건(ATS, Data Safety, 권한 목적문구) 미리 준비
- 딥링크/앱링크/리다이렉트 주소 일관성 유지
프로젝트 구조
hybrid-app/
├─ web/ # Next.js(App Router) — PWA/SSR/ISR
│ ├─ src/app # 라우트·서버 액션
│ ├─ src/api # API 핸들러(edge/node)
│ ├─ src/lib # 공통 유틸·도메인 로직
│ └─ public | out # 정적 자산/정적 export 결과
├─ apps/ # Capacitor 컨테이너
│ ├─ android | ios # 네이티브 래퍼
│ └─ capacitor.config.ts
└─ 빌드 흐름:
1) web: npm run build:web / build:app
2) copy: web/out → apps/www
3) apps: npx cap copy && npx cap sync → 스토어 빌드빌드/배포 플로우
# Web (SSR)
cd web
npm run build:web && npm run dev # http://localhost:3000
# App (Android)
npm run build:app && npm run copy:to-app
cd ../apps && npx cap copy && npx cap sync
npm run android:build && npm run android
# iOS (준비 필요)
npm run ios경계(Interface) 우선
웹-네이티브 경계를 먼저 정의합니다. Capacitor 플러그인 호출 시그니처를 표준화하고 브라우저 폴백을 제공해 테스트 가능성과 SSR 안전성을 확보합니다.
// device/clipboard.ts (web fallback)
import { Clipboard } from '@capacitor/clipboard';
export async function writeClipboard(text: string) {
try {
await Clipboard.write({ string: text });
} catch {
await navigator.clipboard?.writeText(text);
}
}배경지식 체크: ‘플러그인’은 카메라/푸시처럼 네이티브 기능입니다. 웹에서는 실패할 수 있어 try/catch + 웹 폴백을 같이 둡니다.
Next.js + Capacitor 핵심 라이브러리/사용법
- @capacitor/app: 앱 상태/딥링크(appUrlOpen) 이벤트
- @capacitor/push-notifications: 권한 요청/토큰 발급/알림 수신
- @capacitor/browser: 인앱 브라우저(OAuth 리다이렉트 도움)
- @capacitor/clipboard: 복사/붙여넣기
- @capacitor/preferences(또는 community secure storage): 민감 정보 보관
- next-pwa: PWA 서비스워커/오프라인/설치 지원
// platform-guard.ts (SSR/웹/앱 안전 가드)
import { Capacitor } from '@capacitor/core';
import { App } from '@capacitor/app';
export function getPlatform() {
// SSR에서는 window가 없음
if (typeof window === 'undefined') return 'server';
return Capacitor.getPlatform(); // 'ios' | 'android' | 'web'
}
export function listenDeepLink(onOpen: (url: string) => void) {
if (typeof window === 'undefined') return; // SSR 가드
App.addListener('appUrlOpen', ({ url }) => onOpen(url));
}// push.ts (푸시 최소 예시)
import { PushNotifications } from '@capacitor/push-notifications';
export async function initPush() {
const perm = await PushNotifications.requestPermissions();
if (perm.receive !== 'granted') return;
await PushNotifications.register();
PushNotifications.addListener('registration', (token) => {
// 서버에 token.value 전송
});
PushNotifications.addListener('pushNotificationReceived', (n) => {
// 알림 수신 시 UX 처리
});
}// next.config.js (next-pwa 간단 설정)
const withPWA = require('next-pwa')({
dest: 'public',
disable: process.env.NODE_ENV === 'development',
});
module.exports = withPWA({});SSR 안전 수칙
- 플러그인 호출은 항상 클라이언트에서만(SSR 가드: typeof window 체크)
- 네이티브 전용 코드는 dynamic import({ ssr: false })로 분리
- 항상 웹 폴백을 준비(클립보드/브라우저/알림)
난제 & 해결
- Google OAuth 리다이렉트: 앱 스킴 등록(kr.zolbo.app:/oauth2redirect)으로 해결
- WS 끊김: visibilitychange/foreground에 재구독 + 지수 백오프
- 번들 용량: route-level chunk·이미지 최적화·dynamic import
- 심사 리젝: ATS/권한 최소·개인정보 고지/리다이렉트 일관성 체크리스트
임팩트(지표)
- 고객 신뢰 설문 48% → 79%
- 가입→활성 전환율 30% → 56%
- 출시 리드타임 1.0x → 0.7x(웹 플로우 재사용)
운영 체크리스트
- PWA: manifest/서비스워커/오프라인 전략
- Android: keystore/권한/IAP 선언
- iOS: Provisioning/Capabilities/ATS
- 공통: SSO redirect, App/Universal Links
한 팀으로 네이티브 신뢰를 가져오는 가장 짧은 길.
핵심 배움
- 코드는 재사용하고, 경험은 네이티브처럼
- 결정 기준은 비용이 아니라 ‘신뢰 지표’
- 웹 팀만으로 앱 생태계 진입 가능