[rn] 코드 인젝션으로 웹뷰 컨트롤하기
![Cover Image for [rn] 코드 인젝션으로 웹뷰 컨트롤하기](https://cdn.hashnode.com/res/hashnode/image/upload/v1762932116538/c5c9cf64-cc3f-47a7-a9fc-13f61dbc6cab.png?w=1600&h=840&fit=crop&crop=entropy&auto=compress,format&format=webp)
#RN , #AI Tools, #react-native-webview
글의 순서
문제 상황
요구사항 파악하기
괜찮은 해결 방법 찾기
몰랐던 사실 & 느낀 점
문제상황
어느 날 앱 개발자로부터 도착한 메세지
👨💻앱 개발자 ==> ✉️ ==> 👩💻웹 개발자
Next routing 사용해서 화면 전환 하면 client-side 화면 이동만 되나요?
Exhibition 페이지에서 화면 이동할때 캐치해야하는데 캐치가 안되어서요.
전시(Exhibition) 페이지에서 라우팅시 페이지를 Reload 해줄 수 없나요?
👨💻앱 개발자가 이런 부탁을 한 이유
앱 일부 페이지가 웹뷰로 되어있음
Before: 그 동안은 Privacy Policy와 같이 사용자 상호작용이 없는 페이지만 웹뷰로 사용했고, Header나 Footer 등은 가려서 사용자가 웹뷰 안에서 페이지 이동할 우려가 없었습니다.

After: 그런데 작품들을 전시 형식으로 보여주는 Exhibition 페이지를 앱에서 웹뷰로 보여주게 되었습니다. Exhibition페이지 내부에서는 페이지 이동을 트리거하는 인터랙션 요소가 많아서, 해당 요소를 통해 다른 페이지로 이동하지 않도록 막아야할 필요가 생겼습니다

이 때문에 앱 개발자가 페이지 이동을 감지해 막으려고 한 것이죠.
- 👨💻💭 “View Details 클릭 > 페이지 이동 > 감지해서 막아야지”
그런데 생각대로 되지 않았습니다.
서버사이드 렌더링일때만 페이지 이동 감지 가능한데, Next.js의 라우터는 클라이언트 사이드 렌더링을 하기 때문이죠.
👨💻❓”그러면 어떻게 감지하지?”
👨💻❗️”Exhibition 페이지에서만 서버사이드 렌더링으로 해달라고 할까?”
👨💻✉️ ”Exhibition 페이지에서 라우팅할 때, 페이지를 Reload 해줄 수 없나요?”
문제 해결
👩💻웹 개발자: 요구사항 파악 => 문제 해결 방향 잡기
👩💻💭 “문제가 되는 시나리오를 모두 생각해보자”
Exhibition 페이지에서 문제가 되는 상황, 즉 화면 전환을 캐치 해야 하는 상황은 아래와 같다.

페이지 이동이 일어나는 시나리오1: 작품 클릭
작품을 누르면 작품 상세 모달이 뜨고, 거기서 버튼을 누르면 화면이 이동됩니다.
1. Detail 버튼: 작품 상세 페이지로 이동 -> 웹 페이지 이동
2. Buy 버튼: 구매 페이지로 이동 -> 웹 페이지 이동
페이지 이동이 일어나는 시나리오2: Exit 버튼 클릭
- Exhibition 페이지 내부의 Exit 버튼 클릭 -> 웹 페이지 뒤로가기 발생
👩💻💭 “요구사항을 다시 분석해보자”
나에게 주어진 요구사항: “앱에서 페이지 변경을 감지할 수 있도록 Reload 하기"
그런데 페이지 이동을 캐치하는 이유를 다시 생각해보면
웹뷰의 페이지 이동을 다른 행동으로 대체하기 위해서 임
- ex: (본래 동작) 웹 상품 디테일 화면으로 이동 => (대체) 앱의 상품 디테일 화면으로 이동
앱에서 Routing을 핸들링 할 수 있으면 문제가 해결 된다는 사실을 알 수 있습니다.
이제 방향은 잡았으니, 해결 방법을 탐구해봅니다.
괜찮은 해결 방법 찾기
해결책 1: JavaScript Injection
👩💻💭 “익숙한 문제가 아니니까 Claude AI에게 일반적인 해결책을 물어보자”
👩💻❓ “JavaScript Injection을 사용하라고?”
👩💻💭 “알려준 답안을 좀 고치면, 버튼 클릭 동작을 인터셉트 할 수 있겠어“
해결책 2: Query Params
사실 가장 먼저 머리속에 떠오른 방법은 Privacy Policy 페이지에서 header와 footer를 가리는 데 사용했던 Query Params로 fullReload를 컨트롤 하는 방법입니다.

Query Params
fullReload가true인 경우에만router.push대신window.location.href를 사용하여 전체 리로드 일어나게 분기하는 방법입니다
const fullReload = queryParams.fullReload === 'true';
if (fullReload) {
// fullReload가 true인 경우 전체 페이지 리로드 w;
window.location.href = path;
} else {
// 그 외의 경우 router.push 사용
router.push(path);
};
동작은 하지만 좋은 방법이 아닌 것 같아 사용하지 않았습니다.
좋은 방법이 아닌 이유1: 웹 프론트엔드에서 처리해야하는 쿼리 파라미터가 추가되어 복잡도가 높아짐
좋은 방법이 아닌 이유2: 웹뷰 진입시 해당 쿼리를 달고 들어오면 무조건 페이지 변경 시 Full Reload가 일어나게 됨(Full Reload를 섬세하게 선택 적용하는 것이 불가)
좋은 방법이 아닌 이유3: 웹 프론트엔드에서 이 코드가 왜 작성되었으며 어디에서 쓰이는지 코드만 보고 파악이 어려움.(앱 이슈를 웹 단에서 해결)
좋은 방법이 아닌 이유4: 3과 같은 이유로 웹 개발자가 실수로 분기를 삭제하거나 쿼리 파라미터를 무시하면 쉽게 깨짐
해결방법 상의하기
👩💻✉️ “ 👨💻님, 말씀하신 대로 Exhibition 페이지에서 리로드하는 방식을 사용해도 동작은 되는데 더 괜찮은 방법이 있는 것 같아서요.”
👩💻✉️ “ JavaScript Injection을 사용하면 버튼 클릭했을때의 동작을 앱에서 컨트롤 할 수 있을 것 같아요! 제가 조금 구현해서 올려놨는데 한번 확인해주실 수 있나요?”
(생략: 리로드보다 Javascript Injection이 더 좋은 이유 4가지 설명)
👨💻✉️ “👩💻님, 코드 확인해보았는데 말씀하신 방법이 더 좋은 방법인 것 같아요! 👍 이 방법으로 가시죠”
👩💻✉️ “좋습니다.👍 그런데 앱팀은 다른 태스크로 바쁘신 것 같은데, 웹팀은 지금 한가해서요. 제가 구현까지 해볼까요?”
👨💻✉️ “그래주시면 감사하죠!”
구현하기
해결책2(JavaScript Injection)의 구현
Claude AI가 일반적인 해결책이라고 알려준 JavaScript Injection으로 구현해보기로 합니다.
Claude AI가 알려준 것은 버튼 클릭의 동작을 인터셉트 하는 것입니다.
작품 클릭 > 버튼 클릭 > 페이지 이동
exit 버튼 클릭 > 페이지 이동
👩💻💭 “그런데 다시 한번 생각해보면”
작품 클릭 > 버튼 클릭 > 페이지 이동
exit 버튼 클릭 > 페이지 이동
👩💻💭 “버튼 클릭을 인터셉트 하는 것보다, 페이지 이동을 관장하는 Router의 동작을 인터셉트 하는 것이 더 적절할 것 같네”
“이것이 가능할까?”
가능여부를 Claude에게 물어보고 아래와 같은 결과물을 도출할 수 있었습니다.
const injectedJavaScript = `
(function() {
const overrideRouterPush = function() {
// app인지 확인
window.isNativeApp = true;
// router.push 함수를 저장
const originalPush = window.next.router.push;
// router.push 함수를 변형
window.next.router.push = function(url, as, options) {
// 페이지 이동 컨트롤 대상인지 확인
const shouldInterceptPush = ['/buy', '/artwork'].some(pattern =>
url?.includes(pattern)
);
if (shouldInterceptPush) {
// 컨트롤 대상일때
window.ReactNativeWebView.postMessage(
JSON.stringify({
event: 'routerPush',
data: {
url: url,
},
})
);
return;
} else {
// 컨트롤 대상 아닐때 원래 함수 사용
originalPush.call(this, url, as, options);
}
};
};
window.onload = overrideRouterPush; // 오버라이드 코드가 window 로드 시점에 실행되도록
})();
`;
// 위 injectedJavaScript함수를 Webview 컴포넌트에 넘겨주기
// props: injectedJavaScript, injectedJavaScriptBeforeContentLoaded
<WebView
{...생략}
injectedJavaScript={injectedJavaScript}
injectedJavaScriptBeforeContentLoaded={injectedJavaScript}
/>
그러면 Query Params를 사용하는 방법에서는 불가능했던 선택적용(경우에 따라 기존의 router.push 사용 or 페이지 이동 막음)을 앱에서 컨트롤 가능합니다.
Step1: Javascript Injection으로 Next.js의 router.push를 modify(메세지 전송, 위의 코드 참조)
Step2: onMessage에서 메시지를 받아서 분기처리(아래 코드)
// WebView 컴포넌트에 넘기는 onMessage props의 예시
// 여기서 javascript code injection에서 넘긴 메세지를 받아 처리할 수 있다.
const onMessage = (event) => {
const message = JSON.parse(event.nativeEvent.data) as Message;
if (message.event === 'routerPush') {
const url = getUrl(message.data.url);
if (url === 'artwork') {
// 앱의 아트워크 디테일 페이지로 이동
return false;
}
if (url === 'buy') {
// 앱의 구매 페이지로 이동
return false;
}
}
return true;
};
해결책 1과 해결책 2의 차이
Code Injection을 사용하는 방법에서는 앱에서 필요한 요구사항을 앱에서 직접 작성하므로 혼란의 여지가 적음
프론트 개발자와 소통하지 않고 직접 컨트롤 가능
웹 프론트엔드에서의 실수로 앱에 버그가 생길 우려 적음
몰랐던 사실 & 느낀 점
안드로이드는
injectedJavaScriptBeforeContentLoaded를 붙여야 합니다.- Claude AI 에게 RN 관련 질문할때는 '안드로이드에서 작동하게 하려면 추가할 것은 없어?'라고 한번 더 물어보는 것이 좋을 것 같습니다.
입사 후 1-2개월간 앱과 웹을 모두 작업했는데, 그 이후로 웹 프론프엔드 쪽만 건드렸기 때문에 오랜만에 앱 작업을 했습니다.
- 그러다보니 IOS와 안드로이드를 각각 시뮬레이터와 에뮬레이터로 세팅하는 것도 오랜만에 하다보니 시간이 꽤 걸렸습니다.
요구사항이 있다고 해서 그대로 작업하는 것이 아니라 분석을 해보는 자세가 좋은 것 같고, 시간은 더 걸렸지만 더 나은 방법으로 작업한 것 같습니다.
Humonnom's Tech Blog