노트

웹뷰 브리지(웹↔네이티브 통신)

WebView JavaScript Bridge

프런트엔드#browser#architecture · 연결된 개념 3개

쉽게 말하면

웹뷰 브리지는 앱 안에 끼워 넣은 웹 화면과 앱 본체가 서로 말을 거는 통로예요. 한집에 살지만 방이 나뉜 두 사람이 쪽지로 부탁을 주고받듯, 웹이 못 하는 푸시나 리뷰 팝업을 앱에 부탁해요.

비유가 깨지는 곳 쪽지를 보내도 상대가 준비됐는지는 몰라요. 앱은 SPA 화면 전환도, 웹이 준비됐는지도 모르거든요. 그래서 웹이 ready 메시지를 먼저 보내는 핸드셰이크로 맞추고, 허용 출처 목록으로 아무나 못 부르게 해요.

웹뷰(WebView)는 앱 화면에 끼워 넣는 "주소창 없는 브라우저 한 장"이다. iOS는 WKWebView, Android는 WebView를 쓴다. 화면 대부분을 웹으로 그리는 하이브리드 앱은 웹을 배포하면 앱도 바뀐다는 장점이 있지만, 푸시 알림 수신이나 앱 리뷰 팝업, 다른 앱 열기처럼 웹이 못 하는 일은 네이티브가 해야 한다. 웹뷰 안의 JS(JavaScript)와 바깥의 네이티브 코드는 변수도 함수도 공유하지 않으므로, 플랫폼이 열어 둔 통로로만 말을 주고받는다.

통로 네 가지

통로방향하는 일
User-Agent 표식앱 → 웹·서버"앱 안에서 열렸다"는 신분 표시
메시지 핸들러웹 → 앱웹이 네이티브 기능을 부탁
스크립트 실행앱 → 웹네이티브가 웹에 값을 주입
URL(딥링크·쿼리)양쪽화면 이동 자체가 메시지

User-Agent 표식. iOS는 WKWebViewConfiguration.applicationNameForUserAgent로 기본 UA(User-Agent) 뒤에 MyApp/ios/4.2.0 같은 토큰을 붙일 수 있다. 웹뷰가 보내는 모든 요청에 붙으므로 웹과 서버가 같은 정규식으로 앱 여부를 판단한다. 다만 서버 렌더링 시점에는 클라이언트 전용 정보(브리지 존재 여부 등)를 알 수 없고, 포맷을 바꾸면 판별 코드를 모든 곳에서 함께 고쳐야 한다.

웹 → 앱. iOS는 WKUserContentController에 이름을 붙여 WKScriptMessageHandler를 등록하면, 웹에서 window.webkit.messageHandlers.<이름>.postMessage(값)으로 보낼 수 있다. 답을 받아야 하면 프로미스로 돌려주는 WKScriptMessageHandlerWithReply가 있다. Android는 2026년 현재 WebViewCompat.addWebMessageListener를 권한다. 허용할 출처 목록을 정해 두어 다른 사이트나 끼어든 iframe이 네이티브 코드를 부르지 못하게 할 수 있기 때문이다. 오래된 addJavascriptInterface는 출처 검사가 없고 모든 프레임에 노출돼 권하지 않는다.

// 웹 쪽: 앱이면 네이티브로, 아니면 웹 SDK로
if (window.webkit?.messageHandlers?.analytics) {
  window.webkit.messageHandlers.analytics.postMessage({ event, params })
} else {
  webAnalytics.track(event, params)
}

이벤트를 정의하는 쪽을 웹 한 곳에 두고 네이티브는 배달만 하면, 이벤트를 바꿀 때 앱 심사 없이 웹 배포로 끝난다.

앱 → 웹. evaluateJavaScript(iOS)·evaluateJavascript(Android)로 웹뷰 안에서 JS 문자열을 실행하고 결과값도 받아 온다. 페이지가 로드될 때마다 자동 실행할 스크립트는 iOS의 WKUserScript로 심는다. 문자열을 이어 붙여 코드를 만들 때는 값을 JSON으로 직렬화해 따옴표 깨짐과 주입을 막는다.

URL. 앱 밖에서 들어오는 링크는 iOS 유니버설 링크(Universal Links)와 Android 앱 링크(App Links), 또는 myapp:// 같은 커스텀 스킴으로 앱을 연다. 앱은 받은 URL을 웹 주소로 바꿔 웹뷰에 로드한다. 푸시 알림을 탭했을 때도 링크에 쿼리를 붙여 넘기면 웹이 그걸 읽고 처리한다. 반대로 웹뷰가 이동하려는 URL은 네이티브가 이동 직전에 검사해 외부 앱이나 시스템 브라우저로 돌릴 수 있다.

두 세계의 시차

  • SPA 화면 전환을 앱은 모른다. 웹뷰가 아는 건 문서를 새로 받는 진짜 이동뿐이다. history.pushState로 주소만 바꾸는 SPA(Single-Page Application) 이동은 신호가 오지 않는다. 앱이 경로를 알아야 하면 웹이 메시지 핸들러로 직접 알리게 한다. pushState를 덮어쓰는 몽키패치는 replaceState를 놓치거나 라우터 구현이 바뀌면 조용히 깨진다
  • 앱은 웹이 준비됐는지 모른다. 로드 완료 콜백은 HTML 파싱 직후라, React가 전역 함수를 붙이기 전일 수 있다. 폴링과 재시도로 버틸 수도 있지만, 정석은 웹이 준비됐을 때 "ready" 메시지를 먼저 보내고 앱이 그때 값을 넣는 웹 주도 핸드셰이크다. 받은 값은 웹이 저장해 두면 다음 기회에 복구할 수 있다

앱 배포는 심사와 사용자 업데이트를 기다려야 하고 웹 배포는 즉시라서, 빈틈은 웹 쪽에서 메우는 쪽으로 자연스럽게 기운다.

safe area

노치와 홈 인디케이터를 피하는 방법은 둘이다. 네이티브가 웹뷰 자체를 safe area 안으로 줄이거나, 웹뷰를 화면 전체에 깔고 웹이 CSS로 피한다. 후자는 <meta name="viewport" content="width=device-width, viewport-fit=cover">로 화면 끝까지 그리게 한 뒤 env(safe-area-inset-*)로 여백을 준다.

.bottom-bar {
  padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
}

웹뷰 디버깅은 iOS에서 isInspectable을 켜면 Safari 웹 인스펙터가 붙는다. 운영 빌드에서는 끄는 것이 안전하다. 출처 개념은 동일 출처 정책, 서버 렌더링과 클라이언트 값의 어긋남은 하이드레이션을 본다.

출처: Apple — WKScriptMessageHandler · Apple — applicationNameForUserAgent · Apple — evaluateJavaScript · Android — Access native APIs with JavaScript bridge · MDN — env() · WebKit — Designing Websites for iPhone X

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • WebGPU

    웹페이지에서 GPU로 그래픽과 범용 계산을 하는 브라우저 API. WebGL의 후속으로, 브라우저 안에서 ML 모델을 돌리는 기반이 된다.

  • Activity와 숨겨진 라우트 보존

    Activity는 React 19.2의 컴포넌트로, UI를 언마운트하지 않고 display: none으로 숨기면서 state와 DOM(Document Object Model)을 보존한다. 숨기는 동안 Effect는 정리(cleanup)된다. 예전 이름은 Offscreen이다.

  • loading 경계와 레이아웃 끌어올리기

    Next.js App Router에서 loading.tsx는 같은 세그먼트의 page와 그 아래(하위 layout 포함)를 suspense로 감싸지만, **같은 세그먼트의 layout은 감싸지 않는다**. 그래서 데이터를 기다릴 필요가 없는 공통 UI(탭바, 필터 헤더)를 page에서 layout으로 끌어올리면 스켈레톤에 덮이지 않고 처음부터 실제 모습으로 보인다.

  • Electron 앱 구조

    Electron은 Chromium과 Node.js를 묶어 웹 기술(HTML·CSS·JavaScript)로 데스크톱 앱을 만드는 프레임워크다. 앱은 OS(Operating System) 권한을 가진 메인 프로세스(Main Process) 하나와, 창마다 하나씩 뜨는 렌더러 프로세스(Renderer Process)로 나뉜다.

  • Pages Router에서 App Router로

    App Router는 Next.js 13에서 도입된 app/ 디렉터리 기반 라우터로, 서버 컴포넌트·중첩 레이아웃(Nested Layouts)·스트리밍을 기본으로 한다. pages/ 기반의 Pages Router에서 옮길 때 바뀌는 생각을 정리한다.

보기 옵션