교육 제품: 2개월 차
한 줄 요약
강의 영상 녹화 워크플로를 개선했습니다
하이라이트
- 강의 영상을 더 쉽게 녹화할 수 있는 몇 가지 기법을 익혔습니다.
- Merchant of Record 서비스를 사용할 필요가 없다는 결론을 내렸습니다.
- htmx를 웹 애플리케이션을 만드는 기본 툴킷에 포함시켰습니다.
목표 달성도
매달 초에 이루고 싶은 목표를 정합니다. 이번 달에는 목표를 얼마나 달성했는지 돌아보겠습니다.
강의 중 4개 레슨의 배포 가능한 버전 녹화하기
- 결과: 한 개 레슨의 대부분을 녹화함
- 성적: D
영상 녹화에 얼마나 오래 걸리는지 잊고 있었습니다! 그리고 녹화 외에 해야 할 일이 얼마나 많은지도 과소평가했습니다. 대부분의 주에는 녹화할 시간 자체가 없었지만, 이제는 녹화 리듬을 되찾았습니다.
녹화 한 시간당 쓸 수 있는 분량은 5~10분 정도이고, 90~120분 정도 녹화하면 완전히 지쳐버립니다. 집중했던 한 주에는 45분짜리 레슨 하나를 완성하기도 했지만, 라이브 강의를 마쳤으니 이제 속도를 더 낼 수 있을 것 같습니다.
새 버전 강의 판매 시작하기
- 결과: 일부만 완성된 강의를 판매하는 대신 대기 명단을 운영함
- 성적: 해당 없음
새 강의 판매를 미루기로 했습니다. 처음에는 작업하는 동안 기존의 2020년 녹화본을 제공하면서, 새 버전을 얼리 액세스 할인 상품으로 판매할 생각이었습니다. 하지만 그걸 깔끔하게 패키징하기가 어렵다는 걸 깨달았습니다.
아내의 출산 예정일 전에 강의를 다 끝내지 못할 수도 있고, 아이가 태어나면 몇 달간 육아휴직을 쓸 계획입니다. 휴직 중에 수강생들에게 나머지 강의를 빚진 기분이 들고 싶지 않아, 사전 판매 대신 대기 명단을 운영하기로 했습니다.
영상 녹화 개선
강의 영상을 녹화할 때마다 녹화를 방해하거나 아예 시작조차 못 하게 하는 요소가 정말 많습니다. 더 빨리 녹화를 시작하고 방해에 덜 취약해지도록, 녹화에 따르는 마찰을 줄이는 데 집중했습니다.
책상용 삼각대 마련하기
녹화할 때는 모니터 위에 올려두는 Razer Kiyo 웹캠을 사용합니다. 문제는 카메라를 적절한 높이에 맞추려면 일일이 손으로 조정해야 한다는 점이었습니다.

녹화에 사용하는 Razer Kiyo 웹캠입니다.
평소에는 데스크톱 모니터의 중앙이 눈높이에 오도록 맞춰둡니다. 그런데 모니터가 눈높이에 있고 웹캠이 그 위에 있으면 카메라가 위에서 저를 내려다보는 각도가 됩니다.
괜찮은 카메라 앵글을 얻으려고 모니터 암과 책상 높이를 조정해 카메라가 눈높이에 오도록 하곤 했습니다. 하지만 모니터를 옮기는 과정 자체가 녹화의 마찰을 크게 늘렸고, 다음 녹화 때 정확히 같은 카메라 위치를 재현할 수도 없었습니다.
이 카메라 문제는 작고 책상 위에 올려둘 수 있는 삼각대인 SmallRig VT-20으로 해결했습니다.

녹화를 시작할 때마다 책상이나 모니터 위치를 다시 조정하지 않아도 되도록 작은 삼각대를 샀습니다.
녹화할 때는 삼각대를 저와 모니터 사이에 둡니다. 다리 위치를 페인터 테이프로 책상에 표시해 두어 매번 같은 위치에 놓을 수 있습니다.
삼각대가 있으니 녹화하고 싶을 때 삼각대와 카메라를 책상 위에 올려놓기만 하면 위치가 바로 맞습니다. 마찰을 줄이는 데 큰 도움이 되었고, 매번 이것저것 높이를 조절하지 않고 평소 책상 세팅 그대로 녹화할 수 있어 좋습니다.
유일한 단점은 삼각대가 슬라이드를 살짝 가린다는 점인데, 녹화할 즈음이면 슬라이드를 거의 외운 상태라 큰 문제는 아닙니다.


웹캠을 모니터 위에 장착했을 때(왼쪽)와 모니터 앞 책상 삼각대에 장착했을 때(오른쪽). 삼각대를 쓰면 카메라를 정면으로 바라보기 더 쉽습니다.
녹화 장비를 항상 연결해 두기
예전에는 녹화가 끝나면 웹캠을 서랍에 넣어두고, 마이크와 마이크 암은 다른 방에 보관했습니다. 그러다 보니 매 녹화 세션의 첫 5분은 장비를 모으고 책상에 세팅하는 데 쓰였습니다.
이제ObjJj는 가능한 한 많은 장비를 연결된 채로 바로 녹화할 수 있는 상태로 둡니다. 마이크는 책상 위에 그대로 연결해 두고, 웹캠도 삼각대에 장착한 채 책상 위에 바로 올려놓을 수 있게 준비해 둡니다.
챕터 단위로 녹화하기
강의를 준비하면서 리서치 차원에서 Aaron Francis의 스크린캐스팅 강의를 봤습니다. 그의 강의는 라이브 코딩에 초점이 맞춰져 있지만, 제 강의에도 충분히 적용할 수 있는 내용이 많아 구매할 가치가 있었습니다.
Aaron의 강의 자체도 도움이 됐지만, 가장 큰 배움 중 일부는 그가 직접 말한 내용이 아니라 강의를 어떻게 패키징했는지를 보면서 얻었습니다.
제 강의의 첫 번째 버전은 30~60분짜리 레슨 7개로 구성되어 있었습니다. Aaron의 레슨도 길이는 비슷하지만, 각 레슨을 몇 분짜리 짧은 챕터들로 세분화해 둡니다.

Aaron Francis는 자신의 레슨을 몇 분 길이의 짧은 챕터들로 나눕니다.
수강생 입장에서도 짧은 챕터 방식이 좋습니다. 가장 관심 있는 챕터로 바로 건너뛸 수 있으니까요.
제작자 입장에서도 짧은 챕터가 좋습니다. 40분짜리 크고 다루기 힘든 영상 하나를 만드는 것보다 4분짜리 영상 10개를 녹화하고 편집하는 게 훨씬 쉽습니다. 또 작업할 때 40분짜리 영상을 절반쯤 편집한 상태로 두는 것보다, 100% 완성된 챕터 5개를 가지고 있는 편이 심리적으로도 훨씬 낫습니다.
순서에 의존하지 않도록 레슨 만들기
2020년에 강의를 녹화할 때는 모든 영상을 “파트 2: 해커 뉴스 이해하기에 오신 것을 환영합니다” 같은 식으로 시작했고, “다음 영상에서는 글감 고르는 법에 대해 이야기하겠습니다”라고 마무리했습니다.
새 녹화에서는 레슨을 챕터로 나누다 보니 영상의 개수가 자연스럽게 많아졌습니다. 편집 과정에서 순서를 바꾸거나 영상을 삭제하면 번호와 연결을 맞추기 위해 인트로와 아웃트로를 대거 다시 녹화해야 합니다.
Aaron Francis의 강의를 보고 나서야 그는 영상에서 순서를 암시하지 않는다는 걸 깨달았습니다. 저도 영상에서 레슨 번호를 없애고 순서를 언급하지 않기로 했습니다. 다른 레슨의 내용을 언급할 때는 “나중 영상에서”라고 하지 않고 “그 부분은 Foo 영상에서 더 자세히 다룹니다”라고 말합니다.
같은 티셔츠 세 장 사기
Aaron Francis 강의의 팁 중 하나는 모든 레슨에서 일관된 모습을 유지하라는 것이었습니다. 그는 모든 영상에서 무지 검은 티셔츠를 입고 나오는데, 좋은 아이디어라고 생각했습니다.
처음에는 녹화할 때마다 갈아입었다가 끝나면 벗어두는 전용 셔츠 한 장을 마련했습니다. 평소에 셔츠를 입으면 자기 전에 세탁 바구니에 넣기 전까지 약 14시간 정도 입으니, 같은 셔츠로 1시간짜리 녹화를 14번 정도는 하고 세탁하면 되겠다고 생각했습니다.
셔츠 냄새에 대한 제 생각이 틀렸다는 걸 금방 알게 됐습니다. 녹화를 세 번 하고 나니 셔츠가 이미 찝찝하게 느껴졌습니다.
그래서 그냥 같은 티셔츠를 세 장 샀습니다.

영상의 연속성을 위해 같은 티셔츠를 세 장 주문했습니다.
평소에도 입는 심플한 네이비 색상의 Bonobos 티셔츠입니다. 세 장이 있으면 두 장이 세탁 중이어도 한 장은 항상 입을 수 있을 것 같습니다.
라이브 강의의 마법 같은 순간들
블로깅 라이브 강의 여섯 세션을 모두 마쳤습니다. 강의 자체도 즐거웠지만, 특히 기억에 남는 두 순간이 있었습니다.
게스트 연사 초청하기
첫 강의를 준비하던 중 Adam Gordon Bell이 트위터에서 회사로부터 해고됐다고 알리는 글을 봤습니다. Adam은 제가 가장 좋아하는 소프트웨어 팟캐스트 중 하나인 CoRecursive의 호스트이자 블로거로, 전 직장에서 가장 인기 있었던 블로그 글들을 쓴 장본인이기도 합니다.
Adam은 2020년에 같은 강의를 했을 때 파일럿 수강생 그룹에 참여했던 인연이 있습니다. 갑자기 일정이 비게 된 걸 보고 Q&A 게스트로 다시 참여해 줄 수 있는지 물었고, 그는 흔쾌히 다음 주에 합류해 주었습니다.
타이밍이 정말 놀라웠습니다. Adam이 제 강의 “Hit the Front Page of Hacker News” 화상 통화에 참여한 바로 그 순간, 그가 진행한 PowerShell 수석 아키텍트 Jeffrey Snover와의 인터뷰가 해커 뉴스 1위에 올라 있었기 때문입니다.
저는 Adam의 글을 많이 읽어왔기 때문에 인터뷰가 특히 재미있었습니다. 그의 작업 방식을 속속들이 들여다보고, 세월에 따라 어떻게 변화시켜 왔는지 듣는 과정이 무척 흥미로웠습니다.
수강생들에게서도 전율이 느껴졌습니다. 열기의 정도가 생각보다 훨씬 뜨거워서 놀랐는데, 두 번째 수업이었으니 “드디어 분위기 전환이네” 같은 상황도 아니었기 때문입니다. 제 생각에 수강생들은 모두 저를 알고 있었지만 Adam을 아는 사람은 일부뿐이었기 때문에, 검증된 이력을 가진 다른 사람이 같은 개념을 자신만의 관점으로 이야기하는 모습이 흥미로웠던 것 같습니다.
실제 글 라이브 teardown
강의 중에 수강생 몇 명이 각자 쓴 글을 공유하고 피드백을 주는 연습을 해보자고 제안했습니다. 세 명이 글이나 초안을 공유했고 저희가 비평했는데, 더 자주 해보자는 요청이 이어졌습니다.
다음 수업에서는 자원자를 받았지만 공유할 글을 준비해 온 사람이 없었습니다. 그래서 제가 직접 해커 뉴스에 들어가 글을 하나 골라 장단점을 분석해 보자고 제안했습니다. 재미는 있었지만 첫 페이지에 있는 글을 고르다 보니 이미 잘 된 글들이었습니다. 수강생들은 운명이 아직 정해지지 않은, 막 올라온 새 글을 골라보자고 제안했습니다.
막 올라온 글을 비평하기 시작하면서 수업이 정말 살아났습니다. 이미 성공한 글을 보고 왜 성공했는지 설명하는 건 쉽지만, 결과가 아직 모르는 글을 보고 성과를 예측하는 건 훨씬 더 어렵기 때문입니다. 게다가 모르는 사람의 글이고 비공개 그룹에서 발표하는 자리라, 저자를 불쾌하게 하거나 지나치게 까다롭게 보일까 걱정하지 않고 떠오르는 생각을 그대로 말할 수 있었습니다.
제가 막 올라온 글을 읽을 때 수강생들의 반응도 더 많았고 몰입도도 높아 보였습니다. 강의 중에 부정적인 예시를 몇 가지 보여주긴 했지만, 지루한 도입부나 엉성한 구조 때문에 독자가 실시간으로 흥미를 잃어가는 모습을 지켜보는 게 흥미로웠던 것 같습니다.
이 마법 같은 순간들을 어떻게 활용할까?
수업에서 마법 같은 순간이 있을 때마다, 이를 더 적극적으로 활용할 방법을 고민했습니다. 어떻게 하면 이런 순간을 더 많이 만들고 최종 강의에 녹여낼 수 있을까 하고요.
라이브 그룹 Q&A는 여덟 명의 로테이션 멤버와 함께하는 유튜브 채널을 만들지 않는 한 반복하기 어렵습니다. 하지만 그나마 비슷한 대안은 제가 좋아하는 작가들과 1:1 화상 인터뷰를 하는 것이고, 이건 훨씬 실현 가능해 보입니다.
우연이 아니게도, 전문가 인터뷰는 Aaron Francis가 자신의 강의를 홍보할 때 활용하는 방식이기도 합니다.

Aaron Francis는 다가오는 SQLite 강의에 대한 관심을 모으기 위해 SQLite 분야 전문가들과의 장편 인터뷰를 공개합니다.
반면 라이브 teardown은 쉽게 재현할 수 있습니다. 지금 당장 카메라를 켜고 해커 뉴스 글을 읽으며 떠오르는 생각을 그대로 녹화하면 되니까요. 재미있을지는 모르겠지만, 빠르게 실험해 보기에는 좋은 아이디어입니다.
Merchant of Record는 사기일까?
지난주 Merchant of Record를 지원하던 몇 안 되는 결제 대행사 중 하나인 LemonSqueezy가 Stripe에 인수되었다고 발표했습니다. 해커 뉴스 스레드에서 한 댓글이 눈에 들어왔습니다.
MoR 자체가 추가 거래 수수료를 더 뜯어내려는 공포 조장 수법이라고 봅니다
99%의 SaaS는 MoR를 정당화할 만큼의 MRR에 도달하지 못합니다
그 1% 중 7자리 수 MRR을 돌파한 곳은 세금 납부를 관리할 인력을 직접 고용하면 되고, MoR 브랜드가 찍힌 청구서로 고객을 혼란스럽게 할 필요도 없습니다
회계사에게 물어보니, 대부분의 미국 주에서 디지털 제품에 대해 판매세를 내야 하는 최소 기준은 주에 따라 연간 약 10만 달러 또는 100건 거래 정도라고 합니다.
캘리포니아나 뉴욕 같은 인구가 많은 주에서는 기준이 50만 달러입니다. 판매세를 일일이 내는 게 번거로울 정도로 여러 주의 기준을 넘길 정도라면, 그때는 수십만 달러의 매출로 회계사를 고용하면 됩니다.
원래는 Merchant of Record 기능 때문에 Gumroad에서 판매할 계획이었습니다. 하지만 Gumroad는 10%의 수수료를 받고, 거기에 결제 처리 수수료는 별도입니다.
제 거주지인 매사추세츠 외의 주에서 판매세 기준을 넘을 가능성이 희박하다는 점을 감안하면, Gumroad 밖에서 강의를 판매해 10%를 아낄 수 있습니다.
사이드 프로젝트
htmx 폼에서 선호하는 패턴 찾기
지난 회고에서 이야기했듯이 htmx를 쓰기 시작했고 마음에 들지만 오류 처리가 어색하다고 느꼈습니다.
예시로 제가 만든 영화 리뷰 웹 앱인 ScreenJournal의 폼을 보여드리겠습니다.

ScreenJournal 웹 앱의 간단한 HTML 폼입니다.
사용자가 폼을 제출하면 결과는 두 가지뿐입니다.
- 설정이 성공적으로 저장되었습니다.
- 요청을 처리하는 중에 오류가 발생했습니다.
htmx의 관용적인 처리 방식은 사용자가 폼을 제출하면 서버가 사용자가 제출한 값과 성공 또는 오류 메시지를 포함해 폼 전체의 HTML을 다시 응답으로 보내는 것입니다.
저는 htmx가 권장하는 폼 패턴이 어색하고 버그가 생기기 쉽다고 생각합니다. 브라우저에는 이미 폼이 올바르게 채워져 있는데, 왜 서버가 폼 전체를 다시 보내 브라우저가 다시 렌더링하게 해야 할까요? 새로 필요한 정보는 성공 메시지나 오류 메시지뿐인데, 왜 그것만 보내지 않는 걸까요?
htmx 기능을 더 가볍게 만들기 위해 위 ScreenJournal 폼에 적용한, 제가 살짝 변형한 htmx 패턴은 다음과 같습니다.
<form
hx-put="/account/notifications"
hx-clear="#result-success, #result-error"
hx-disabled-elt="input, .btn"
hx-target="#result-success"
hx-target-error="#result-error"
hx-swap="textContent"
>
<label for="new-reviews-checkbox">
Email me when users post reviews
</label>
<input
type="checkbox"
id="new-reviews-checkbox"
name="new-reviews"
/>
<label for="all-comments-checkbox">
Email me when users add comments
</label>
<input
type="checkbox"
id="all-comments-checkbox"
name="all-comments"
/>
<button value="Save">Save</button>
<div role="status">
<span>Loading...</span>
</div>
</form>
<div id="result-success" role="alert"></div>
<div id="result-error" role="alert"></div>핵심은 <form> 태그에 있으니 하나씩 살펴보겠습니다.
hx-put="/account/notifications"사용자가 폼을 제출하면 서버의 /account/notifications 경로로 HTTP PUT 요청을 보냅니다.
hx-clear="#result-success, #result-error"사용자가 폼을 제출하면 ID가 result-success 또는 result-error인 요소의 내용을 비웁니다.
이 속성은 htmx에 포함된 것이 아니라 제가 만든 clear-before-send라는 커스텀 확장 기능입니다. CSS 규칙인 .alert:empty { display: none }과 함께 사용해, 텍스트가 없을 때는 alert 클래스를 가진 <div> 태그를 숨깁니다.
hx-disabled-elt="input, .btn"HTTP 요청이 진행되는 동안 모든 <input> 태그와 .btn CSS 클래스를 가진 요소를 비활성화해 사용자가 같은 요청을 중복 제출하지 못하게 합니다.
참고로 htmx 문서를 보면 hx-disabled-elt="this"가 폼 전체를 비활성화해야 할 것처럼 나와 있지만 실제로는 동작하지 않습니다. 그래서 폼 안의 모든 입력 요소를 선택하는 선택자를 직접 쓰는 방식으로 우회했습니다.
hx-target="#result-success"서버가 200번대 상태 코드로 응답하면, 응답 본문을 ID가 result-success인 요소 안에 넣습니다.
hx-target-error="#result-error"서버가 200번대가 아닌 상태 코드로 응답하면, 응답 본문을 ID가 result-error인 요소 안에 넣습니다.
이 속성은 htmx 코어 라이브러리에 포함된 것이 아니라 response-targets라는 htmx 확장 기능에서 제공하는 것입니다.
hx-swap="textContent"hx-target이나 hx-target-error의 대상을 채울 때, htmx가 대상 요소의 내부 또는 외부 HTML을 교체하는 대신 textContent를 교체하도록 합니다.
결과는 다음과 같습니다. 개발 서버를 2초 지연 후 응답하도록 설정했고, 두 번에 한 번은 오류 메시지와 함께 실패하도록 했습니다.
ScreenJournal의 알림 관리 화면
注目할 점은 다음과 같습니다.
- 요청이 진행되는 동안 htmx가 폼을 비활성화합니다.
- 사용자가 “Save”를 다시 클릭하는 즉시 이전 성공/오류 메시지가 사라집니다.
- 성공 메시지와 오류 메시지의 스타일이 다릅니다.
각 폼마다 커스텀 자바스크립트를 작성하지 않고도 꽤 많은 기능을 구현할 수 있습니다. 이런 흔한 기능 때문에 서드파티 라이브러리가 필요하지 않도록 HTML 자체가 진화했으면 좋았겠지만, htmx가 그 간극을 메워주고 있어 만족스럽습니다.
마무리
무엇을 해냈나?
- 이 블로그를 통해 Is It Keto를 2,000달러에 판매했습니다.
- 블로깅 라이브 강의를 모두 마쳤습니다.
- 첫 번째 htmx 확장 기능을 만들었습니다.
- 강의 홍보용 마케팅 도구인 Personal Best를 만들었습니다.
배운 점
- 영상 녹화 과정에서 마찰을 줄일 방법을 찾아야 합니다.
- 재능 있는 블로거와의 인터뷰와 블로그 글 teardown을 공개해 강의를 홍보할 수 있습니다.
- 미국의 대부분의 인디 창업자에게는 Merchant of Record가 필요하지 않을 가능성이 높습니다.
다음 달 목표
- 강의 녹화를 마칩니다.
- 강의 판매를 시작합니다.
글을 무작위로 읽기