2026년 8월 27일 • ☕️ 5 min read
웹페이지를 Figma로 옮기는 도구를 직접 만들어 스토어에 올렸다. 코드를 쓴 시간보다 심사를 통과하는 데 쓴 시간이 길었다.
세 번 반려당했다. 한 번은 이름 때문이고, 두 번은 권한 때문이다. 셋 다 내가 틀렸고 심사가 맞았는데, 그래서 오히려 기록해 둘 가치가 있다고 생각했다. 남의 반려 사유는 읽을 일이 잘 없으니까.
처음 이름은 html2figma였다. Figma Community에 플러그인을 올렸고 이런 답이 왔다.
Website url does not align with our branding guidelines브랜드 가이드라인을 읽어보니 제품명·도메인·핸들에 “Figma”나 “Fig”를 넣지 말라고 명시돼 있었다. 허용되는 건 “X for Figma” 형태뿐이다. 반박할 게 없었다.
문제는 이름만 바꾸면 끝이 아니라는 데 있었다. 도메인을 옮기고, 구 도메인은 301로 넘기고, 패키지명과 문서와 스토어 자산에 박힌 옛 이름을 전부 찾아 지워야 했다. 놓친 곳도 있었다. Google OAuth 동의 화면의 앱 이름이 한참 뒤까지 html2figma로 남아 있어서, 로그인하는 사람은 설치한 것과 다른 이름을 보고 있었다.
지금 이름은 html2design이다.
크롬 웹스토어에서 자동 심사에 두 번 걸렸다. 두 번 다 같은 위반 항목이었다.
첫 번째는 scripting이었다.
다음 권한을 요청하지만 사용하지는 않습니다(scripting)코드를 뒤져보니 chrome.scripting 호출이 정말 0건이었다. 페이지에서 스크립트를 실행하는 일은 하고 있었지만, 그건 chrome.debugger로 CDP의 Runtime.evaluate를 부르는 방식이었다. 처음부터 필요 없던 권한을 매니페스트에 적어둔 것이다. 지우고 재제출했다.
두 번째는 tabs였다. 이건 좀 더 배울 게 있었다.
항목에 의해 구현된 메서드/속성에 관해 다음 권한을 요청할 필요가 없습니다(tabs)chrome.tabs.query를 실제로 호출하고 있는데도 권한이 불필요하다는 것이다. 확인해 보니 맞았다. chrome.tabs.query는 권한 없이도 동작하고, 권한이 없을 때 가려지는 건 url·title·favIconUrl·pendingUrl 네 필드뿐이다. 내 코드는 tab.id만 쓰고 있었다. 페이지의 URL과 제목은 탭 객체가 아니라 DOMSnapshot 파싱 결과에서 가져오고 있었으니 처음부터 상관이 없었다.
chrome.tabs.create도 권한을 요구하지 않는다. 탭을 새로 여는 일이라 당연히 필요할 거라고 생각했는데 아니었다.
호출이 있어도 권한이 불필요할 수 있다. 이게 두 번의 반려로 배운 것이다. 권한을 적기 전에 그 API가 정말 그 권한을 요구하는지 문서에서 확인해야 한다.
같은 논리를 적용해서 activeTab도 같이 지웠다. executeScript나 insertCSS를 쓰지 않고, 호스트 권한이 이미 있어서 접근 범위를 넓히지도 좁히지도 않았다. 세 번째 반려를 기다릴 이유가 없었다.
결과적으로 권한이 다섯 개에서 세 개로 줄었다. debugger, storage, identity. 설치할 때 뜨는 경고 문구도 짧아졌으니 결국 이득이었다.
남긴 건 <all_urls> 호스트 권한 하나다. 캡처한 페이지의 이미지를 임의 출처에서 가져와야 해서 실제로 필요하다. 대신 이 권한이 있으면 “자세한 검토” 대상이 되어 심사가 길어진다. 그건 감수하는 수밖에 없었다.
반려 세 번보다 오래 잡고 있었던 건 로그인이었다.
Google 로그인이 아예 되지 않았다. 동의 화면까지는 정상이고 “계속”을 누르면 창이 닫히는데, 그 뒤에 아무 일도 일어나지 않는다. 에러도 안 떴다. 로그가 없으니 어디서 끊기는지 알 수 없었다.
원인은 이랬다. chrome.identity.launchWebAuthFlow가 인증 창을 띄우면 포커스가 옮겨가고, 그 순간 크롬이 익스텐션 팝업을 닫는다. 팝업이 닫히면 그 JS 컨텍스트가 파괴되므로 await 뒤에 있는 코드가 실행되지 않는다. 코드 교환도, 세션 저장도, 에러 표시도 전부 그 뒤에 있었다. 에러가 안 뜬 이유도 같다. 에러를 그릴 주체가 이미 사라진 것이다.
제일 고약했던 건 개발자 도구를 붙여놓으면 잘 됐다는 점이다. devtools가 붙어 있으면 팝업이 닫히지 않으니 흐름이 끝까지 간다. 그래서 한참 동안 “내 환경에서는 되는데” 상태였다.
해결은 인증 흐름 전체를 서비스 워커로 옮기는 것이었다. 워커는 팝업이 닫혀도 살아 있고, 결과를 storage에 저장하므로 팝업을 다시 열면 반영돼 있다. 결제 페이지를 여는 것도 워커가 하게 했다. 팝업이 중간에 죽어도 탭은 열린다.
교훈을 하나로 줄이면, 익스텐션 팝업에서 새 창을 띄우는 비동기 흐름을 시작하면 안 된다. 팝업은 포커스를 잃는 순간 사라지는 컨텍스트다.
만들어놓고 지운 기능도 있다.
Figma 쪽에서 색과 텍스트를 Local Styles로 자동 생성해 주는 기능이 있었는데 빼버렸다. 전달 방식도 정리했다. 처음에는 .h2f 파일로 저장해서 플러그인에 드롭하거나, 클립보드로 복사해 붙여넣거나, 6자리 코드로 바로 보내는 세 가지가 있었는데 지금은 마지막 하나만 남았다.
문제는 지운 기능이 여러 곳에 광고로 남아 있었다는 것이다. 랜딩 페이지, Figma Community 설명, 스토어 스크린샷, 블로그 글까지. 없는 기능을 홍보하고 있었다. 기능을 지울 때 코드만 지우면 끝이 아니라는 걸 이때 알았다. 특히 스토어 스크린샷처럼 이미지에 박힌 문구는 찾기도 어렵다.
가격도 내렸다. 월 $9에서 $5로. 이건 반려와 무관하게 그냥 판단이었다.
무료는 월 5회, 가입도 로그인도 필요 없다. 무제한은 월 $5다.
크롬 확장과 Figma 플러그인을 둘 다 설치해야 동작한다. 확장이 캡처하고 플러그인이 그리는 구조라 한쪽만으로는 아무것도 안 된다. 이게 가장 큰 이탈 지점인 걸 알지만 아직 답을 못 찾았다.
못 하는 것도 그대로 적어둔다. <canvas>로 그린 픽셀은 못 가져온다. 요소 박스만 온다. 그라디언트 각도는 근사값이고, 설치되지 않은 폰트는 Inter로 대체된다. 캡처 대상이 자체 폰트를 쓰는 사이트면 이게 눈에 띈다.
홍보를 아직 시작하지 않았는데 스무 명쯤이 쓰고 있다. 깨지는 페이지 URL을 보내주는 게 지금 가장 도움이 된다.