SCRUM-14378 | 커밋 수: 23개 | 기간: 2025.06
문제상황
a 태그 클릭 시 전체 페이지가 리로드되는 문제가 있었습니다. SPA 라우팅이 일관되게 적용되지 않아 일부는 클라이언트 라우팅으로, 일부는 서버 요청으로 처리되고 있었습니다. 네이티브 앱과의 연동 시에도 웹뷰 처리 방식이 통일되어 있지 않았습니다.
주요 개선사항
$goWithRouter 전역 함수를 구현하여 모든 페이지 이동을 단일 인터페이스로 통합했습니다. /webapp 경로를 자동으로 감지하여 Vue Router로 이동하도록 처리했고, 기존에 사용되던 data-cmd="open_main_webpage" 속성도 이 함수를 통해 통합 처리하도록 구현했습니다.
50개+ 위치 적용
a 태그 → $goWithRouter 전환
리로드 제거
SPA 전환으로 깜빡임 없는 UX
네이티브 통합
앱/웹 라우팅 일관된 처리
비즈니스 임팩트
페이지 전환 속도가 향상되어 사용자가 체감하는 응답성이 개선되었습니다. 전체 페이지 리로드로 인한 깜빡임이 제거되어 사용자 경험이 향상되었고, 네이티브 앱과의 라우팅 연동 안정성도 확보되었습니다.
기술적 구현 상세
AS-IS
기존에는 a 태그를 사용하여 페이지 이동 시 전체 페이지가 리로드되었습니다.
<!-- 기존 구조 (더미 예시) -->
<template>
<a href="/webapp/home">홈으로</a>
<a href="/webapp/my">마이페이지</a>
</template>
문제점:
전체 페이지 리로드로 인한 깜빡임이 발생했고, SPA의 장점을 활용할 수 없었습니다. 네이티브 앱에서 웹뷰 처리 방식도 일관되지 않았습니다.
TO-BE
$goWithRouter 전역 함수를 통해 SPA 라우팅을 일관되게 처리합니다.
<template>
<a @click="$goWithRouter('/webapp/home')">홈으로</a>
<a @click="$goWithRouter('/webapp/my')">마이페이지</a>
</template>
핵심 구현
- $goWithRouter 호출
- /webapp 경로?
- Vue Router push
- 네이티브 앱?
- app.cmd 호출
1. $goWithRouter 전역 함수
// public/js/common.js
window.emart = window.emart || {};
window.emart.$goWithRouter = function(url, options = {}) {
const { replace = false, query = {} } = options;
// /webapp 경로인지 확인
if (isWebappPath(url)) {
// Vue Router로 이동
const router = window.emart.router;
if (router) {
const routePath = url.replace('/webapp', '');
const routeOptions = { path: routePath, query };
if (replace) {
router.replace(routeOptions);
} else {
router.push(routeOptions);
}
return;
}
}
// 네이티브 앱 환경에서는 앱 명령 호출
if (isApp()) {
window.emart.app.cmd('open_main_webpage', { url });
return;
}
// 일반 웹 환경에서는 location 이동
if (replace) {
window.location.replace(url);
} else {
window.location.href = url;
}
};
function isWebappPath(url) {
return url.startsWith('/webapp') || url.includes('/webapp/');
}
function isApp() {
return typeof window.emart?.app?.cmd === 'function';
}
2. Vue 전역 프로퍼티 등록
// main.ts
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
const app = createApp(App);
// 전역 프로퍼티로 등록
app.config.globalProperties.$goWithRouter = (
url: string,
options?: { replace?: boolean; query?: Record<string, string> }
) => {
window.emart.$goWithRouter(url, options);
};
// 라우터 인스턴스 전역 노출
window.emart.router = router;
app.use(router);
app.mount('#app');
3. 타입 정의
// types/global.d.ts
declare global {
interface Window {
emart: {
$goWithRouter: (
url: string,
options?: {
replace?: boolean;
query?: Record<string, string>;
}
) => void;
router?: Router;
app?: {
cmd: (command: string, params?: Record<string, unknown>) => void;
};
};
}
}
// Vue 컴포넌트에서 사용하기 위한 타입 확장
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$goWithRouter: (
url: string,
options?: {
replace?: boolean;
query?: Record<string, string>;
}
) => void;
}
}
4. 컴포넌트에서 사용
<!-- components/layout/SideMenu.vue -->
<script setup lang="ts">
import { getCurrentInstance } from 'vue';
const { proxy } = getCurrentInstance()!;
const navigateToHome = () => {
proxy?.$goWithRouter('/webapp/home');
};
const navigateToMyPage = () => {
proxy?.$goWithRouter('/webapp/my', {
query: { tab: 'profile' }
});
};
</script>
<template>
<nav class="side-menu">
<ul>
<li @click="navigateToHome">
<span>홈</span>
</li>
<li @click="navigateToMyPage">
<span>마이페이지</span>
</li>
</ul>
</nav>
</template>
5. data-cmd 속성 통합 처리
// public/js/common.js
// 기존 data-cmd="open_main_webpage" 속성 처리
document.addEventListener('click', (event) => {
const target = event.target as HTMLElement;
const cmdElement = target.closest('[data-cmd="open_main_webpage"]');
if (cmdElement) {
event.preventDefault();
const url = cmdElement.getAttribute('data-url') ||
cmdElement.getAttribute('href');
if (url) {
window.emart.$goWithRouter(url);
}
}
});
6. 홈 페이지 적용 예시
<!-- views/Home.vue -->
<script setup lang="ts">
const { proxy } = getCurrentInstance()!;
const goToSearch = () => {
proxy?.$goWithRouter('/webapp/search');
};
const goToCategory = (categoryId: string) => {
proxy?.$goWithRouter('/webapp/category', {
query: { id: categoryId }
});
};
</script>
<template>
<div class="home">
<SearchBar @click="goToSearch" />
<CategoryList @select="goToCategory" />
</div>
</template>
회고
잘한 점
SPA 라우팅과 네이티브 앱 연동을 하나의 함수로 통합 처리했습니다. 전역 함수로 일관된 인터페이스를 제공하면서도 기존 data-cmd 속성과의 호환성을 유지했습니다.
개선할 점
일부 영역에서 Revert 후 재적용이 필요했던 점은 아쉬웠습니다. 아직 적용되지 않은 영역에 추가로 확대 적용할 필요가 있습니다.
배운 점
SPA 라우팅과 전통적인 페이지 이동의 차이를 깊이 이해하게 되었습니다. 네이티브 앱 웹뷰에서의 라우팅 처리 방식을 배웠고, Vue 전역 프로퍼티를 활용하여 프레임워크 내외부를 연결하는 방법을 익혔습니다.