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

녹화에 사용하는 Razer Kiyo 웹캠.
평소에는 모니터 중앙이 거의 눈높이에 오도록 배치해 둔다. 그런데 모니터가 눈높이에 있으면 그 위에 있는 웹캠은 나를 위에서 내려다보는 각도가 된다.
좋은 카메라 앵글을 얻으려고 모니터 암과 책상 높이를 조절해 카메라가 눈높이에 오도록 맞췄다. 하지만 모니터를 옮기는 과정이 녹화의 마찰을 크게 높였고, 녹화할 때마다 정확히 같은 카메라 위치를 재현할 수도 없었다.
작은 데스크용 삼각대인 SmallRig VT-20으로 카메라 문제를 해결했다.

녹화를 시작할 때마다 책상이나 모니터 위치를 다시 조정하지 않아도 되도록 데스크 삼각대를 샀다.
녹화할 때는 삼각대를 나와 모니터 사이에 둔다. 매번 같은 위치에 놓을 수 있도록 페인터 테이프로 다리 위치를 책상에 표시해 두었다.
삼각대가 있으니 녹화하고 싶을 때 삼각대와 카메라를 책상에 올려놓기만 하면 위치가 바로 맞는다. 마찰을 줄이는 데 큰 도움이 되고, 매번 이것저것 높이를 조절하느라 애쓸 필요 없이 평소 책상 세팅 그대로 녹화할 수 있어 좋다.
삼각대의 유일한 단점은 슬라이드가 일부 가려진다는 점이지만, 녹화 단계에 이르면 슬라이드는 이미 다 외우고 있어서 큰 문제는 아니다.


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

Aaron Francis는 그의 레슨을 각각 몇 분 길이의 짧은 챕터들로 나눈다.
수강생 입장에서도 짧은 챕터 방식이 마음에 든다. 가장 관심 있는 챕터만 골라 볼 수 있기 때문이다.
강의 제작자 입장에서도 짧은 챕터가 좋다. 40분짜리 영상 하나를 만들고 편집하는 것보다 4분짜리 영상 10개를 만드는 게 더 쉽다. 또 작업할 때도 40분짜리 영상 편집을 절반쯤 한 상태보다, 100% 완성된 챕터 5개를 가지고 있는 편이 심리적으로 훨씬 낫다.
레슨을 순서에 의존하지 않게 만들기
2020년에 강의를 녹화할 때는 모든 영상을 “파트 2: Hacker News 이해하기에 오신 것을 환영합니다” 같은 식으로 시작했고, “다음 영상에서는 어떤 주제를 쓸지 고르는 방법을 이야기하겠습니다” 라며 끝냈다.
새 녹화에서는 레슨을 챕터로 나누다 보니 자연히 영상의 개수가 많아졌다. 편집 과정에서 영상의 순서를 바꾸거나 삭제하면 번호와 연결을 맞추기 위해 인트로와 아웃트로를 다시 찍어야 한다.
Aaron Francis의 강의를 보고 나서야 그는 영상에서 순서를 암시하지 않는다는 걸 깨달았다. 나도 영상에서 레슨 번호를 빼고 순서를 언급하지 않기로 했다. 다른 레슨에 있는 내용을 언급할 때는 “뒤에 나오는 영상에서”라고 하는 대신 “그 부분은 Foo 영상에서 더 자세히 다룹니다”라고 말하는 식이다.
같은 셔츠 세 장 사기
Aaron Francis 강의의 팁 중 하나는 모든 레슨에서 일관된 모습을 유지하라는 것이었다. 그는 모든 영상에서 검은색 무지 티셔츠를 입고 나오는데, 좋은 아이디어라고 생각했다.
처음에는 녹화할 때마다 갈아입었다가 끝나면 벗어둘 셔츠 한 장을 따로 마련해 두었다. 평소에 셔츠를 입으면 세탁 전에 14시간 정도 입는다고 생각하니, 같은 셔츠로 1시간짜리 녹화를 14번 정도는 할 수 있을 거라 계산했다.
셔츠 냄새에 대한 내 머릿속 모델이 잘못된 것이었다. 세 번 녹화했을 뿐인데 셔츠에서 불쾌한 느낌이 들기 시작했다.
그래서 그냥 같은 셔츠를 세 장 샀다:

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

Aaron Francis는 SQLite에 관한 향후 강좌에 대한 관심을 모으기 위해 SQLite 분야 전문가들과의 롱폼 인터뷰를 공개한다.
반면 라이브 뜯어보기는 쉽게 재현할 수 있다. 지금 당장 카메라를 켜고 Hacker News 글을 읽으며 떠오르는 생각을 그대로 녹화하면 된다. 그게 재미있을지는 모르겠지만, 빠르게 실험해 보기에는 좋은 아이디어다.
Merchant of Record는 사기인가?
지난주, Merchant of Record를 지원하던 몇 안 되는 결제 프로세서 중 하나인 LemonSqueezy가 Stripe에 인수되었다고 발표했다. Hacker News 스레드에서 한 댓글이 눈에 띄었다:
MoR 자체가 추가 거래 수수료를 뜯어내기 위한 공포 마케팅이라고 생각한다
99%의 SaaS는 MoR을 정당화할 만큼의 MRR에 도달하지 못한다
7자리 MRR을 돌파한 1%에 속한다면 세금 납부를 관리할 인력을 내부에서 고용하면 되고, 고객에게 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는 대상 요소의 innerHTML이나 outerHTML을 교체하는 대신 textContent를 교체해야 한다.
결과는 다음과 같다. 개발 서버를 2초 대기 후 응답하도록 조정했고, 두 번에 한 번꼴로 오류 메시지와 함께 실패하도록 만들었다:
ScreenJournal의 알림 관리 화면
주목할 점은 다음과 같다:
- 요청이 진행 중일 때 htmx가 폼을 비활성화한다.
- 사용자가 “Save”를 다시 클릭하는 즉시 이전 성공/오류 메시지가 사라진다.
- 성공 메시지와 오류 메시지의 스타일이 다르다.
따라서 각 폼마다 커스텀 JavaScript를 작성하지 않고도 꽤 많은 기능을 구현할 수 있다. 이런 흔한 기능에 서드파티 라이브러리가 필요하지 않고 HTML 자체가 이렇게 진화했더라면 좋았겠지만, htmx가 그 간극을 메워 주고 있어 만족한다.
마무리
무엇을 해냈나?
- 이 블로그를 통해 Is It Keto를 2천 달러에 판매했다.
- 블로깅에 관한 라이브 강의를 모두 마쳤다.
- 첫 번째 htmx 확장 기능을 만들었다.
- 강의를 위한 마케팅 툴인 Personal Best를 만들었다.
배운 점
- 영상 녹화 과정의 마찰을 줄일 기회를 찾아라.
- 재능 있는 블로거들과의 인터뷰와 블로그 글 뜯어보기를 공개해 강의를 홍보할 수 있다.
- 미국의 대부분의 인디 창업자는 Merchant of Record가 필요하지 않을 것이다.
다음 달 목표
- 강의 녹화 마무리하기.
- 강의 판매 시작하기.
글을 무작위로 읽기
댓글
로그인하고 댓글 남기기