OpenReplay: 자체 호스팅 세션 리플레이 & 사용자 분석 플랫폼 완전 가이드
⏱️ 예상 읽기 시간: 18분
서론
웹 애플리케이션에서 사용자가 어떻게 행동하는지, 어디서 문제를 겪는지 실시간으로 파악하고 싶다면 OpenReplay가 완벽한 선택입니다.
OpenReplay는 자체 호스팅이 가능한 오픈소스 세션 리플레이 플랫폼으로, 사용자의 웹 사이트 이용 과정을 영상으로 재생하며 네트워크 활동, 콘솔 로그, JavaScript 오류까지 모든 기술적 세부사항을 캡처합니다.
이 튜토리얼에서 배울 내용
- OpenReplay의 핵심 기능과 아키텍처
- Docker와 Kubernetes를 통한 자체 호스팅 배포
- JavaScript 트래커 설치 및 설정
- 세션 리플레이와 실시간 사용자 지원 활용
- 고급 분석 및 알림 설정
- 프라이버시 제어 및 보안 설정
개발환경
- OS: macOS/Linux/Windows
- Docker: 20.0+
- Kubernetes: 1.20+ (선택사항)
- Node.js: 16.0+
- 브라우저: Chrome, Firefox, Safari, Edge
OpenReplay 소개
핵심 특징
OpenReplay는 다음과 같은 강력한 기능들을 제공합니다:
- 완전한 세션 리플레이: 사용자 행동의 모든 것을 영상으로 재생
- 낮은 성능 영향: 26KB 압축 트래커로 최소한의 성능 오버헤드
- 자체 호스팅: 모든 데이터가 자신의 인프라에 저장
- 프라이버시 제어: 세밀한 데이터 보안 및 마스킹 기능
- 실시간 지원: 라이브 세션 모니터링 및 사용자 지원
주요 기능 상세
1. 세션 리플레이
사용자의 웹 사이트 이용 과정을 영상으로 기록하고 재생합니다:
- 마우스 움직임, 클릭, 스크롤
- 키보드 입력 (보안 설정 가능)
- 페이지 로딩 및 전환
- 모바일 터치 제스처
2. 개발자 도구
브라우저 개발자 도구와 유사한 디버깅 환경을 제공합니다:
- 네트워크 요청/응답 모니터링
- JavaScript 오류 및 콘솔 로그
- 성능 메트릭 (Core Web Vitals)
- 메모리 및 CPU 사용량
3. Spot 브라우저 확장
Chrome 확장 프로그램으로 직접 버그를 기록할 수 있습니다:
- 원클릭 세션 리코딩
- 자동 기술 정보 수집
- 개발팀과의 직접 공유
4. Assist 실시간 지원
사용자와 실시간으로 상호작용할 수 있습니다:
- 라이브 스크린 공유
- WebRTC 기반 음성/영상 통화
- 원격 제어 및 안내
아키텍처 구성요소
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Frontend │ │ Backend API │ │ PostgreSQL │
│ (React) │────│ (Python) │────│ Database │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
│ │ │
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Tracker │ │ Storage │ │ Redis Cache │
│ (JavaScript) │────│ (MinIO/S3) │────│ & Queue │
└─────────────────┘ └─────────────────┘ └─────────────────┘
빠른 시작하기
방법 1: Docker Compose 배포 (권장)
가장 빠른 배포 방법은 Docker Compose를 사용하는 것입니다:
1단계: 저장소 클론
# OpenReplay 저장소 클론
git clone https://github.com/openreplay/openreplay.git
cd openreplay
2단계: 환경 설정
# Docker Compose 디렉토리로 이동
cd scripts/docker-compose
# 환경 변수 파일 복사
cp .env.example .env
환경 변수 설정:
# .env 파일 편집
vim .env
# 필수 설정
DOMAIN_NAME=your-domain.com
POSTGRES_PASSWORD=your_secure_password
JWT_SECRET=your_jwt_secret_key_here
OPENREPLAY_LICENSE_KEY=your_license_key
# MinIO 설정
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=your_minio_password
# Redis 설정
REDIS_PASSWORD=your_redis_password
3단계: 서비스 시작
# 모든 서비스 시작
docker-compose up -d
# 서비스 상태 확인
docker-compose ps
# 로그 확인
docker-compose logs -f
설치가 완료되면 http://localhost:9000
에서 OpenReplay 대시보드에 접속할 수 있습니다.
방법 2: Kubernetes 배포
프로덕션 환경에서는 Kubernetes 배포를 권장합니다:
1단계: Helm 설치
# Helm 설치 (macOS)
brew install helm
# Helm 저장소 추가
helm repo add openreplay https://charts.openreplay.com
helm repo update
2단계: 네임스페이스 생성
# OpenReplay 전용 네임스페이스 생성
kubectl create namespace openreplay
3단계: 설정 값 준비
# values.yaml 생성
global:
domainName: "your-domain.com"
postgresqlPassword: "your_secure_password"
jwtSecret: "your_jwt_secret"
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: "nginx"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
tls:
enabled: true
postgresql:
auth:
postgresPassword: "your_secure_password"
primary:
persistence:
size: "100Gi"
minio:
auth:
rootPassword: "your_minio_password"
persistence:
size: "200Gi"
redis:
auth:
password: "your_redis_password"
4단계: Helm 배포
# OpenReplay 설치
helm install openreplay openreplay/openreplay \
--namespace openreplay \
--values values.yaml \
--wait
# 설치 상태 확인
kubectl get pods -n openreplay
kubectl get services -n openreplay
JavaScript 트래커 설정
기본 설치
웹 애플리케이션에 OpenReplay 트래커를 설치합니다:
1단계: NPM 패키지 설치
# npm 설치
npm install @openreplay/tracker
# 또는 yarn 설치
yarn add @openreplay/tracker
2단계: 기본 트래커 설정
// src/openreplay.js
import Tracker from '@openreplay/tracker';
const tracker = new Tracker({
projectKey: "YOUR_PROJECT_KEY", // OpenReplay 대시보드에서 확인
ingestPoint: "https://your-domain.com/ingest", // 자체 호스팅 URL
__DISABLE_SECURE_MODE: false, // HTTPS 환경에서는 false
});
// 트래커 시작
tracker.start();
// 사용자 식별
tracker.setUserID("user-123");
tracker.setMetadata("name", "John Doe");
tracker.setMetadata("email", "john@example.com");
export default tracker;
3단계: React 애플리케이션 통합
// src/App.js
import React, { useEffect } from 'react';
import tracker from './openreplay';
function App() {
useEffect(() => {
// 트래커 초기화
tracker.start();
// 페이지 뷰 추적
tracker.setMetadata('page', window.location.pathname);
return () => {
// 컴포넌트 언마운트 시 정리
tracker.stop();
};
}, []);
const handleButtonClick = () => {
// 커스텀 이벤트 추적
tracker.event('button_clicked', {
component: 'header',
action: 'navigation'
});
};
return (
<div className="App">
<header>
<button onClick={handleButtonClick}>
메뉴 열기
</button>
</header>
{/* 나머지 애플리케이션 */}
</div>
);
}
export default App;
Vue.js 통합
// src/plugins/openreplay.js
import Tracker from '@openreplay/tracker';
import { App } from 'vue';
const tracker = new Tracker({
projectKey: "YOUR_PROJECT_KEY",
ingestPoint: "https://your-domain.com/ingest",
});
export default {
install(app: App) {
tracker.start();
app.config.globalProperties.$tracker = tracker;
app.provide('tracker', tracker);
}
};
<!-- components/UserProfile.vue -->
<template>
<div class="user-profile">
<button @click="updateProfile">프로필 업데이트</button>
</div>
</template>
<script>
export default {
inject: ['tracker'],
methods: {
updateProfile() {
// 이벤트 추적
this.tracker.event('profile_updated', {
section: 'user_settings',
timestamp: Date.now()
});
// 실제 프로필 업데이트 로직
this.performUpdate();
}
}
}
</script>
Angular 통합
// src/app/services/openreplay.service.ts
import { Injectable } from '@angular/core';
import Tracker from '@openreplay/tracker';
@Injectable({
providedIn: 'root'
})
export class OpenReplayService {
private tracker: Tracker;
constructor() {
this.tracker = new Tracker({
projectKey: "YOUR_PROJECT_KEY",
ingestPoint: "https://your-domain.com/ingest",
});
}
start(): void {
this.tracker.start();
}
setUser(userId: string, metadata: Record<string, any>): void {
this.tracker.setUserID(userId);
Object.entries(metadata).forEach(([key, value]) => {
this.tracker.setMetadata(key, value);
});
}
trackEvent(name: string, payload?: Record<string, any>): void {
this.tracker.event(name, payload);
}
stop(): void {
this.tracker.stop();
}
}
// src/app/app.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { OpenReplayService } from './services/openreplay.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent implements OnInit, OnDestroy {
constructor(private openReplay: OpenReplayService) {}
ngOnInit(): void {
this.openReplay.start();
// 사용자 정보 설정
this.openReplay.setUser('user-456', {
role: 'admin',
subscription: 'premium'
});
}
ngOnDestroy(): void {
this.openReplay.stop();
}
onFormSubmit(): void {
this.openReplay.trackEvent('form_submitted', {
form_type: 'contact',
validation_passed: true
});
}
}
고급 플러그인 설정
Redux 상태 추적
// src/store/openreplay-middleware.js
import tracker from '../openreplay';
const openReplayMiddleware = (store) => (next) => (action) => {
// Redux 액션 추적
tracker.event('redux_action', {
type: action.type,
payload: action.payload,
timestamp: Date.now()
});
const result = next(action);
// 상태 변경 후 스냅샷
const newState = store.getState();
tracker.setMetadata('redux_state', JSON.stringify(newState));
return result;
};
// store 설정
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './reducers';
const store = createStore(
rootReducer,
applyMiddleware(openReplayMiddleware)
);
export default store;
Apollo GraphQL 통합
// src/apollo/openreplay-link.js
import { ApolloLink } from '@apollo/client';
import tracker from '../openreplay';
const openReplayLink = new ApolloLink((operation, forward) => {
const startTime = Date.now();
// GraphQL 쿼리 시작 추적
tracker.event('graphql_query_start', {
operationName: operation.operationName,
query: operation.query.loc?.source.body,
variables: operation.variables
});
return forward(operation).map(response => {
const endTime = Date.now();
// 쿼리 완료 추적
tracker.event('graphql_query_complete', {
operationName: operation.operationName,
duration: endTime - startTime,
hasErrors: !!response.errors,
errorCount: response.errors?.length || 0
});
if (response.errors) {
response.errors.forEach(error => {
tracker.event('graphql_error', {
operationName: operation.operationName,
message: error.message,
path: error.path
});
});
}
return response;
});
});
// Apollo Client 설정
import { ApolloClient, InMemoryCache, from } from '@apollo/client';
const client = new ApolloClient({
link: from([openReplayLink, httpLink]),
cache: new InMemoryCache()
});
Axios 요청 추적
// src/api/axios-config.js
import axios from 'axios';
import tracker from '../openreplay';
// 요청 인터셉터
axios.interceptors.request.use(
(config) => {
const startTime = Date.now();
config.metadata = { startTime };
tracker.event('api_request_start', {
url: config.url,
method: config.method?.toUpperCase(),
headers: config.headers,
data: config.data
});
return config;
},
(error) => {
tracker.event('api_request_error', {
message: error.message,
config: error.config
});
return Promise.reject(error);
}
);
// 응답 인터셉터
axios.interceptors.response.use(
(response) => {
const endTime = Date.now();
const duration = endTime - response.config.metadata.startTime;
tracker.event('api_request_success', {
url: response.config.url,
method: response.config.method?.toUpperCase(),
status: response.status,
duration,
responseSize: JSON.stringify(response.data).length
});
return response;
},
(error) => {
const endTime = Date.now();
const duration = error.config?.metadata ?
endTime - error.config.metadata.startTime : 0;
tracker.event('api_request_failed', {
url: error.config?.url,
method: error.config?.method?.toUpperCase(),
status: error.response?.status,
duration,
errorMessage: error.message
});
return Promise.reject(error);
}
);
export default axios;
프라이버시 제어 설정
민감한 데이터 마스킹
// 고급 프라이버시 설정
const tracker = new Tracker({
projectKey: "YOUR_PROJECT_KEY",
ingestPoint: "https://your-domain.com/ingest",
// 입력 필드 마스킹
obscureInputs: true,
obscureInputNumbers: true,
obscureInputEmails: true,
// 클래스/ID 기반 마스킹
obscureTextNumbers: true,
obscureTextEmails: true,
// 특정 요소 완전 숨김
privacySuppressionLevel: 2, // 0: 없음, 1: 부분, 2: 완전
// 커스텀 마스킹 규칙
sanitize: (data) => {
// 신용카드 번호 마스킹
if (data.match(/\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}/)) {
return '****-****-****-****';
}
// 주민등록번호 마스킹
if (data.match(/\d{6}-\d{7}/)) {
return '******-*******';
}
return data;
}
});
// HTML 속성으로 마스킹 지정
<!-- 완전 숨김 -->
<div data-openreplay-hidden>
민감한 정보가 포함된 섹션
</div>
<!-- 텍스트만 마스킹 -->
<div data-openreplay-masked>
사용자 이름: John Doe
</div>
<!-- 입력 필드 마스킹 -->
<input type="password" data-openreplay-obscured />
<input type="text" class="or-mask" placeholder="신용카드 번호" />
GDPR 준수 설정
// GDPR 준수를 위한 동의 관리
class PrivacyManager {
constructor(tracker) {
this.tracker = tracker;
this.consentGiven = this.getStoredConsent();
}
getStoredConsent() {
return localStorage.getItem('openreplay-consent') === 'true';
}
requestConsent() {
return new Promise((resolve) => {
// 동의 팝업 표시
const consentModal = this.createConsentModal();
document.body.appendChild(consentModal);
consentModal.addEventListener('consent-given', () => {
this.giveConsent();
resolve(true);
});
consentModal.addEventListener('consent-denied', () => {
this.denyConsent();
resolve(false);
});
});
}
giveConsent() {
this.consentGiven = true;
localStorage.setItem('openreplay-consent', 'true');
// 트래커 시작
this.tracker.start();
// 동의 이벤트 기록
this.tracker.event('privacy_consent_given', {
timestamp: Date.now(),
version: '1.0'
});
}
denyConsent() {
this.consentGiven = false;
localStorage.setItem('openreplay-consent', 'false');
// 트래커 중지
this.tracker.stop();
}
revokeConsent() {
this.denyConsent();
// 서버에 데이터 삭제 요청
fetch('/api/privacy/delete-user-data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userId: this.tracker.getUserID(),
requestType: 'data_deletion'
})
});
}
createConsentModal() {
const modal = document.createElement('div');
modal.innerHTML = `
<div class="privacy-consent-modal">
<h3>데이터 수집 동의</h3>
<p>사용자 경험 개선을 위해 세션 데이터를 수집합니다.</p>
<button id="consent-accept">동의</button>
<button id="consent-deny">거부</button>
</div>
`;
modal.querySelector('#consent-accept').onclick = () => {
modal.dispatchEvent(new CustomEvent('consent-given'));
modal.remove();
};
modal.querySelector('#consent-deny').onclick = () => {
modal.dispatchEvent(new CustomEvent('consent-denied'));
modal.remove();
};
return modal;
}
}
// 사용법
const privacyManager = new PrivacyManager(tracker);
// 애플리케이션 시작 시 동의 확인
if (!privacyManager.consentGiven) {
privacyManager.requestConsent();
} else {
tracker.start();
}
세션 리플레이 활용하기
대시보드 접속 및 기본 사용
배포가 완료되면 웹 브라우저에서 OpenReplay 대시보드에 접속합니다:
# 로컬 Docker 배포의 경우
http://localhost:9000
# 도메인 설정한 경우
https://your-domain.com
1단계: 프로젝트 설정
- 관리자 계정으로 로그인
- 새 프로젝트 생성
- 프로젝트 키 복사
- JavaScript 트래커에 적용
2단계: 세션 모니터링
대시보드에서 실시간 세션을 모니터링할 수 있습니다:
- 라이브 세션: 현재 활성 사용자들의 실시간 활동
- 세션 기록: 과거 세션들의 재생 및 분석
- 사용자 여정: 사용자의 전체 탐색 경로
- 성능 메트릭: 페이지 로딩 시간, 상호작용 지연
고급 필터링 및 검색
OpenReplay의 강력한 검색 기능을 활용합니다:
// 커스텀 메타데이터로 세션 분류
tracker.setMetadata('user_type', 'premium');
tracker.setMetadata('feature_flags', JSON.stringify({
newCheckout: true,
betaFeatures: false
}));
// A/B 테스트 그룹 추적
tracker.setMetadata('ab_test_group', 'variant_b');
// 사용자 컨텍스트 정보
tracker.setMetadata('device_type', 'mobile');
tracker.setMetadata('browser_version', navigator.userAgent);
대시보드에서 이러한 메타데이터로 필터링할 수 있습니다:
- 특정 사용자 그룹의 세션만 조회
- 오류가 발생한 세션들만 필터링
- A/B 테스트 그룹별 비교 분석
- 디바이스 유형별 사용자 행동 분석
실시간 사용자 지원 (Assist)
Assist 기능으로 사용자에게 실시간 지원을 제공합니다:
1단계: Assist 활성화
// Assist 기능 활성화
import Assist from '@openreplay/tracker/assist';
const assist = new Assist({
confirmText: "고객지원팀에서 화면을 공유하려고 합니다. 동의하시겠습니까?",
config: {
audio: true,
video: false, // 화면 공유만, 카메라는 비활성화
screen: true
}
});
tracker.use(assist);
2단계: 지원 세션 시작
대시보드에서 진행중인 세션을 선택하고 “Assist” 버튼을 클릭합니다:
- 사용자에게 동의 팝업이 표시됩니다
- 동의 시 실시간 화면 공유가 시작됩니다
- 필요시 음성/영상 통화를 추가할 수 있습니다
- 원격으로 사용자를 안내할 수 있습니다
3단계: 지원 이력 관리
// 지원 세션 메타데이터 추가
assist.onSessionStart((session) => {
tracker.setMetadata('support_session_id', session.id);
tracker.setMetadata('support_agent', session.agentId);
// 지원 시작 이벤트
tracker.event('support_session_started', {
sessionId: session.id,
requestType: 'user_initiated',
timestamp: Date.now()
});
});
assist.onSessionEnd((session, summary) => {
// 지원 완료 이벤트
tracker.event('support_session_ended', {
sessionId: session.id,
duration: summary.duration,
resolution: summary.resolution,
satisfaction: summary.userRating
});
});
분석 및 알림 설정
커스텀 대시보드 구성
OpenReplay에서 비즈니스에 특화된 대시보드를 구성합니다:
1단계: 핵심 메트릭 정의
// 비즈니스 이벤트 추적
class BusinessMetrics {
constructor(tracker) {
this.tracker = tracker;
}
// 전환률 추적
trackConversion(type, value) {
this.tracker.event('conversion', {
type, // 'signup', 'purchase', 'subscription'
value,
timestamp: Date.now(),
sessionId: this.tracker.getSessionToken()
});
}
// 사용자 참여도 추적
trackEngagement(action, duration) {
this.tracker.event('engagement', {
action, // 'page_view', 'scroll', 'click'
duration,
depth: this.calculateScrollDepth(),
interactions: this.getInteractionCount()
});
}
// 기능 사용률 추적
trackFeatureUsage(feature, context) {
this.tracker.event('feature_usage', {
feature,
context,
userType: this.tracker.getMetadata('user_type'),
timestamp: Date.now()
});
}
calculateScrollDepth() {
const scrollTop = window.pageYOffset;
const docHeight = document.documentElement.scrollHeight;
const windowHeight = window.innerHeight;
return Math.round((scrollTop / (docHeight - windowHeight)) * 100);
}
getInteractionCount() {
return parseInt(sessionStorage.getItem('interaction_count') || '0');
}
}
// 사용법
const metrics = new BusinessMetrics(tracker);
// 결제 완료 시
metrics.trackConversion('purchase', 129.99);
// 기능 사용 시
metrics.trackFeatureUsage('advanced_search', {
page: 'product_catalog',
filters_applied: 3
});
2단계: 알림 설정
대시보드에서 다음과 같은 알림을 설정할 수 있습니다:
- 오류 급증 알림: JavaScript 오류가 임계치를 초과할 때
- 성능 저하 알림: 페이지 로딩 시간이 늘어날 때
- 전환율 하락 알림: 주요 전환 이벤트가 감소할 때
- 사용자 이탈 패턴: 특정 페이지에서 이탈률이 높아질 때
A/B 테스트 분석
OpenReplay를 활용한 A/B 테스트 결과 분석:
// A/B 테스트 통합
class ABTestTracker {
constructor(tracker) {
this.tracker = tracker;
this.testVariant = this.getTestVariant();
}
getTestVariant() {
// 외부 A/B 테스트 도구에서 가져오거나 자체 구현
const userId = this.tracker.getUserID();
const hash = this.hashUserId(userId);
return hash % 2 === 0 ? 'variant_a' : 'variant_b';
}
trackVariantExposure(testName) {
this.tracker.setMetadata(`test_${testName}`, this.testVariant);
this.tracker.event('ab_test_exposure', {
testName,
variant: this.testVariant,
timestamp: Date.now()
});
}
trackVariantConversion(testName, goalType, value = null) {
this.tracker.event('ab_test_conversion', {
testName,
variant: this.testVariant,
goalType, // 'click', 'signup', 'purchase'
value,
timestamp: Date.now()
});
}
hashUserId(userId) {
let hash = 0;
for (let i = 0; i < userId.length; i++) {
const char = userId.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // 32비트 정수로 변환
}
return Math.abs(hash);
}
}
// 사용 예제
const abTest = new ABTestTracker(tracker);
// 새로운 체크아웃 플로우 테스트
abTest.trackVariantExposure('checkout_redesign');
// 다른 버튼 디자인 표시
if (abTest.testVariant === 'variant_b') {
document.querySelector('.cta-button').classList.add('new-design');
}
// 전환 추적
document.querySelector('.purchase-button').addEventListener('click', () => {
abTest.trackVariantConversion('checkout_redesign', 'purchase_attempt');
});
실제 테스트 및 예제
이제 실제 테스트를 위한 예제 애플리케이션을 만들어보겠습니다:
간단한 React 테스트 앱
// src/OpenReplayDemo.js
import React, { useEffect, useState } from 'react';
import tracker from './openreplay';
function OpenReplayDemo() {
const [counter, setCounter] = useState(0);
const [userInfo, setUserInfo] = useState({
name: '',
email: ''
});
useEffect(() => {
// OpenReplay 트래커 시작
tracker.start().then(() => {
console.log('OpenReplay 트래커 시작됨');
// 사용자 정보 설정
tracker.setUserID('demo-user-123');
tracker.setMetadata('demo_mode', 'enabled');
tracker.setMetadata('app_version', '1.0.0');
// 페이지 뷰 이벤트
tracker.event('page_view', {
page: 'demo',
timestamp: Date.now()
});
});
return () => {
tracker.stop();
};
}, []);
const handleCounterClick = () => {
setCounter(prev => prev + 1);
// 클릭 이벤트 추적
tracker.event('counter_clicked', {
current_value: counter,
new_value: counter + 1
});
};
const handleFormSubmit = (e) => {
e.preventDefault();
// 폼 제출 이벤트 추적
tracker.event('demo_form_submitted', {
has_name: !!userInfo.name,
has_email: !!userInfo.email,
timestamp: Date.now()
});
// 사용자 정보 업데이트
tracker.setMetadata('demo_user_name', userInfo.name);
tracker.setMetadata('demo_user_email', userInfo.email);
alert('데모 폼이 제출되었습니다!');
};
const handleErrorTest = () => {
// 의도적 오류 발생 (테스트용)
tracker.event('demo_error_test', {
test_type: 'intentional_error'
});
try {
// 존재하지 않는 함수 호출
nonExistentFunction();
} catch (error) {
console.error('데모 오류:', error);
// 오류 이벤트 수동 추적
tracker.event('demo_javascript_error', {
error_message: error.message,
error_stack: error.stack,
user_triggered: true
});
}
};
return (
<div style={{ padding: '20px', fontFamily: 'Arial, sans-serif' }}>
<h1>OpenReplay 데모 애플리케이션</h1>
<div style={{ marginBottom: '30px', padding: '20px', border: '1px solid #ccc' }}>
<h2>카운터 테스트</h2>
<p>현재 카운트: {counter}</p>
<button onClick={handleCounterClick}>
카운트 증가
</button>
</div>
<div style={{ marginBottom: '30px', padding: '20px', border: '1px solid #ccc' }}>
<h2>폼 입력 테스트</h2>
<form onSubmit={handleFormSubmit}>
<div>
<label>
이름:
<input
type="text"
value={userInfo.name}
onChange={(e) => setUserInfo(prev => ({
...prev,
name: e.target.value
}))}
placeholder="이름을 입력하세요"
style={{ marginLeft: '10px', padding: '5px' }}
/>
</label>
</div>
<div style={% raw %}{{ marginTop: '10px' }}>
<label>
이메일:
<input
type="email"
value={userInfo.email}
onChange={(e) => setUserInfo(prev => ({
...prev,
email: e.target.value
}))}
placeholder="이메일을 입력하세요"
style={{ marginLeft: '10px', padding: '5px' }}
/>
</label>
</div>
<button type="submit" style={% raw %}{{ marginTop: '10px' }}>제출</button>
</form>
</div>
<div style={{ marginBottom: '30px', padding: '20px', border: '1px solid #ccc' }}>
<h2>오류 테스트</h2>
<button onClick={handleErrorTest}>
의도적 오류 발생
</button>
</div>
<div style={{ padding: '20px', border: '1px solid #ccc' }}>
<h2>개발자 도구</h2>
<p>브라우저 개발자 도구를 열어 OpenReplay 로그를 확인하세요.</p>
<button onClick={{() => {
console.log('OpenReplay 콘솔 로그 테스트');
console.warn('OpenReplay 경고 메시지 테스트');
console.error('OpenReplay 오류 메시지 테스트');
}}}>
콘솔 로그 생성
</button>
</div>
</div>
);
}
export default OpenReplayDemo;
macOS 개발환경 스크립트
#!/bin/bash
# setup-openreplay-demo.sh
echo "🚀 OpenReplay 데모 환경 설정 시작..."
# 현재 디렉토리 확인
CURRENT_DIR=$(pwd)
echo "📁 작업 디렉토리: $CURRENT_DIR"
# Node.js 설치 확인
if ! command -v node &> /dev/null; then
echo "❌ Node.js가 설치되어 있지 않습니다."
echo "💡 Homebrew로 설치: brew install node"
exit 1
fi
echo "✅ Node.js 버전: $(node --version)"
# Docker 설치 확인
if ! command -v docker &> /dev/null; then
echo "❌ Docker가 설치되어 있지 않습니다."
echo "💡 Homebrew로 설치: brew install --cask docker"
exit 1
fi
echo "✅ Docker 버전: $(docker --version)"
# OpenReplay 서버 디렉토리 생성
if [ ! -d "openreplay-server" ]; then
echo "📦 OpenReplay 서버 클론 중..."
git clone https://github.com/openreplay/openreplay.git openreplay-server
fi
# React 데모 앱 생성
if [ ! -d "openreplay-demo-app" ]; then
echo "📦 React 데모 앱 생성 중..."
npx create-react-app openreplay-demo-app
fi
cd openreplay-demo-app
# OpenReplay 트래커 설치
echo "📦 OpenReplay 트래커 설치 중..."
npm install @openreplay/tracker @openreplay/tracker-assist
# 환경 변수 파일 생성
if [ ! -f ".env.local" ]; then
echo "🔧 환경 변수 파일 생성..."
cat > .env.local << EOF
# OpenReplay 설정
REACT_APP_OPENREPLAY_PROJECT_KEY=your_project_key_here
REACT_APP_OPENREPLAY_INGEST_POINT=http://localhost:9000/ingest
# 데모 모드 활성화
REACT_APP_DEMO_MODE=true
EOF
echo "📝 .env.local 파일을 편집하여 실제 프로젝트 키를 입력하세요."
fi
# 데모 시작 스크립트 생성
cat > start-demo.sh << 'EOF'
#!/bin/bash
echo "🔍 OpenReplay 데모 모드 시작..."
# OpenReplay 서버 시작 (백그라운드)
echo "🚀 OpenReplay 서버 시작 중..."
cd ../openreplay-server/scripts/docker-compose
docker-compose up -d
echo "⏳ 서버 초기화 대기 중 (30초)..."
sleep 30
# React 데모 앱 시작
echo "🌐 React 데모 앱 시작..."
cd ../../openreplay-demo-app
echo "💡 브라우저에서 http://localhost:3000 을 열어주세요."
echo "🎯 OpenReplay 대시보드는 http://localhost:9000 에서 확인할 수 있습니다."
npm start
EOF
chmod +x start-demo.sh
# 정리 스크립트 생성
cat > stop-demo.sh << 'EOF'
#!/bin/bash
echo "🛑 OpenReplay 데모 정리 중..."
# React 앱 중지 (별도 터미널에서 Ctrl+C)
echo "📝 React 앱은 별도 터미널에서 Ctrl+C로 중지하세요."
# OpenReplay 서버 중지
echo "🚀 OpenReplay 서버 중지 중..."
cd ../openreplay-server/scripts/docker-compose
docker-compose down
echo "✅ 데모 정리 완료!"
EOF
chmod +x stop-demo.sh
echo "✅ OpenReplay 데모 환경 설정 완료!"
echo ""
echo "🚀 다음 단계:"
echo " 1. .env.local 파일에 OpenReplay 프로젝트 키 입력"
echo " 2. ./start-demo.sh 실행"
echo " 3. http://localhost:3000 에서 데모 테스트"
echo " 4. http://localhost:9000 에서 OpenReplay 대시보드 확인"
echo " 5. 완료 후 ./stop-demo.sh 실행"
결론
OpenReplay는 자체 호스팅이 가능한 강력한 세션 리플레이 및 사용자 분석 플랫폼입니다. 이 튜토리얼을 통해 다음을 학습했습니다:
핵심 성과
- 포괄적인 사용자 추적: 세션 리플레이부터 실시간 지원까지
- 완전한 데이터 제어: 자체 호스팅으로 보안과 프라이버시 보장
- 개발자 친화적: 다양한 프레임워크 지원과 풍부한 API
- 확장 가능한 아키텍처: Docker와 Kubernetes를 통한 프로덕션 배포
다음 단계
- 고급 분석 구현: 비즈니스 메트릭과 연동한 커스텀 대시보드 구축
- AI 기반 인사이트: 머신러닝을 활용한 사용자 행동 패턴 분석
- 멀티 플랫폼 확장: 모바일 앱과 데스크톱 애플리케이션 추적
- 실시간 알림: 중요한 이벤트에 대한 즉시 알림 시스템 구축
유용한 리소스
OpenReplay로 사용자 경험을 한 단계 업그레이드하고 데이터 주권을 확보하세요! 🚀
이 튜토리얼이 도움이 되었다면 GitHub에서 ⭐를 눌러주세요.