[기술블로그] EP8. 사용자의 첫 5분을 상상하는 일: 신입 개발자의 온보딩 플로우 구현기
안녕하세요! 키냅스(Kynapse)의 풀스택 개발을 맡고 있는 개발자 윤유빈입니다.
개발자로서 첫 단독 기능을 맡게 되었을 때, 저는 그동안 많이 다뤄 본 작업들과는 조금 결이 다른 작업을 받았습니다. 버그 수정도, 기존 화면 개선도 아닌 “온보딩을 만들어 보자”라는 제안이었죠.
솔직히 처음엔 기능에 대한 의문이 앞섰습니다.
“지금도 문제가 없는데, 꼭 필요한 플로우일까?“
우리 서비스는 아직 초기 단계라 이탈률이 문제로 드러난 적도 없었고, “어디서 뭘 설정해야 하죠?” 같은 지원 문의가 쌓여 있는 것도 아니었으니까요. 그런데 관점을 살짝 바꿔서 생각해 보니, 답이 달라졌습니다.
“아직 문제가 보이지 않는다”는 건, 문제가 없다는 뜻이 아니라 문제를 관측할 환경이 아직 갖춰지지 않았다는 뜻일 수 있다.

처음 서비스에 들어온 팀 오너의 입장에서, 워크스페이스의 첫 화면은 생각보다 막막합니다.
팀 이름도, 로고도, 안에 놓인 샘플 문서도 있긴 하지만 — 전부 기본값이죠. 내 팀 이름이 아니고, 내 로고가 아니고, 내가 쓰려던 문서가 아닙니다.
그 “남의 템플릿 같은” 화면 앞에서 “자, 이제 뭘 해야 하죠?”라고 물을 사용자를 먼저 떠올려 보니
— 온보딩은 지표가 시켜서 만드는 게 아니라, 지표가 나빠지기 전에 만들어야 하는 기능이라는 생각이 들었습니다.
그리고 또 한 가지.
온보딩은 사용자에게만 필요한 게 아니었습니다.
우리 팀 입장에서도 “지금 들어오는 사용자들이 누구인가”를 알고 싶었죠.
어떤 규모의 팀이 주로 들어오는지, 어떤 산업군의 사용자가 많은지, 어떤 기능을 기대하며 가입하는지
— 이 정보 없이는 마케팅 방향을 잡을 수도, 서비스의 우선순위를 정할 수도 없습니다.
즉, 온보딩 중간에 넣은 설문 단계는 사용자를 위한 장치이기 이전에,
우리가 사용자를 처음으로 이해할 수 있는 거의 유일한 접점이기도 했습니다.
이번 글에서는 신입 개발자가 4단계 온보딩을 구현하며 고민했던 UX와 기술적 선택들,
그리고 두 번의 시행착오를 어떻게 해결했는지 공유하려 합니다.
[EP9. 핵심 요약]
-
4단계 플로우의 의도를 이해하기
한 화면에 다 넣는 대신 4단계로 쪼갠 이유를, 구현하며 직접 납득한 과정 -
상태의 “수명”에 따라 담을 그릇을 나누기
플로우가 끝나면 사라져야 할 데이터와, 계속 남아야 할 데이터를 구분해서 저장 -
지연된 호출로 불필요한 사이드 이펙트 막기
초대 메일을 “수집 시점”이 아닌 “완료 시점”에 일괄 발송 -
시행착오 #1 — 뒤로가기 한 번에 메일이 두 번
중복 발송 이슈를 프론트 + 백엔드 이중 방어로 해결 - 시행착오 #2 — 권한의 사각지대
서버 권한은 조작을 막아 주지만, 애초에 들어올 필요 없는 사람이 플로우를 밟고 있는 문제는 별도로 진입 가드로 차단 - 앞으로
상상으로 만든 설계를, 사용자 로깅 데이터로 검증해 나갈 계획
| 왜 한 페이지가 아니라 4단계일까
온보딩 작업에 들어가기 전, 저는 이미 기획에서 정리된 “4단계 구조”를 전달받은 상태였습니다.
|
단계 |
사용자에게 주는 메시지 |
|---|---|
|
Step 1. 워크스페이스 설정 |
“당신의 공간을 만들어 주세요” |
|
Step 2. 설문 |
“어떤 팀이신지 알려 주세요” |
|
Step 3. 팀원 초대 |
“함께할 사람들을 불러 주세요” |
|
Step 4. 첫 문서 생성 |
“시작할 준비가 끝났어요” |
솔직히 처음엔 “이걸 굳이 나눠야 하나?” 싶었습니다. 설정할 항목들이 그렇게 많지 않았거든요.
한 페이지에 쭉 배치해도 스크롤 몇 번이면 끝날 분량인데, “한 화면에 다 보여 주는 게 오히려 친절한 게 아닐까?”라는 쪽에 가까웠죠.
그런데 구현을 시작하면서 생각이 바뀌었습니다.
한 화면에 모든 정보를 쌓는 건 개발자 입장에서 친절한 거지, 사용자 입장에서 친절한 게 아니더군요.
한눈에 보이는 폼이 길면 사용자는 일단 스크롤부터 하면서 “얼마나 오래 걸릴까”를 가늠하고, 그 순간 심리적 피로가 시작됩니다.
한 번에 하나씩만 보여 주는 것이, 사용자의 인지 부하를 줄이는 가장 단순하면서도 강력한 방법이라는 점진적 공개(Progressive
Disclosure) 원칙을 이때 알게 됐습니다.
단계로 쪼개는 일의 숨은 효용
단계를 나누면 부수적인 효과가 따라옵니다.
-
어디서 떠나는지 보인다 — 이탈 지점이 단계 단위로 측정 가능해집니다.
“Step 2 이탈률이 높다 → 설문이 길다 → 줄이자” 같은 구체적 개선 판단이 가능해지죠. -
필요에 맞게 끊을 수 있다 — Step 2(설문)와 Step 3(초대)은 스킵 가능하게 설계되어 있었습니다.
“넘어가도 괜찮다”는 선택지를 주는 것만으로도, 이탈 대신 완료로 넘어가는 사용자가 늘어날 수 있거든요.
단순한 UI 분리로 보였던 설계가, 실은 미래의 개선 가능성까지 열어 두는 구조적인 결정이라는 걸 구현하면서 비로소 이해하게 됐습니다.
| 단계 사이의 데이터는 어디에 둘 것인가
4단계로 나뉘어 있다는 건, 각 단계가 서로 다른 화면이라는 뜻입니다.
화면을 넘나들며 모은 데이터를 어디에 잠시 담아 둘지를 결정해야 했습니다.
이 고민의 핵심은 “어느 저장소가 가장 좋은가”가 아니었습니다. 진짜 질문은 이거였죠.
“이 데이터는 얼마나 오래 살아 있어야 하는가?”

데이터마다 수명이 달랐습니다.
-
설문 응답 — 서버로 전송되고 나면 프론트에서는 더 들고 있을 이유가 없는 데이터입니다.
Step 4 완료 시점에 한 번 제출하고 나면, 프론트 쪽에서는 플로우가 닫히는 순간 자연스럽게 사라져도 상관없습니다. -
초대할 이메일 목록 — 나중에 다른 로직(초대 API 호출)과 함께 쓰여야 하는 데이터입니다.
특정 화면에만 매어 두면 오히려 불편해집니다.
그래서 프론트에서 들고 있는 방식을 각자의 수명에 맞춰 나눴습니다.
전자는 온보딩 플로우 내부에서만 공유되는 임시 저장소에, 후자는 더 넓게 접근 가능한 앱 전역 저장소에요.
처음엔 무심코 “다 한 곳에 넣으면 되지”라고 생각했는데, 수명을 기준으로 저장 위치를 나누는 것만으로도 코드를 읽는 사람 입장에서 의도가 훨씬 분명해졌습니다.
“이 데이터는 플로우가 끝나면 프론트에서 잊혀져도 괜찮아”
vs
“이 데이터는 다른 화면에서도 계속 쓰일 수 있어”
이 한 줄의 차이를, 저장 위치가 대신 말해주는 구조였습니다.
|초대 메일은 “언제” 보낼 것인가
가장 고민을 많이 한 부분입니다.
자연스럽게 생각하면, Step 3에서 이메일을 입력하는 순간 초대 메일이 나가는 것이 맞습니다. “초대” 버튼을 눌렀는데 메일이 당장 안 간다면 그게 더 이상한 UX니까요. 초기 구현도 그렇게 했습니다.
그런데 한 가지 시나리오를 떠올려 보니 문제가 보였습니다.
사용자가 Step 3에서 이메일을 입력해서 초대는 했는데, Step 4를 완료하지 않고 창을 닫아 버리면?
아직 오너가 준비 중인 상태에서 초대만 먼저 나가 버립니다.
초대받은 동료는 기본 템플릿 그대로 남아 있는 워크스페이스로 덜컥 들어오게 되죠.
-
오너 입장 — “아직 세팅도 안 끝냈는데 사람을 부른 꼴”
-
초대받은 사람 입장 — “초대는 왔는데 들어가 보니 기본값 그대로네?”
둘 다 좋지 않죠. 그래서 구조를 뒤집었습니다.
Step 3에서는 이메일을 “모으기만” 하고, 실제 초대 발송은 Step 4 완료 버튼을 눌렀을 때 일괄 처리한다.
이제 Step 3은 이메일을 받아서 앱 상태에 담아 두는 일만 합니다. 메일은 한 통도 나가지 않습니다. 대신 사용자가 Step 4의 “완료” 버튼을 누르는 그 순간, 그동안 미뤄 두었던 일들이 한꺼번에 실행됩니다.
-
쌓여 있던 이메일 목록을 꺼내 초대 메일을 일괄 발송
-
사용자가 미리 업로드해 둔 파일을 새 워크스페이스로 임포트
-
온보딩 완료 플래그를 남기고 홈으로 이동
이렇게 바꾸니 온보딩을 끝까지 완주한 사용자에게만 초대가 나갑니다. 중간 이탈 시 빈 초대가 쌓일 일이 없어졌죠.
이 패턴에는 이름이 있습니다. 지연된 사이드 이펙트(Deferred Side Effects) — 되돌릴 수 없는 작업은 최대한 “확정된 시점”으로 미룬다는 원칙입니다. 메일 발송, 결제, 외부 알림처럼 한 번 나가면 되돌릴 수 없는 행동에서 특히 유용한 사고방식이죠.
완벽해 보였습니다. 적어도 며칠 뒤 팀내 테스트를 돌리기 전까지는요.
| 시행착오 ① — “뒤로가기 누르면 메일이 두 번 오는데요?”
연 발송 구조로 바꾸고 며칠 뒤, 팀내 테스트 중에 이런 피드백을 받았습니다.
“저 방금 뒤로 갔다가 다시 완료 눌렀는데, 메일이 두 번 왔어요.”
머리를 한 대 맞은 기분이었습니다.
저는 “중간에 이탈하는 사용자” 만 머릿속에 두고 지연 호출을 설계했는데, 정작 끝까지 가는 사용자가 여러 번 도착하는 경우 는 전혀 고려하지 않고 있었던 거죠.
-
완료 후 홈 화면으로 이동했다가, 뒤로가기로 돌아와 완료를 또 누르는 경우
-
온보딩 URL로 나중에 다시 접속하는 경우
모두 같은 사용자에게 같은 메일이 여러 번 가는 결과로 이어졌습니다.
해결 — 이중 방어
한 겹만 막으면 언젠가 새는 구멍이 생깁니다. 그래서 프론트와 백엔드 양쪽에서 막기로 했습니다.
① 프론트 — 발송 완료 플래그
이메일 목록을 저장할 때 “이미 발송이 끝난 이메일”을 표시하는 상태를 함께 관리했습니다. 완료 로직은 이 표시를 확인해서 아직 안 보낸 이메일만 걸러 보냅니다. 버튼을 여러 번 눌러도 두 번째부터는 보낼 이메일이 없으니 메일이 안 나갑니다.
② 백엔드 — 재진입 차단
하지만 프론트에만 의존하면 위험합니다. 사용자가 새 탭에서 URL을 직접 치고 들어오면 앱 상태가 초기화돼서 “처음부터 다시”가 될 수도 있고, 프론트 코드에 버그가 생길 수도 있으니까요.
그래서 서버에서도 한 번 더 걸었습니다. 이미 온보딩을 완료한 사용자가 온보딩 경로로 접근하면, 자동으로 홈으로 돌려보내도록 한 거죠. 한 번 끝낸 플로우에는 다시 들어올 수 없게 만든 겁니다.
이 두 겹이 합쳐지고 나서야 비로소 “몇 번을 눌러도 안전한” 구조가 완성됐습니다.
여기서 배운 건 하나입니다.
UX 설계에서는 “정상 경로”만큼이나 “정상이 아닌 경로”를 먼저 상상해 봐야 한다.
뒤로가기, 새로고침, URL 직접 입력, 네트워크 지연 — 이 네 가지는 이제 제 체크리스트의 고정 항목입니다.
|5. 시행착오 ② — 이 화면, 이 사람이 봐도 되는 거였나?

온보딩이 거의 마무리될 즈음, 동료가 버그 하나를 제보해 주셨습니다.
“저 팀에 초대받아 들어온 건데, 온보딩화면에 들어가지네요? 오너도 아닌데 이 화면 제가 봐도 되는 거 맞아요?”
등골이 서늘해졌습니다.
온보딩은 팀 오너가 자기 워크스페이스를 세팅하는 플로우인데,
팀에 초대받아 들어온 일반 멤버도 그 화면에 들어갈 수 있었던 겁니다.
다행히 팀 이름이나 설정 변경 같은 실제 데이터 조작은 서버 쪽 권한 체크로 이미 막혀 있었습니다.
Step 1에서 일반 멤버가 팀 이름을 바꿔 “저장”을 눌러도,
서버는 “당신은 오너가 아니라 이 작업을 할 수 없습니다” 하고 튕겨낼 수 있었죠.
그래서 돌이킬 수 없는 사고는 일어나지 않았습니다.
하지만 문제는 따로 있었습니다.
권한 체크에 막혀서 실패하긴 하지만, 그 전까지 일반 멤버가 오너용 온보딩 화면을 보고, 클릭하고, 폼을 채워 보는 이상한 경험을 하고 있었다는 겁니다.
-
오너가 아닌데도 “당신의 공간을 만들어 주세요”라는 환영 메시지를 보게 되고,
-
팀 이름을 바꿔 보려다 저장 시점에야 이름 모를 에러를 만나고,
-
건너뛰기도 할 수 없는 화면에 갇혀 “어? 이거 내가 할 수 있는 일이 없는데?” 라고 뒤늦게 깨닫는,
애초에 이 사람이 밟을 필요가 없는 플로우를 끝까지 내려가며 시간과 주의력을 낭비하는 상황이 되어 있었던 것이죠.
겉보기에는 보안 이슈가 아니지만, UX 관점에서는 명백한 설계 누락이었습니다.
해결은 기술적으로는 단순했습니다.
온보딩 플로우에 진입하는 전 팀 오너인지 먼저 확인해서, 오너가 아니면 아예 홈으로 돌려보내는 가드 한 겹을 추가한 것뿐이었죠.
서버가 요청을 거절하기 전에, 프론트에서 이 사람이 여기 들어올 필요가 없다는 것을 먼저 판단하도록 만든 겁니다.
하지만 이 작은 한 겹이 남긴 교훈은 생각보다 컸습니다.
“이 동작을 못 막는가”뿐 아니라 “이 사람이 여기 있을 이유가 있는가”까지 물어야 한다.
권한 검사는 흔히 “나쁜 일을 막는 것”으로만 여겨지지만,
사실은 각 사용자를 자기에게 의미 있는 화면에만 있도록 안내하는 일이기도 합니다.
저는 이번에 그 두 역할이 한 쌍이라는 걸 몸으로 배웠습니다.
|마치며 — 신입 개발자가 온보딩을 만들며 배운 것
온보딩 작업 이전까지 저에게 “개발”은 주로 주어진 기능을 정확히 구현하는 일에 가까웠습니다.
그런데 이번 작업은 조금 달랐습니다.
기획에서 내려온 4단계 구조를 그대로 받아 구현하는 일처럼 보였지만,
코드를 쓰다 보니 “왜 이렇게 나뉘어 있을까”를 스스로 납득하는 과정이 필요했습니다.
그 과정에서 상태를 어디에 둘지, 언제 사이드 이펙트를 발생시킬지, 잘못된 경로로 들어오는 사용자는 어떻게 막을지를 하나씩 직접 결정해야 했고, 그제서야 이 기능 하나를 진짜로 “이해”하게 된 것 같습니다.
이번 프로젝트를 통해 머릿속에 남은 세 가지 원칙은 이렇습니다.
-
1.지표가 시키기 전에, 먼저 상상하기
— 아직 문제가 지표로 드러나지 않았다고 해서 문제가 없는 건 아닙니다.
사용자의 첫 5분을 먼저 머릿속에서 살아 보는 일은 신입 개발자도 충분히 할 수 있고, 어쩌면 가장 잘할 수 있는 일일지도 모릅니다. -
2.정상 경로만큼, 비정상 경로를 설계하기
— 뒤로가기 · 새로고침 · URL 직접 입력 · 네트워크 지연. 이 네 가지는 이제 모든 UI 기능 개발에서 체크리스트처럼 자문하게 됐습니다. -
3.상태는 적을수록 강하다
— 하나로 표현할 수 있는 상태를 두세 개로 쪼개는 순간, 버그가 자랄 틈이 생깁니다.
파생할 수 있는 것은 저장하지 않는다이 원칙을 앞으로도 곳곳에서 써먹고 싶습니다.
|앞으로 — “상상”을 “데이터”로 바꿔 나가기
이번 온보딩은 지표 없이 사용자를 상상하며 만든 기능입니다.
저희가 내린 판단들 — 4단계가 적절한지, 스킵을 허용한 게 맞는지, 첫 문서 업로드를 마지막 단계에 둔 게 맞는지 — 중 어느 하나도 아직 실제 사용자 데이터로 검증되지 않았습니다.
지금은 가설로 이루어진 설계인 셈이죠.
그래서 다음으로 준비하고 있는 작업은 사용자 행동 로깅과 지표 수집입니다.
이런 질문들에 답할 수 있는 데이터를 쌓으려 합니다.
-
단계별 이탈률 — 어느 Step에서 가장 많이 떠나는가?
-
완주 시간 분포 — 평균 완주 시간은 얼마이고, 너무 오래 걸리는 사용자는 어디서 막히는가?
-
스킵 비율 — 스킵 가능한 단계에서 실제로 몇 %가 스킵을 택하는가? 스킵한 사용자와 끝까지 채운 사용자의 리텐션은 다른가?
-
설문 응답 분포 — 들어오는 사용자들의 팀 규모·산업군·사용 목적은 어떻게 나뉘는가? 마케팅이 겨냥한 타깃과 실제 유입층이 일치하는가?
이런 지표가 쌓이면, 지금은 “아마 이럴 것이다”라고 가정한 판단들을 하나씩 검증하거나 뒤집을 수 있게 됩니다.
Step 2 이탈률이 유난히 높다면 설문을 더 줄이거나 뒤로 미루는 선택지가 생기고,
설문 응답이 마케팅 타깃과 크게 어긋난다면 광고 채널이나 랜딩 페이지를 다시 점검해야 한다는 신호가 됩니다.
결국 이번 글에서 만든 건 “완성된 온보딩”이라기보다는 “앞으로 계속 다듬어 나갈 1차 버전”에 가깝습니다.
상상으로 만든 것을 데이터로 검증하고, 그 데이터로 다시 다음 가설을 세우는 사이클 — 이게 기능 하나가 진짜로 성숙해지는 과정이라는 걸 이번 프로젝트를 통해 알게 됐습니다.
앞으로 제가 만드는 기능들도 이 네 가지를 기준점으로 삼을 생각입니다.
“사용자 입장에서 먼저 상상하고, 비정상 경로를 함께 설계하고,
상태를 단순하게 유지하고, 만든 뒤에는 반드시 데이터로 검증한다.”
이런 고민들과 개선 과정을 바탕으로, 더 나은 서비스를 만들어가고 있습니다.
앞으로도 지속적으로 발전해 나갈 키냅스의 모습을 지켜봐 주세요!