SPA 환경 분석스크립트 설치 (React · Vue)
해당 제품(서비스)
웹사이트 서비스 전체 에디션(레벨). CTS 서비스는 아래 「CTS 서비스의 SPA 설치」를 함께 참고해 주십시오.
문서 정의
React·Vue처럼 화면을 이동해도 문서 전체를 다시 불러오지 않는 SPA(Single Page Application) 웹사이트에 로거 분석스크립트를 설치하는 방법입니다. 일반 웹사이트처럼 공통 영역에 기본 스크립트만 넣으면 최초 진입 화면만 집계되고 이후 화면 이동은 집계되지 않으므로, 라우팅이 바뀔 때마다 스크립트를 다시 실행하도록 설치해야 합니다.
가이드 내려받기
- 로거 SPA 환경 태깅 가이드 v2.1 (React · Vue, PDF): 내려받기
- CTS SPA 환경 태깅 가이드 v1.0 (React · Vue, PDF): 내려받기
- 일반 웹사이트 설치 가이드(개발자용 매뉴얼, PDF): 내려받기
- 로거 자료실 2130번 글: 「가이드 — 로거 SPA 환경 스크립트 설치 매뉴얼 가이드(업데이트)」 (로거 로그인 후 열람)
- 같은 가이드는 콘솔 「서비스 관리 > 분석 스크립트」의 [SPA 환경 설치 가이드] 버튼으로도 받을 수 있으며, 기존 LOGGER 리포트의 분석스크립트 다운로드·메일 첨부 파일에도 포함되어 있습니다.
SPA에서 로거가 동작하는 원리
로거 기본 스크립트는 문서를 불러올 때 1회 동작하도록 만들어져 있습니다. SPA에서는 다음 네 가지를 반드시 따로 처리해야 합니다.
| 구분 | 내용 | 처리 방법 |
|---|---|---|
| 1회만 실행 | 기본 스크립트가 불러오는 bstrk.1.js 는 중복 실행을 막기 위해 window._BSTRK_LOADED 가 정의되어 있지 않을 때만 동작합니다. SPA에서는 최초 진입 후 이 값이 남아 있어 이후 라우팅에서는 아무 일도 일어나지 않습니다. | 라우팅마다 _BSTRK_LOADED 를 undefined 로 초기화하고 추적 스크립트를 다시 삽입합니다(가상 페이지뷰). |
| 첫 화면 중복 집계 | 스크립트는 불러오는 즉시 페이지뷰 1건을 자동 전송합니다. 라우터 훅은 최초 진입에도 실행되므로 그대로 두면 첫 화면이 2건으로 집계됩니다. | 라우터 훅에서 최초 내비게이션 1건은 건너뜁니다(firstNavigationDone 플래그). |
| 환경변수 순서 | 완료 페이지의 전환 정보(_TRK_PI 등)는 기본 스크립트가 로드되는 시점에 이미 설정되어 있어야 수집됩니다. | 완료 화면이 별도 라우트이면 window 환경변수를 설정한 뒤 스크립트를 다시 삽입합니다. SPA에서는 _trk_flashEnvView 를 쓰지 않습니다(로드 시점 URL에 고정되기 때문). |
| 비동기 로딩 | 스크립트는 async 로 로드되어 완료 시점이 정해져 있지 않습니다. 로드 전에 발생한 전환 호출은 그대로 버려집니다. bstrk.1.js 에는 공식 커맨드 큐가 없습니다. | 로드 전 호출을 보관했다가 순서대로 실행하는 호출 큐(withLogger)를 둡니다. |
설치 구성
Vue 3 + Vue Router 4(History Mode) 기준이며, React도 같은 모듈을 사용합니다(React는 라우터 연결 부분만 다릅니다).
| 파일 | 역할 |
|---|---|
| index.html | 전역 설정 변수(_TRK_LID 등)만 선언합니다. 추적 스크립트 자체는 넣지 않습니다. |
| src/analytics/logger.js | 스크립트 로딩, 호출 큐, 가상 페이지뷰 전송, 전환 전송 함수를 모은 모듈 |
| src/router/index.js | installRouterHook(router) 로 afterEach 훅 등록 |
| src/main.js | 라우터 준비 후 로거 로드 |
| 각 화면 컴포넌트 | 전환이 필요한 화면에서만 전환 전송 함수 호출 |
1단계 — index.html 전역 설정 변수
{서비스번호}는 로거 서비스를 만들 때 발급된 서비스번호로 바꿉니다(콘솔 「서비스 관리 > 분석 스크립트」에서 확인).
<!-- LOGGER(TM) TRACKING SCRIPT V.40 : 전역 설정만 선언 -->
<script id="logger_{서비스번호}_tracker_id" type="text/javascript">
var _TRK_LID = "{서비스번호}";
var _L_TD = "ssl.logger.co.kr";
var _TRK_CDMN = "";
</script>
...
<noscript id="logger_{서비스번호}_noscript_tracker">
<img alt="Logger Script" width="1" height="1"
src="//ssl.logger.co.kr/tracker.1.tsp?u={서비스번호}&js=N" />
</noscript>
noscript 이미지 주소는 http:// 대신 프로토콜 상대 경로(//)로 씁니다. https 사이트에서 혼합 콘텐츠로 차단되지 않게 하기 위해서입니다.
2단계 — 로거 모듈(src/analytics/logger.js)
추적 스크립트는 index.html 이 아니라 모듈에서 동적으로 불러옵니다. 라우팅 때 재실행을 제어하기 위해서입니다. 모듈은 로딩 → 호출 큐 → 가상 페이지뷰 → 전환 전송 순서로 구성합니다(전체 코드는 가이드 2.3 ~ 2.7절).
const SERVICE_NO = '{서비스번호}';
const SCRIPT_ID = `logger_${SERVICE_NO}_tracking_script`;
const SCRIPT_SRC = '//fs.bizspring.net/fs4/bstrk.1.js';
const ENV_VARS = ['_TRK_PI','_TRK_PN','_TRK_PND','_TRK_OA','_TRK_OE','_TRK_OP',
'_TRK_CP','_TRK_MF','_TRK_SX','_TRK_AG','_TRK_IK'];
let ready = false, conversionHandled = false;
const queue = [];
const isLoggerReady = () => typeof window._trk_flashEnvView === 'function';
// 앱 부팅 시 1회 — 스크립트 로드 후 큐에 쌓인 호출을 순서대로 실행
export function loadLogger() {
if (document.getElementById(SCRIPT_ID)) return;
const s = document.createElement('script');
s.id = SCRIPT_ID; s.type = 'text/javascript'; s.async = true; s.src = SCRIPT_SRC;
s.onload = () => waitUntilReady(flushQueue); // 전역 함수 정의를 100ms 간격으로 확인
document.head.appendChild(s);
}
// 모든 로거 호출은 이 함수로 — 로드 전이면 큐에 보관
export function withLogger(job) {
if (ready && isLoggerReady()) job(); else queue.push(job);
}
// 공통 — _BSTRK_LOADED 초기화 후 추적 스크립트 재삽입
function reinsertBstrk(onDone) {
document.getElementById(SCRIPT_ID)?.remove();
if (onDone) window._trk_flashEnvView = undefined;
window._BSTRK_LOADED = undefined;
const s = document.createElement('script');
s.id = SCRIPT_ID; s.type = 'text/javascript'; s.async = true; s.src = SCRIPT_SRC;
if (onDone) s.onload = () => {
const wait = () => typeof window._trk_flashEnvView === 'function' ? onDone() : setTimeout(wait, 30);
wait();
};
document.head.appendChild(s);
}
// 가상 페이지뷰
export function sendPageView() { withLogger(() => reinsertBstrk()); }
// 전환 — vars 예: { _TRK_PI: 'ODR', _TRK_OP: 'A001;B002', _TRK_OE: '1;2', _TRK_OA: '1000;500' }
export function sendConversion(vars) {
conversionHandled = true; // 같은 라우팅의 페이지뷰 재전송을 건너뜀
withLogger(() => {
Object.entries(vars).forEach(([k, v]) => { window[k] = v; });
reinsertBstrk(() => ENV_VARS.forEach(k => { window[k] = ''; })); // 다음 화면 오염 방지
});
}
// 링크·버튼 클릭 수
export function sendClick(type, label) {
withLogger(() => window._trk_clickTrace?.(type, label));
}
bstrk.1.js 에는 가상 페이지뷰 전송용 공개 함수와 공식 커맨드 큐가 없습니다. 재삽입 방식과 withLogger() 자체 구현이 현재 유일한 방법이며, 실데이터 수집 시험에서 세션 연속성이 정상으로 확인되었습니다.
3단계 — 라우터 연결(Vue Router)
router.afterEach() 에서 가상 페이지뷰를 보냅니다. 최초 내비게이션은 스크립트 로드로 이미 1건이 전송되므로 건너뜁니다. 로거 로드는 라우터가 준비된 뒤에 합니다.
// src/analytics/logger.js (이어서)
export function installRouterHook(router) {
let firstNavigationDone = false;
router.afterEach((to, from, failure) => {
if (failure) return;
if (!firstNavigationDone) { firstNavigationDone = true; return; } // 최초 1건 skip
if (to.fullPath === from.fullPath) return;
conversionHandled = false;
setTimeout(() => { if (!conversionHandled) sendPageView(); }, 0);
});
}
// src/router/index.js
installRouterHook(router);
// src/main.js — 라우터 준비 후 로드해 최초 페이지뷰가 확정된 경로로 전송되게 한다
router.isReady().then(() => { loadLogger(); app.mount('#app'); });
4단계 — 전환(환경변수) 전송
- 완료 화면이 별도 라우트인 경우(주문완료·신청완료 화면): 완료 화면 컴포넌트가 화면에 붙은 뒤(onMounted / useEffect) sendConversion() 을 호출합니다. 같은 라우팅의 페이지뷰는 자동으로 한 번만 전송됩니다.
- 완료 화면 없이 응답만 받는 경우(버튼 클릭, fetch·axios 응답): 응답이 성공한 시점에 sendConversion() 을 호출합니다. 현재 URL 기준으로 전환이 전송됩니다.
- 여러 상품은 값 안에서 세미콜론(;)으로 구분합니다(_TRK_OP · _TRK_OE · _TRK_OA).
- 링크·버튼 클릭 수는 sendClick(type, label) 로 보냅니다. type 은 EVT(링크 클릭), DNL(다운로드), RFO(외부 사이트 연결), SCI(장바구니 담기), SCO(장바구니 삭제)입니다.
// 완료 화면형 (Vue) — src/views/ApplyComplete.vue
onMounted(() => {
sendConversion({ _TRK_PI: 'ODR', _TRK_OP: 'APPLY', _TRK_OE: '1', _TRK_OA: '1' });
});
// 완료 화면이 없는 경우 — 응답 성공 시점
async function submitInquiry(payload) {
const res = await fetch('/api/inquiry', { method: 'POST', body: JSON.stringify(payload) });
if (!res.ok) return;
sendConversion({ _TRK_PI: 'RES' }); // 문의 완료
}
React에서 설치할 때
로거 모듈(큐·전환 함수)은 프레임워크와 무관하므로 그대로 씁니다. 라우터 연결은 React Router 의 useLocation() 으로 경로 변화를 구독하는 컴포넌트를 Router 안에 한 번 배치합니다.
흔한 실수: useLocation 을 import 만 하고 const location = useLocation() 을 호출하지 않으면 라우팅이 바뀌어도 다시 실행되지 않아 최초 진입만 집계됩니다. 또한 cleanup(return () => …)에서 스크립트를 지우면 언마운트 경합이 생기므로, 다음 삽입 직전에 지웁니다.
// src/analytics/LoggerScript.jsx
const LoggerScript = () => {
const location = useLocation(); // ★ 반드시 필요
useEffect(() => {
if (!document.getElementById(TRACKER_ID)) { // 전역 설정 변수는 최초 1회만
const config = document.createElement('script');
config.id = TRACKER_ID; config.type = 'text/javascript';
config.text = `var _TRK_LID = "${SERVICE_NO}"; var _L_TD = "ssl.logger.co.kr"; var _TRK_CDMN = "";`;
document.head.appendChild(config);
}
if (typeof window._BSTRK_LOADED !== 'undefined') window._BSTRK_LOADED = undefined;
document.getElementById(SCRIPT_ID)?.remove(); // 이전 추적 스크립트 제거 후 재삽입
const script = document.createElement('script');
script.id = SCRIPT_ID; script.type = 'text/javascript'; script.async = true; script.src = SCRIPT_SRC;
document.head.appendChild(script);
}, [location.pathname, location.search]); // ★ 라우터 상태를 구독
return null;
};
// src/App.jsx — Router 안에 한 번만
<BrowserRouter>
<LoggerScript />
<Routes>{/* ... */}</Routes>
</BrowserRouter>
GTM을 사용하는 React 사이트는 React 환경 태깅 가이드(개발자용)를 참고해 주십시오.
GTM을 사용하는 경우(요약)
- 태그: 맞춤 HTML에 기본 스크립트를 넣고 서비스번호를 바꿉니다.
- 트리거: All Pages + 기록 변경(History Change) 두 개를 함께 연결합니다. 초기 로드는 All Pages, 이후 라우팅은 기록 변경이 담당합니다. 기본 제공 변수의 [기록] 항목(History Source, New History Fragment 등)을 모두 켭니다.
- 모든 SPA 구조에서 기록 변경 트리거가 동작하지는 않습니다. 미리보기 모드에서 확인하고, 동작하지 않으면 dataLayer 맞춤 이벤트로 대체합니다.
- 전환은 애플리케이션에서 dataLayer.push({ event: 'loggerOrderResult', loggerPI: 'ODR', … }) 로 보내고, 맞춤 이벤트 트리거에 연결한 맞춤 HTML 태그에서 _TRK_PI = "{{loggerPI}}"; 처럼 설정합니다. 중괄호 안에 따옴표를 넣지 않습니다(_TRK_SX = "{{loggerSX}}"; 가 올바른 표기).
CTS 서비스의 SPA 설치
CTS 스크립트(l4cts.v4.2.js)는 GA4와 같은 방식의 커맨드 큐를 내장하고 있어, 로드 전에 호출한 _tcts_m() 도 보관했다가 로드 후 자동으로 실행합니다. 따라서 큐를 따로 만들 필요가 없고, 라우팅마다 _tcts_m(서비스번호, 매체코드) 를 호출하면 됩니다. 서비스번호와 매체코드는 「서비스 관리 > 분석 스크립트」에서 확인합니다.
<!-- index.html : 기본 CTS 스크립트보다 위에 -->
<script type="text/javascript">
var _L_LALT = "30"; /* 간접전환 기간(일) — 기본 30 */
window._tcts_m = window._tcts_m || function() {
(window._tcts_m.q = window._tcts_m.q || []).push(arguments);
};
</script>
// src/analytics/cts.js
const SERVICE_NO = '서비스번호', MEDIA_CODE = '매체코드';
export function sendPageView() { window._TRK_PI = ''; window._tcts_m(SERVICE_NO, MEDIA_CODE); }
export function sendConversion(pi, order = {}) {
window._TRK_PI = pi;
if (order.amount != null) window._TRK_OA = String(order.amount);
if (order.quantity != null) window._TRK_OE = String(order.quantity);
if (order.product != null) window._TRK_OP = String(order.product);
if (order.orderNo != null) window._TRK_ODN = String(order.orderNo);
window._tcts_m(SERVICE_NO, MEDIA_CODE);
window._TRK_PI = window._TRK_OA = window._TRK_OE = window._TRK_OP = window._TRK_ODN = '';
}
CTS는 스크립트(https://fs.bizspring.net/fs4/l4cts.v4.2.js)를 불러온 뒤 sendPageView() 를 한 번 호출하고, 이후 라우팅(Vue: afterEach, React: useEffect([location]))마다 다시 호출합니다. 전환은 sendPageView() 와 별도로 sendConversion('ODR', {...}) · sendConversion('RGR') 처럼 호출합니다.
설치 체크리스트
- index.html 에 전역 설정 변수 블록을 넣고 서비스번호를 확인합니다.
- src/analytics/logger.js(로딩·큐·가상 페이지뷰·전환)를 추가합니다.
- 라우터 훅을 연결합니다(Vue: afterEach / React: LoggerScript 컴포넌트).
- 라우터가 준비된 뒤 loadLogger() 를 호출합니다.
- 전환 완료 지점에 sendConversion() 을 넣습니다.
- History Mode 서버 rewrite 설정을 확인합니다(아래 유의사항).
- 데이터검증 TOOL™ 로 페이지뷰·전환 수집을 확인합니다.
데이터 검증
- 리포트에 로그인해 [데이터검증 TOOL™]에서 대상 사이트를 고르고 [조회]를 누른 뒤, 사이트를 방문해 표시되는 정보를 확인합니다.
- SPA 검증 포인트: 최초 진입 1건이 기록되는지, 메뉴를 이동할 때마다 페이지뷰가 1건씩 늘어나는지, 첫 화면이 2건으로 중복 집계되지 않는지 확인합니다.
- 전환 검증 포인트: 전환이 완료될 때 _TRK_PI 값이 의도한 코드로 들어오는지 확인합니다.
- 데이터검증 TOOL™ 은 본인 IP에서만 실시간 검증할 수 있습니다. 시크릿 모드를 쓰거나 쿠키를 지운 뒤 검증합니다. 표시되지 않으면 ① 스크립트 설치 상태 ② [분석제외 IP] 등록 여부를 확인합니다.
주요 환경변수와 _TRK_PI 코드
| 변수 | 용도 | 비고 |
|---|---|---|
| _TRK_PI | 웹페이지 성격 / 전환 유형 | RGR 회원가입 결과, LIR 로그인 결과, PDV 상품 상세, OCV 장바구니 보기, ODF 주문 입력, ODR 주문 완료, RES 문의 완료, PNF 404 등 |
| _TRK_PN · _TRK_PND | 상품(항목) 코드 · 상품(항목) 명 | - |
| _TRK_OA · _TRK_OE · _TRK_OP | 주문 금액 · 수량 · 상품 코드 | 여러 항목은 ; 로 구분 |
| _TRK_CP | 카테고리 | 예: /회사소개/제휴안내/ |
| _TRK_MF | 브랜드(제조사)명 | - |
| _TRK_SX · _TRK_AG | 회원 성별(M/F/U) · 회원 특성(연령 코드) | - |
| _TRK_IK | 내부 검색어 | - |
변수별 상세 설정은 환경변수 스크립트 이하 문서를 참고해 주십시오.
유의사항
- History Mode 에서는 서버가 모든 경로를 index.html 로 응답하도록 rewrite 를 설정해야 합니다(예: Nginx try_files $uri $uri/ /index.html;). 설정이 없으면 주소로 직접 들어올 때 404가 나고 그 페이지뷰가 수집되지 않습니다.
- Nuxt 등 SSR 환경에서는 로거 스크립트가 서버에서 실행되지 않도록 클라이언트 전용 플러그인(.client)으로 분리하고, 라우팅 훅도 클라이언트에서만 등록합니다. 전환 호출은 onMounted 이후에 둡니다.
- 가이드의 코드는 일반적인 구조를 기준으로 한 예시입니다. 고객사 애플리케이션 구조에 따라 연결 위치는 달라질 수 있으며, 설치 지원이 필요하면 service@logger.co.kr 로 문의해 주십시오.
이 문서의 Markdown: /console/manual-stt/console-install-spa.md.txt