내가 참여 중인 프로젝트에서는, 탈퇴 사유 수집을 서버 DB에 저장해놓지 않고 Amplitude에 분석 데이터로 저장해놓는 방법을 선택했다.

Amplitude란?
Amplitude는 서비스를 이용하는 사용자가 취하는 모든 행동(클릭, 페이지 방문, 구매 등)을 트래킹할 수 있는 툴이다. 기존에 사용하던 툴인 GA는 개인별 추적을 지원하지 않고, 데이터가 실시간으로 수집되지않아 즉각적인 변화를 확인할 수 없다. 그러나 Amplitude는 이러한 단점을 모두 보완한다.
Amplitude는 다양한 방식으로 활용할 수 있지만, 우리 서비스에서는 회원탈퇴 페이지에서 '탈퇴하기' 버튼을 누른 사용자와 그들이 선택한 탈퇴 사유를 추적하는 데 활용하기로 했다. 사용자가 선택한 옵션 값은 Amplitude 대시보드에서 바로 확인할 수 있으며, 이를 통해 서비스 탈퇴의 주요 원인을 분석하고 개선 방향을 도출할 수 있다. 또한, 이는 서버 자원을 아끼기 위한 선택이기도 하다 —> 굳이 별도 서버 이벤트 저장 없이 Amplitude를 통해 손쉽게 수집 및 시각화가 가능하기 때문이다.

Amplitude로 탈퇴 사유 트래킹하기
1. 시작하기
package로 설치하는 방법이 있고, 사이트에 바로 나와있는대로 script를 추가해주는 방법이 있다.
내 프로젝트는 TypeScript를 사용하고 있기 때문에, 나는 타입 정의가 자동으로 제공되는 패키지 설치 방식을 선택했다.
pnpm add @amplitude/analytics-browser
위 명령어를 입력해주면 된다.
2. 이벤트 추적 시작
프로젝트의 최상단에 amplitude를 시작한다는 코드를 넣어보자. 나 같은 경우는 App.tsx에 추가했다!
이때 API key는 amplitude 사이트에서 회원가입만 하면 발급된다.
import { init } from '@amplitude/analytics-browser';
init(import.meta.env.VITE_AMPLITUDE_API_KEY);
function App() {}
이렇게 init 함수를 App함수 밖으로 빼주면, amplitude 초기화가 애플리케이션이 시작될 때 한 번만 발생하게 된다.
컴포넌트 함수 내부에서 실행하면 컴포넌트가 리렌더링될 때마다 실행될 수 있어 불필요한 초기화가 반복될 수 있으므로..
3. 이벤트 설정 후 보내기
이벤트를 Amplitude 서버로 전송하려면 Amplitude SDK에서 제공하는 핵심 메소드인 track()을 사용하면 된다.
예를 들어, 추적하려는 이벤트가 "버튼 클릭"일때:
import { track } from '@amplitude/analytics-browser';
// 1
const handleButtonClick = () => {
track('Button Clicked');
}
// 2
const eventProperties = {
buttonColor: 'primary',
};
const handleButtonClick = () => {
track('Button Clicked', eventProperties);
}
1번 형태는 가장 간단한 형태로, 이벤트 이름만 전달한다.
2번 속성이 있는 이벤트를 추적할 때, 이벤트 이름과 함께 추가 속성을 객체 형태로 전달한다.
이 외에도 더 복잡한 이벤트 구조를 정의할 수도 있다고 한다. (ex: BaseEvent)
자 그럼 내가 실제 탈퇴 페이지 컴포넌트에서 작성한 코드를 예시로 보여주겠다.
1. 탈퇴 사유 수집:
const [selectedReason, setSelectedReason] = useState<string>('');
사용자가 선택한 탈퇴 사유를 state로 관리한다.
2. 이벤트 트래킹 실행:
const handleConfirmDeleteAccount = () => {
// Amplitude에 탈퇴 사유 이벤트 전송
track('User Withdrawal', {
reason: selectedReason,
reason_text: reasons.find((r) => r.value === selectedReason)?.text,
});
deleteAccountMutation.mutate();
};
사용자가 최종적으로 탈퇴를 확정할 때 track 함수를 호출하여 Amplitude에 데이터를 전송한다.
- 이벤트 이름:
- 'User Withdrawal'
- 이벤트 속성:
- reason: 선택된 사유의 코드값 (예: 'no_events')
- reason_text: 선택된 사유의 실제 텍스트 (예: '원하는 공연이 많이 없어서')
전체 코드 ⏬
import { useState } from 'react';
import { track } from '@amplitude/analytics-browser';
import { useDeleteAccountMutation } from '@pages/my/hooks/use-delete-account';
import { useOverlay } from 'node_modules/@confeti/design-system/src/context/overlay-context';
import { Button, Dialog, Footer, Header } from '@confeti/design-system';
import * as styles from './delete-account.css';
const DeleteAccount = () => {
const overlay = useOverlay();
const [selectedReason, setSelectedReason] = useState<string>('');
const deleteAccountMutation = useDeleteAccountMutation();
const reasons = [
{ value: 'no_events', text: '원하는 공연이 많이 없어서' },
{ value: 'frequent_errors', text: '잦은 오류, 장애가 발생해서' },
{ value: 'inconvenient', text: '이용하는데 편리하지 않아서' },
{ value: 'rejoin', text: '다른 계정으로 재가입하려고' },
];
const handleConfirmDeleteAccount = () => {
// Amplitude에 탈퇴 사유 이벤트 전송
track('User Withdrawal', {
reason: selectedReason,
reason_text: reasons.find((r) => r.value === selectedReason)?.text,
});
deleteAccountMutation.mutate();
};
const handleDialogOpen = () => {
overlay.open(({ isOpen, close }) => (
<Dialog open={isOpen} handleClose={close}>
<Dialog.Content>
<Dialog.Title>정말 confeti를 탈퇴하실건가요?</Dialog.Title>
<Dialog.Description>
탈퇴 시 계정 및 이용 기록은 모두 삭제되며, <br />
삭제된 데이터는 복구가 불가능합니다. <br />
탈퇴를 진행할까요?
</Dialog.Description>
</Dialog.Content>
<Dialog.Action>
<Button text="취소하기" onClick={close} variant="back" />
<Button
text="탈퇴하기"
onClick={() => {
handleConfirmDeleteAccount();
close();
}}
/>
</Dialog.Action>
</Dialog>
));
};
return (
<>
<Header variant="detail" title="회원탈퇴" />
<main className={styles.selectSection}>
<div className={styles.textStyle}>
탈퇴하시려는 이유를 선택해주세요.
</div>
<div className={styles.radioWrapper}>
{reasons.map((reason) => (
<label key={reason.value} className={styles.label}>
<input
type="radio"
name="withdrawal_reason"
value={reason.value}
checked={selectedReason === reason.value}
onChange={() => setSelectedReason(reason.value)}
className={styles.radioStyle}
/>
<span>{reason.text}</span>
</label>
))}
</div>
</main>
<div className={styles.buttonWrapper}>
<Button
variant="add"
text="탈퇴하기"
disabled={!selectedReason}
onClick={handleDialogOpen}
/>
</div>
<Footer />
</>
);
};
export default DeleteAccount;
탈퇴하기 버튼 클릭 시, 이벤트 이름과 이벤트 속성이 Amplitude로 전송되고, 이는 대쉬보드에서 확인할 수 있다!
해당 데이터는 차트나 데이터베이스 형태로 시각화하여 확인할 수 있으며, CSV 파일로도 다운로드가 가능하다.
나는 이 데이터베이스 링크를 슬랙에 공유하여 기획 파트가 쉽게 확인할 수 있도록 했다.

참고 링크:
https://amplitude.com/docs/sdks/analytics/browser/browser-sdk-2
Browser SDK 2
Starting from v2.8.0 the SDK supports getting the device ID from the URL paramter ampDeviceId. The SDK configuration, for example, init('API_KEY', { deviceId: 'custom-device-id' }) still takes precedence over the URL parameter. Previous versions of the SDK
amplitude.com
'개발' 카테고리의 다른 글
| confeti 웹 성능 최적화 여정 (1) | 2025.05.27 |
|---|---|
| [Typescript] Typescript를 왜 사용하는지에 대해 알아보자 (2) | 2025.04.06 |