처음에는 제 불편을 줄이려고 만든 작은 도구였어요.
영어로 된 기술 문서를 읽다가 문장이 잘 들어오지 않으면, 옆에 번역된 글을 띄워두고 빠르게 비교하곤 했어요. 그런데 원문과 번역문을 오갈 때마다 같은 위치를 다시 찾아야 했고, 그 과정에서 읽기의 흐름이 자주 끊겼어요.
그래서 원하는 탭의 스크롤을 함께 움직일 수 있는 확장 프로그램을 만들기 시작했어요. 기능만 보면 단순해 보여요. 한 탭을 스크롤하면 다른 탭도 같이 움직이게 하는 일이니까요.
하지만 실제로 만들어보니 이 문제는 단순한 스크롤 이벤트 처리 문제가 아니었어요. 문서마다 구조가 달랐고, 모든 차이를 자동으로 맞추기보다 사용자가 필요할 때 기준점을 조정할 수 있게 하는 편이 더 현실적이라고 판단했어요. 웹페이지의 CSS가 확장 프로그램의 스크롤 제어와 충돌하기도 했고, 사용자가 늘어난 뒤에는 기존 동작을 바꾸지 않고 새 기능을 추가하는 일도 중요해졌어요.
이 글에서는 synchronize-tab-scrolling을 만들게 된 배경과, 개인 도구가 작은 제품이 되어가며 바뀐 판단 기준을 정리해보려고 해요. 구현 코드 자체보다, 왜 이 기능이 필요했고 어떤 기준으로 UX와 기술 선택을 했는지에 초점을 맞췄어요.
특히 다음 세 가지를 중심으로 이야기해요.
- 읽기 흐름을 끊는 문제를 어떻게 정의했는지
- 자동화와 사용자 통제권 사이에서 어떤 선택을 했는지
- 실제 사용자가 생긴 뒤 기존 기대를 깨뜨리지 않는 제품 운영을 어떻게 고민했는지
왜 스크롤 동기화가 필요했나
당시 읽고 있던 글은 Chrome 개발자 블로그의 원문과 네이버 D2에 번역된 글이었어요. 원문을 중심으로 읽다가 문장이 잘 들어오지 않으면 번역문을 확인하고, 다시 원문으로 돌아오는 식이었죠.
문제는 번역문을 볼 때마다 같은 위치를 다시 찾아야 한다는 점이었어요.
두 문서는 같은 내용을 다루고 있었지만 구조가 완전히 같지는 않았어요. 원문에는 목차가 있었고, 번역문에는 없었어요. 문장을 번역하면 길이도 달라져요. 이런 차이는 긴 글을 읽을수록 조금씩 누적됐어요.

원문에서 어느 정도 읽다가 번역문을 확인하려고 하면, 번역문 탭을 직접 스크롤하며 비슷한 문장을 찾아야 했어요. 확인을 마친 뒤에는 다시 원문으로 돌아와야 했고요. 한두 번이면 괜찮지만, 긴 시리즈 글을 읽는 동안 계속 반복되면 읽기의 흐름이 끊겨요.
제가 해결하고 싶었던 건 단순히 “스크롤을 두 번 하기 귀찮다”는 문제가 아니었어요. 더 정확히는, 문서를 이해하는 데 써야 할 집중력을 위치 찾기에 계속 빼앗기는 문제였어요.
이 기능의 출발점은 두 탭을 같은 비율로 움직이는 것이 아니었어요. 여러 페이지를 오가며 읽을 때, 지금 보고 있던 맥락을 잃지 않게 돕는 것이었어요.
이 글에서 다루는 핵심 문제는 스크롤 위치를 맞추는 일이 아니라, 원문과 번역문을 오가며 읽는 동안 같은 맥락을 반복해서 다시 찾지 않게 만드는 일이었어요.
만들지 않을 뻔했습니다
처음부터 직접 만들 생각은 아니었어요. 불편을 느낀 뒤 가장 먼저 한 일은 이미 비슷한 확장 프로그램이 있는지 찾아보는 것이었어요.
당시 제가 확인한 범위에서는 탭 스크롤을 동기화해주는 확장 프로그램이 Tab Scroll Synchronizer 하나뿐이었어요. 지금은 비슷한 확장 프로그램이 더 많아졌지만, 당시에는 선택지가 거의 없었습니다. 이후 제가 만든 확장 프로그램을 Chrome Web Store에 공개하면서 같은 문제를 다루는 또 하나의 선택지가 생겼어요.
이 글은 기존 도구를 평가하려는 글이 아니에요. 오히려 비슷한 문제를 먼저 제품으로 풀어낸 좋은 시도였고, 저는 그 사용 경험을 통해 제가 실제로 원하는 읽기 흐름을 더 선명하게 정리할 수 있었어요.
가장 먼저 아쉬웠던 건 스크롤 비율만 맞춰서는 제가 겪던 문제가 충분히 해결되지 않는다는 점이었어요.
원문과 번역문은 같은 내용을 담고 있어도 화면의 길이가 쉽게 달라져요. 번역문은 원문보다 길거나 짧을 수 있고, 어떤 문서에는 목차가 있지만 다른 문서에는 없을 수 있어요. 광고, 고정 헤더, 안내 배너처럼 본문과 직접 관련 없는 요소가 한쪽에만 들어가기도 해요. Staging과 Production을 비교할 때도 실험 UI나 배너 유무 때문에 높이가 달라질 수 있고요.
이런 상황에서는 두 페이지를 같은 비율로 스크롤해도 같은 문맥에 도착하지 않아요. 처음에는 비슷하게 맞는 것처럼 보여도, 긴 글을 읽을수록 차이가 조금씩 누적돼요. 그래서 제게 필요했던 것은 “같은 비율로 움직이는 기능”이 아니라, 사용자가 한 번 맞춰둔 위치 차이를 유지하면서 함께 스크롤하는 기능이었어요.
정리하면 당시 제 기대와 어긋났던 지점은 세 가지였어요.
- 스크롤 비율만 맞추는 방식으로는 같은 문맥을 안정적으로 찾기 어려웠어요.
- 설치 전에 열려 있던 탭에서는 바로 동작하지 않아 첫 사용 흐름이 끊겼어요.
- URL 이동 동기화가 없어 같은 사이트 안에서 언어만 다른 문서를 읽을 때 흐름을 이어가기 어려웠어요.
두 번째 지점인 설치 직후의 흐름은 특히 크게 느껴졌어요. 보통 이런 확장 프로그램은 문서를 읽다가 불편을 느낀 바로 그 순간 설치하게 돼요. 그런데 설치 전에 이미 열려 있던 탭에서 바로 동작하지 않으면, 사용자는 방금 보고 있던 문서를 닫고 다시 열어야 해요.
당시 저는 확장 프로그램을 개발해본 적이 없었지만, “설치하기 전에 열려 있던 탭이라서 그런가?” 하고 짐작해볼 수는 있었어요. 그래서 탭을 닫았다가 다시 열었고, 그제야 동작하는 것을 확인했어요. 하지만 이 흐름은 첫 사용 경험으로는 좋지 않다고 느꼈어요. 사용자는 기능을 써보려고 설치했는데, 바로 앞에서 동작하지 않으면 제품이 실패했다고 느끼기 쉬워요. 그래서 설치 전에 열려 있던 탭에서도 바로 동작해야 한다고 생각했어요.
URL 이동도 함께 맞춰지지 않았어요. 다만 이 부분은 모든 원문/번역문 조합에서 가능한 문제는 아니었어요. 서로 다른 사이트에 올라온 글처럼 path와 query 구조가 완전히 다르면 한쪽 이동을 다른 쪽에 그대로 적용하기 어렵고, 잘못하면 404로 이어질 수 있어요. 대신 같은 사이트 안에서 언어만 다른 문서를 함께 볼 때는, 한쪽에서 다음 페이지로 이동하면 다른 탭도 언어 맥락을 유지한 채 같은 흐름을 따라가면 좋겠다고 생각했어요.
이 경험 덕분에 첫 버전의 요구사항이 더 선명해졌어요. 더 많은 기능을 넣는 것보다, 제가 실제로 겪은 읽기 흐름의 끊김을 줄이는 데 집중하기로 했어요.
처음부터 완벽한 동기화를 목표로 하지 않은 이유
스크롤 동기화를 생각하면 먼저 떠오르는 방식은 단순해요. 한 탭에서 현재 스크롤 위치를 비율로 계산하고, 연결된 다른 탭도 각자의 문서 길이에 맞춰 같은 비율로 이동시키는 방식이에요.
하지만 제가 겪던 문제는 조금 달랐어요. 원문과 번역문은 같은 내용을 담고 있었지만, 페이지 구조가 완전히 같지는 않았어요. 한쪽에는 목차가 있고, 다른 쪽에는 없었어요. 번역 과정에서 문장 길이도 달라졌고요. 이런 차이가 누적되면 “같은 비율”이 “같은 문맥”을 의미하지 않게 돼요.
이 문제를 해결하는 방법은 크게 두 가지가 있다고 봤어요.
문서 구조를 분석해서 같은 위치를 찾기
제목, 문단, 이미지, 코드 블록 같은 요소를 비교해서 두 문서의 대응 지점을 찾는 방식이에요. 잘 동작하면 가장 편할 수 있어요. 하지만 웹페이지는 생각보다 제각각이에요. 예외도 많고, 오작동했을 때 사용자는 왜 다른 위치로 이동했는지 이해하기 어려워요.
사용자가 기준점을 맞추고 그 차이를 유지하기
사용자가 특정 키를 누른 동안에는 현재 탭만 움직이고, 키를 떼면 그 위치가 새 기준점으로 저장돼요. 이후에는 다시 모든 탭이 함께 스크롤돼요.
저는 두 번째 방식을 선택했어요. 자동화 수준을 낮추는 선택이기도 했어요. 확장 프로그램이 모든 페이지 구조를 이해해서 같은 문단을 찾아주지는 않아요. 대신 사용자가 한 번 기준점을 맞추면, 그 뒤로는 같은 위치를 반복해서 찾지 않아도 되게 도와줘요.
결국 이 기능의 목표는 모든 문서를 완벽하게 이해하는 것이 아니었어요. 사용자가 매번 처음부터 위치를 찾지 않아도 되도록, 한 번 맞춘 기준점을 유지해주는 것이었어요.
페이지 위에 얹히는 도구를 방해되지 않게 설계하기
스크롤 동기화가 시작되면 보여줘야 할 정보가 생각보다 많았어요.
- 어떤 탭이 연결되어 있는지
- 연결 상태가 정상인지
- 두 페이지의 위치 차이를 얼마나 맞춰두었는지
- URL 동기화가 켜져 있는지
하지만 이 정보를 항상 크게 보여주면 더 중요한 것을 잃게 돼요. 사용자는 문서를 읽으려고 확장 프로그램을 켠 것이지, 확장 프로그램 UI를 보려고 켠 것이 아니니까요. 그래서 동기화된 페이지 위에 작은 오버레이 도구를 올리고, 필요할 때만 열어 자세한 정보를 확인할 수 있게 만들었어요.
읽는 화면을 가리지 않게 하기


Why it works
동기화 상태와 연결된 탭 정보는 필요하지만, 문서를 읽는 동안 계속 보여야 하는 정보는 아니었어요. 항상 열린 패널로 두면 본문을 가리고, 사용자는 확장 프로그램 때문에 다시 읽기 흐름을 잃을 수 있어요.
Implementation considerations
기본 상태에서는 작은 버튼만 남기고, 사용자가 필요할 때 도구를 열 수 있게 했어요. control 키로 열고 닫을 수 있게 만든 것도 같은 이유였어요. 마우스를 찾아 움직이지 않아도, 읽던 자리에서 바로 상태를 확인하고 다시 닫을 수 있게 하고 싶었어요.
Business impact
오버레이의 기본값은 보여주기보다 방해하지 않기에 가까웠어요. 정보는 필요하지만, 사용자가 읽고 있는 본문보다 앞서면 안 된다고 봤기 때문이에요.
Actionable takeaway
보조 도구를 페이지 위에 올릴 때는 정보량보다 사용자가 하고 있던 일을 먼저 보세요. 기본 상태는 조용해야 하고, 필요한 순간에만 자세한 정보를 열 수 있어야 해요.
기대가 깨지는 순간을 방치하지 않기
Why it works
스크롤 동기화를 시작했다고 해서 사용자가 그 탭만 계속 보고 있는 것은 아니에요. 다른 탭에서 오래 작업하다 돌아왔는데 갑자기 동기화가 멈춰 있으면, 사용자는 연결 상태를 추론하기보다 제품이 불안정하다고 느낄 수 있어요.
Implementation considerations
content script는 동기화가 시작되면 30초마다 마지막 통신 시점을 확인해요. 60초 넘게 scroll:sync나 scroll:ping을 받지 못하면 연결이 오래됐다고 판단하고, 탭이 다시 보이는 상태가 되었을 때 ping으로 연결을 확인해요. 오래된 연결이면 background에 재연결을 요청하고, 최대 3번까지 500ms, 1000ms, 2000ms 간격으로 다시 시도해요. 그래도 실패하면 content script를 다시 주입해 복구를 시도해요.
Business impact
이 상태는 오버레이 버튼의 작은 색상 점으로 보여줬어요. 파란색은 연결과 URL Sync가 모두 살아 있다는 신호, 회색은 연결은 괜찮지만 URL Sync가 꺼져 있다는 신호, 주황색 깜박임은 연결 확인이나 재연결이 필요한 상태를 뜻해요. 색상은 기능 설명이 아니라 신뢰를 유지하기 위한 장치에 가까웠어요.
Actionable takeaway
사용자가 상태를 먼저 궁금해하지 않더라도, 문제가 생겼을 때 제품이 아무 말 없이 실패하면 신뢰가 깨져요. 자동 복구가 있는 기능일수록 복구 중인지, 대기 중인지, 실패했는지를 작게라도 보여줘야 해요.
UI 위치를 사용자가 직접 피하게 하기
Why it works
오버레이 UI는 페이지 위에 올라가기 때문에 어떤 위치에 둬도 누군가에게는 방해가 될 수 있어요. 문서마다 본문 폭도 다르고, 중요한 버튼이나 코드 블록이 있는 위치도 달라요.
Implementation considerations
하나의 고정 위치를 정하는 대신 오버레이 버튼을 사용자가 직접 옮길 수 있게 했어요. 이 위치는 페이지마다 다르게 둘 수 있어요.
Business impact
사용자는 지금 읽는 문서에서 방해되지 않는 위치로 버튼을 치워둘 수 있고, 확장 프로그램은 그 선택을 존중해요. 사용자가 UI를 감수하는 것이 아니라, UI가 사용자의 읽기 환경에 물러서는 구조예요.
Actionable takeaway
페이지 위에 떠 있는 도구는 기본 위치만 잘 고르는 것으로 충분하지 않아요. 콘텐츠 구조가 페이지마다 다르다면, 사용자가 직접 피할 수 있는 통제권을 제공하는 편이 더 안정적이에요.
자유롭게 옮기되, 정렬 부담은 줄이기

Why it works
위치를 자유롭게 바꿀 수 있게 하면 다른 문제가 생겨요. 사용자가 버튼을 아주 정확한 위치에 놓아야 한다면, 그 자체가 또 다른 조작 부담이 돼요.
Implementation considerations
드래그한 버튼은 페이지의 적절한 테두리에 스냅되도록 했어요. 사용자는 대충 원하는 방향으로 옮기면 되고, 제품이 마지막 정리를 해줘요.
Business impact
자유는 주되, 세밀한 정렬 부담은 줄였어요. 사용자는 위치를 직접 정했다는 통제감을 얻지만, 픽셀 단위로 맞춰야 하는 피로는 떠안지 않아도 돼요.
Actionable takeaway
사용자에게 자유도를 줄 때는 정밀 조작까지 함께 넘기지 않는 게 좋아요. 사용자는 방향만 결정하고, 제품이 정렬과 마무리를 맡으면 조작이 훨씬 가벼워져요.
자동화와 통제권을 같이 주기

Why it works
스크롤 위치 보정은 자동으로 모든 문서 구조를 분석해서 맞추는 방식보다, 사용자가 필요할 때 직접 기준점을 바꿀 수 있는 방식이 더 현실적이라고 봤어요. 문서마다 목차, 배너, 헤더, 번역 길이가 달라서 완벽한 자동 추론은 오작동 가능성이 컸기 때문이에요.
Implementation considerations
특정 키를 누른 동안에는 현재 탭만 움직이고, 키를 떼면 그 위치가 새 기준점으로 저장돼요. 이후에는 다시 모든 탭이 함께 스크롤돼요.
Business impact
사용자는 왜 이 위치로 갔는지 추측하는 대신, 직접 맞춘 기준을 바탕으로 동기화된다는 감각을 가질 수 있어요. 자동화가 사용자를 끌고 가는 것이 아니라, 사용자가 정한 기준을 자동화가 유지하는 구조예요.
Actionable takeaway
자동화가 불확실한 판단을 해야 한다면, 완전 자동보다 사용자가 기준점을 정하고 제품이 그 기준을 유지하는 방식이 더 나을 수 있어요. 예측 가능성이 곧 사용자의 통제감으로 이어져요.
브라우저 확장 프로그램에서 마주친 예상 밖의 스크롤 문제
스크롤 동기화는 처음에는 단순해 보였어요. 한 탭에서 스크롤 위치를 계산하고, 연결된 탭에 그 위치를 전달하면 된다고 생각했어요.
하지만 실제 웹페이지 위에서 돌려보자, “스크롤은 내가 원하는 좌표로 즉시 이동시킬 수 있다”는 전제가 흔들렸어요. 확장 프로그램이 스크롤을 제어한다고 해도, 그 스크롤은 결국 사용자가 보고 있는 페이지 안에서 실행돼요. 페이지의 CSS, 브라우저의 최적화, 탭의 생명주기 같은 요소가 모두 영향을 줄 수 있어요.
이 문제를 가장 분명하게 느낀 건 MDN의 한국어/영어 문서를 동기화할 때였어요. 다른 페이지에서는 자연스럽게 움직이는데, MDN에서는 유독 스크롤이 버벅이는 느낌이 있었어요. 메시지 전달은 정상으로 보였고, 스크롤 비율을 계산하는 로직에서도 바로 이상한 지점을 찾기 어려웠어요.
특정 페이지에서만 문제가 재현된다는 점을 단서로, 페이지 자체가 스크롤 동작에 개입하고 있을 가능성을 확인해봤어요. MDN 페이지의 스크롤이 부드럽게 이동하는 것을 보고 scroll-behavior 관련 CSS를 의심했어요. 실제로 두 페이지에서 의심되는 스크롤 CSS를 제거하자 문제가 사라졌고, 페이지에 적용된 scroll-behavior: smooth가 동기화 스크롤까지 애니메이션화하고 있다는 것을 파악할 수 있었어요.
해결 방향은 스크롤 메시지를 더 많이 보내는 것이 아니었어요. 수신 탭에서 확장 프로그램이 적용하는 스크롤만 페이지의 smooth scrolling과 분리해야 했어요.
그래서 프로그램이 스크롤을 적용하는 아주 짧은 순간에만 scroll root와 document.body의 scrollBehavior를 auto로 바꾸고, 스크롤을 적용한 뒤 바로 원래 inline style로 복원했어요. 페이지 전체의 스크롤 정책을 바꾸지 않고, 확장 프로그램이 만든 스크롤 동작에만 영향을 주도록 범위를 좁힌 거예요.
또 들어오는 모든 스크롤 위치를 순서대로 재생하지 않게 했어요. 빠르게 스크롤하는 동안에는 짧은 시간 안에 여러 위치가 들어오는데, 그걸 모두 적용하면 수신 탭은 이미 지나간 위치를 뒤늦게 따라가요. 그래서 requestAnimationFrame 단위로 최신 위치 하나만 적용하는 방식으로 바꿨어요.

이후 수신 탭은 오래된 위치를 천천히 따라가지 않고, 사용자가 움직이는 탭의 최신 위치를 바로 따라오게 됐어요. 겉으로 보면 작은 버그 수정처럼 보일 수 있지만, 실제로는 “동기화가 내 스크롤을 방해하지 않는다”는 감각을 되찾는 일이었어요.
이 문제를 겪으며 배운 점은 단순했어요. 브라우저 확장 프로그램은 내 코드만으로 완성되지 않아요. 사용자가 보고 있는 페이지와 함께 동작해요. 그래서 기능이 이상하게 느껴질 때는 메시지 흐름이나 상태 관리만 볼 게 아니라, 페이지가 가진 CSS와 브라우저 동작까지 함께 봐야 해요.
사용자 피드백이 기능의 방향을 바꾼 순간
처음에는 이 확장 프로그램을 제가 쓰기 위해 만들었어요. 그래서 기능을 추가할 때도 자연스럽게 제 사용 흐름을 기준으로 생각했어요.
하지만 사용자가 생기고, 직접 의견을 보내주기 시작하면서 기준이 조금씩 바뀌었어요. 그중 기억에 남는 피드백이 staging과 production 페이지를 비교하는 사용 사례였어요.
처음 이 확장 프로그램을 만들게 된 계기는 서로 다른 사이트에 올라온 원문과 번역문을 함께 읽는 일이었어요. 다만 URL 동기화가 자연스럽게 동작하는 경우는 조금 달라요. D2 번역문과 Chrome 개발자 블로그 원문처럼 path와 query 구조가 완전히 다른 페이지는 한쪽 이동을 다른 쪽에 그대로 적용하기 어렵고, 잘못하면 404로 이어질 수 있어요.
그래서 URL 동기화는 같은 사이트 안에서 언어만 다른 페이지를 함께 볼 때 더 적합했어요. 예를 들어 https://developer.chrome.com/blog/inside-browser-part4?hl=en과 https://developer.chrome.com/blog/inside-browser-part4?hl=ko를 나란히 보고 있다면, 한쪽에서 같은 시리즈의 다른 편으로 이동했을 때 다른 탭도 해당 언어를 유지한 채 같은 편으로 이동하길 기대할 수 있어요.
이때 중요한 것은 “변경한 탭의 이동을 다른 탭도 따라가되, 가능한 경우 각 탭의 언어 맥락은 유지한다”는 동작이었어요. 그래서 기존 URL 동기화는 변경된 탭의 웹사이트와 페이지 이동을 기준으로 다른 탭을 함께 이동시키고, target 탭의 locale 정보는 보존하는 방식으로 설계했어요.
그런데 staging과 production을 나란히 비교하는 사용자는 다른 동작을 기대했어요. 예를 들어 production 탭이 https://acme.com/en-US/home에서 https://acme.com/en-US/about-us로 이동했다고 해볼게요. 이때 staging 탭이 https://staging.acme.com/en-US/home을 보고 있었다면, 사용자가 기대한 결과는 https://acme.com/en-US/about-us로 따라가는 것이 아니었어요. staging 탭은 자기 웹사이트에 남아서 https://staging.acme.com/en-US/about-us로 이동해야 했어요.
처음 보면 새 동작이 더 좋아 보일 수도 있어요. 하지만 기존 동작을 그대로 바꾸면, 같은 사이트 안에서 언어만 다른 문서를 비교하던 사용자는 갑자기 기대와 다른 이동을 경험할 수 있어요. 기능 확장은 단순히 더 나은 동작을 덮어쓰는 일이 아니었어요. 이미 사용자가 기대하는 흐름을 깨뜨릴 가능성도 함께 봐야 했어요.
그래서 기존 동작은 Follow changed tab으로 유지하고, 새 요구사항은 Keep each tab's website라는 별도 모드로 추가했어요. 하나의 기능 안에 서로 다른 navigation policy가 생긴 셈이에요.
Follow changed tab: 변경한 탭의 이동을 기준으로 다른 탭이 따라가요.Keep each tab's website: 각 탭이 자기 웹사이트에 남은 채 path만 따라가요.

여기서 일부러 하지 않은 것도 있어요. staging인지 production인지 자동으로 추론하는 smart mode는 만들지 않았어요. 자동으로 잘 맞히면 편하겠지만, 틀렸을 때 사용자는 왜 탭이 이렇게 이동했는지 이해하기 어려워요. 웹사이트 구조도 팀마다 다르고, preview URL이나 locale 경로가 섞이면 예외가 금방 늘어나요.
대신 사용자가 직접 모드를 선택하게 했고, UI에는 기술 용어보다 행동 중심 문구를 사용했어요. “absolute URL”, “relative URL”, “origin” 같은 표현보다 “변경한 탭을 따라가기”, “각 탭의 웹사이트 유지하기”가 사용자가 결과를 예상하기 쉬운 문장이기 때문이에요.
또 선택한 모드를 적용할 수 없는 경우에도 조용히 다른 방식으로 fallback하지 않게 했어요. 사용자가 Keep each tab's website를 선택했는데 안전한 URL을 만들 수 없다면, 다른 모드처럼 이동시키는 대신 이동하지 않고 안내를 보여주는 편이 낫다고 봤어요. 사용자가 선택한 규칙과 실제 동작이 어긋나면 기능을 신뢰하기 어려워지기 때문이에요.
이 피드백은 제게 중요한 기준을 남겼어요. 기능을 확장할 때는 새 사용 사례만 볼 것이 아니라, 기존 사용자가 이미 믿고 있던 동작도 함께 지켜야 해요. 때로는 더 똑똑한 기본값보다, 사용자가 이해할 수 있는 명확한 선택지가 더 나은 제품 경험이 될 수 있어요.
이 글에서는 모드를 나눈 제품 판단까지만 다뤘어요. URL을 실제로 어떻게 계산하고, 왜 조용한 fallback을 막았는지는 별도 글에서 더 자세히 정리해보려고 해요.
개인 도구에서 작은 제품으로 바뀌며 달라진 기준
처음 이 확장 프로그램은 제 불편을 줄이기 위한 도구였어요. 여러 탭이나 문서를 나란히 보면서 비교할 때, 스크롤 위치를 맞추는 일이 번거로워서 만들었어요. 그래서 초기에는 제가 쓰는 환경에서 빠르게 켜지고, 스크롤이 자연스럽게 동기화되면 충분하다고 생각했어요.
그런데 Chrome Web Store에 공개한 뒤 생각이 달라졌어요. 따로 홍보하지 않았고 큰 기대도 하지 않았는데, 어느 순간 MAU가 약 3,500명까지 늘었어요. Chrome Web Store 추천 확장 프로그램 배지도 받았고요.
가장 크게 바뀐 건 제가 이 확장 프로그램의 첫 번째 사용자가 아니라, 운영자이자 제품 책임자가 됐다는 점이에요. 단순히 “내 환경에서 잘 된다”로 끝낼 수 없게 됐어요. 이제는 사용자가 이 확장 프로그램을 믿고 켜둘 수 있는지도 봐야 했어요.
처음에는 읽기 맥락을 잃지 않게 하는 문제였지만, 사용자가 생긴 뒤에는 그 맥락을 안정적으로 지켜주는 일이 제품의 약속이 됐어요.
사용자가 생기면 제품을 개선하는 방식도 달라져요. 많은 제품은 사용 로그와 이벤트 데이터를 바탕으로 빠르게 개선돼요. 어디에서 사용자가 막히는지, 어떤 기능을 자주 쓰는지, 어떤 흐름에서 이탈하는지 알 수 있다면 제품 판단이 훨씬 선명해져요. 저도 그런 방식이 제품을 발전시키는 데 얼마나 강력한지 알고 있어요.
다만 이 확장 프로그램에는 그 방식을 적용하고 싶지 않았어요. 스크롤 동기화 데이터를 수집하려고 하면 사용자가 보고 있는 URL, 탭 제목, 문서 제목, 로컬 파일 경로처럼 민감할 수 있는 정보와 가까워져요. 이 도구를 설치한 사용자는 빠르게 탭을 연결하고 읽던 흐름으로 돌아가고 싶어 해요. 그 앞에 동의 화면을 세우거나, 사용자가 읽는 문서의 맥락을 제품 개선 데이터로 다루는 방식은 이 도구의 역할과 맞지 않다고 봤어요.
기능을 실행하는 동안 브라우저 안에서 필요한 탭 정보는 다뤄요. 어떤 탭을 연결할지 판단하려면 현재 열린 탭의 정보를 확인해야 하니까요. 다만 그 정보를 제품 개선을 위한 행동 데이터로 저장하거나 외부로 보내지 않기로 했어요.
그래서 제품 개선을 위해 사용자의 탐색 정보나 행동 로그를 수집하지 않는 것을 기본 원칙으로 정했어요. 대신 Chrome Web Store 리뷰, GitHub issue, 사용자가 설명해준 재현 상황, 그리고 제가 직접 다양한 웹페이지와 로컬 파일 환경에서 테스트한 결과를 바탕으로 우선순위를 정했어요.
이 결정은 제품을 개선하는 속도를 늦출 수도 있어요. 어떤 버튼이 자주 눌리는지, 어떤 기능에서 사용자가 이탈하는지 자동으로 알 수 없으니까요. 하지만 이 확장 프로그램에서는 그 비용을 감수하는 편이 더 맞다고 봤어요. 사용자가 읽고 있는 문서의 맥락을 건드리지 않는 것이, 이 도구가 계속 켜져 있어도 되는 이유가 된다고 생각했어요.
개인정보 수집이 제품 개선에 도움이 되지 않는다는 뜻은 아니에요. 이 도구에서는 사용자가 읽는 문서의 맥락을 제품 개선 데이터로 다루지 않는 편이 더 중요하다고 판단했어요.
기능을 추가하는 기준도 달라졌어요. 예전에는 “내가 쓰기에 편한가”가 기준이었다면, 지금은 다음 질문을 함께 봐요.
- 사용자 신뢰를 해치지 않는가
- 데이터 수집 없이도 안정적으로 해결할 수 있는가
- 간단한 확장 프로그램이라는 정체성을 유지하는가
그래서 기능을 무작정 많이 넣는 것보다, 사용자가 안심하고 계속 켜둘 수 있는 경험을 더 중요하게 보게 됐어요. 처음 설치한 사용자가 화면을 열었을 때 너무 많은 설정과 옵션을 마주하면 어렵게 느낄 수 있어요. 이 확장 프로그램은 결국 스크롤 동기화를 빠르게 시작하기 위한 도구이기 때문에, UI도 가능한 단순하게 유지하려고 했어요.
업데이트를 관리하는 방식도 바뀌었어요. 예전에는 GitHub semantic release를 적용하고, 버전별 release note를 자동으로 만들고, 중요한 troubleshooting만 문서로 남기는 정도였어요. 코드 변경을 배포 가능한 단위로 정리하는 데에는 충분했지만, 제품 관점에서 “이 버전이 사용자 경험에 어떤 영향을 줬는지”를 보기에는 부족했어요.
그래서 이후에는 GitHub milestone을 더 적극적으로 사용하기 시작했어요. 각 버전에서 어떤 기능을 넣었고 어떤 버그를 고쳤는지 milestone으로 묶고, 버전별 설치 사용자, 삭제 사용자, 지속 시간 같은 Chrome Web Store 지표를 함께 보려고 했어요. 예를 들어 특정 버전에서 삭제한 사용자가 유난히 많다면, 그 버전에서 추가한 기능이나 수정한 버그가 오히려 문제를 만들었을 가능성을 의심할 수 있어요.
이런 관리 방식은 정교한 product analytics를 대체하지는 못해요. 하지만 “이번 버전에서 무엇을 바꿨고, 그 뒤에 사용자가 어떻게 반응했는지”를 최소한의 단위로 연결해보는 데에는 도움이 됐어요. 사용자 데이터를 직접 수집하지 않기로 한 만큼, 제가 볼 수 있는 신호를 더 조심스럽게 해석해야 했어요.
결국 가장 큰 변화는 사용자 수나 배지 자체보다, 제가 문제를 바라보는 방식이 바뀐 것이었어요. 처음에는 제 워크플로우를 자동화하는 개발자였고, 지금은 실제 사용자의 피드백을 받아 우선순위를 정하고, 제품의 안정성과 신뢰를 책임지는 개발자가 됐다고 느껴요.
마치며: 기능보다 약속을 지키는 일
처음에는 제 불편을 줄이기 위해 만든 작은 확장 프로그램이었어요. 원문과 번역문을 나란히 보며 읽을 때, 두 탭의 위치를 반복해서 맞추는 일이 번거로웠고, 그 흐름을 조금 덜 끊기게 만들고 싶었어요.
하지만 실제 사용자가 생기면서 이 기능을 바라보는 방식이 바뀌었어요. 사용자는 매번 기능이 어떻게 동작하는지 다시 검증하고 싶어 하지 않아요. 어제처럼 오늘도 자연스럽게 동작하길 기대해요.
그래서 기능을 추가하는 일보다 더 중요해진 것은 기존 기대를 지키는 일이었어요. 새 기능이 좋아 보여도, 이미 잘 쓰고 있던 사용자의 흐름을 깨뜨리면 개선이 아니라 regression이 될 수 있다고 봤어요.
또 배운 점은 사용자의 피드백을 기다리기만 해서는 부족하다는 것이었어요. 대부분의 사용자는 문제가 생겨도 GitHub issue를 만들거나 메일을 보내지 않아요. 불편하면 조용히 떠날 가능성이 더 커요. 그래서 테스트 케이스를 촘촘히 만들고, PR을 병합하기 전에 E2E 테스트를 통과하게 하고, 버전별 변경과 지표를 함께 보려 했어요. 사용자가 말하기 전에 문제를 발견할 수 있는 구조가 필요했기 때문이에요.
물론 모든 문제를 미리 막을 수는 없어요. 그래도 적어도 같은 문제가 반복되지 않게 만들 수는 있어요. 한 번 겪은 버그를 테스트와 문서로 남기고, 새 기능을 추가할 때 기존 사용 흐름을 다시 확인하는 것. 이 작은 절차들이 결국 제품의 신뢰를 지키는 방식이었어요.
제게 이 프로젝트는 작은 자동화 도구도 실제 사용자의 하루에 들어가면 운영해야 할 제품이 된다는 것을 배운 경험이었어요. 좋은 제품 개발은 그 약속을 깨지 않기 위해, 사용자가 말하기 전에 문제를 발견하고 신뢰를 유지하는 구조를 만드는 일에 가깝다고 생각해요.
이번 글에서는 스크롤 동기화 확장 프로그램이 어떻게 시작됐고, 개인 도구에서 작은 제품으로 바뀌며 어떤 기준이 생겼는지 넓게 정리했어요. 이후 글에서는 이번 글에서 짧게 다룬 내용을 하나씩 더 깊게 정리해보려고 해요.
- URL Sync 모드를 추가하며 기존 사용자의 기대를 지킨 과정
scroll-behavior: smooth때문에 생긴 스크롤 버그를 해결한 과정- 사용자 피드백을 제품 개선 우선순위로 연결한 과정