"NO완벽" 개발기

2025.06.15
profile_imagew0nder
<link-preview url="https://showyourti.me/quote" title="NO완벽" target="_blank"> </link-preview> 제 성격상 완벽주의를 추구하다 보니 여러 힘듦이 있었습니다. 뭔가를 시작하려고 하면 "완벽하게 해야 해"라는 생각 때문에 오히려 아무것도 못하게 되는 상황들이 반복되더라고요. 그래서 완벽하지 않아도 계속 굴러갈 수 있게 여러 방법들을 고민하다가, 관련된 문구를 지속해서 보면 어떨까 싶었어요. 사실 전에도 "완벽하지 않아도 괜찮아", "일단 시작해보자" 같은 문구를 바탕으로 핸드폰 배경화면을 만들어서 SNS에 올렸더니 반응이 좋았었거든요. 그래서 이걸 제대로 된 서비스로 만들어보면 어떨까 해서 잠시 틈이 나서 바이브 코딩으로 가볍게 시작했습니다. 완벽주의를 타파하기 위한 문구가 매일매일 바뀌어서 노출되고, 배경색과 그라데이션도 함께 바뀝니다. 원하는 문구가 있다면 오른쪽 위 저장하기 버튼을 통해서 모바일 바탕화면 이미지를 만들어볼 수 있어요. 하루에도 몇 번씩 폰을 볼 때마다 리마인드를 받는 컨셉이었습니다. ## 기술적 고민: 하나의 코드로 두 곳에서 동작하기 서비스 기획을 하면서 두 가지 기능이 필요했어요. 웹에서 사용자가 원할 때 이미지를 다운로드할 수 있는 기능과, 서버에서 매일 자동으로 이미지를 생성해서 뉴스레터로 구독자들에게 보내주는 기능이었습니다. 매번 이미지를 서버에서 만들면 서버 비용이나 트래픽 비용이 나가니까, 웹에서는 브라우저 자체에서 이미지를 만들고 뉴스레터 같은 자동화가 필요한 부분만 서버에서 처리하려고 했어요. 그럴려면 하나의 생성 코드로 두 군데서 동작하게 해야 했습니다. ## Canvas로 시작했지만... 그래서 처음에 고민한 건 canvas였습니다. 전에 회사에서 브라우저 이미지 생성을 canvas로 했을 때 MFC 하던 느낌도 나고 했지만, 깔끔하고 빠르게 이미지가 잘 생성되었거든요. Node 환경에서도 canvas를 쓸 수 있는 라이브러리가 있지 않을까 해서 찾아보니 node-canvas 같은 것들이 있더라고요. 그냥 되겠거니 하고 일단 브라우저용 canvas 코드부터 만들기 시작했습니다. 배경 노이즈나 그라데이션, 텍스트 렌더링 등을 처리하는데 생각보다 복잡했어요. 특히 텍스트를 여러 줄로 나누는 부분이나 전체 이미지 사이즈에 맞춰서 스케일을 계산하는 부분들이 까다로웠지만, 어떻게든 브라우저에서는 원하는 대로 동작하게 만들었습니다. 그런데 문제가 발생했어요. 그렇게 만든 코드를 node-canvas로 옮기려고 하니까 원하는 스타일이 제대로 지원되지 않았습니다. shadow나 blur, opacity 같은 효과들이 브라우저와 다르게 동작했고, 폰트 관련 처리도 간단하지 않았어요. ## Vercel 배포의 현실 그리고 최종적으로 vercel로 서버를 운영하려고 했는데, node-canvas를 vercel에 올리는 게 생각보다 복잡하더라고요. 혹시나 해서 다른 사람들은 어떻게 했나 찾아보니, 이미 누군가가 정리한 글이 있었어요. 그런데 그 과정을 보니까 패키지 크기가 179MB나 되는데 vercel은 50MB 제한이 있어서 커스텀 빌드를 해야 하고, zlib 버전 충돌 때문에 patchelf 같은 툴로 라이브러리를 패치해야 하더라고요. 읽어보니까 정말 복잡한 과정들이 많았어요. 물론 그 글을 따라서 똑같이 하면 되긴 했겠지만, 혼자 개발하다 보니까 그런 복잡한 설정을 하고 싶지 않았습니다. 나중에 vercel이나 node-canvas가 업데이트되면 또 다른 문제가 생길 수도 있고, 그때마다 이런 복잡한 해결책을 찾아야 한다고 생각하니 부담스러웠어요. ## 방향 전환: Satori라는 대안 그래서 방향을 바꿔서 vercel/og(satori)를 사용하기로 했습니다. 애초에 목표가 스타일 코드를 동일하게 쓰는 거였으니까, HTML로 스타일을 관리하고 서버에서는 그걸 satori에 넣어서 이미지를 만들고, 프론트에서는 html-to-canvas를 통해서 이미지를 만드는 방식으로 접근했어요. 일단 satori에서 원하는 스타일대로 결과물이 나오는지 확인하면서 작업했습니다. canvas보다 훨씬 간단했어요. HTML과 CSS로 레이아웃을 잡으면 그대로 이미지로 변환되니까 텍스트 줄바꿈이나 배경 처리 같은 것들이 자동으로 처리되더라고요. 물론 satori도 완벽하지는 않았어요. position과 opacity 관련해서 브라우저와 다르게 동작하는 부분들이 있었고, 일부 CSS 속성들이 예상과 다르게 적용되기도 했습니다. 하지만 그런 부분들은 다른 방식으로 우회하거나 레이아웃을 조금 다르게 접근해서 해결할 수 있었어요. ## 완벽주의 타파의 진짜 의미 이 프로젝트를 하면서 깨달은 건, "완벽주의 타파"라는 게 단순히 대충 하자는 게 아니라는 거였어요. 기술 선택에서도 마찬가지였습니다. Canvas + node-canvas 조합이 기술적으로는 더 "완벽한" 솔루션일 수 있었지만, 혼자서 유지보수하기에는 너무 복잡했어요. 반면 satori는 기능적으로는 약간의 제약이 있지만, 훨씬 간단하고 안정적으로 동작했습니다. 결국 100% 완벽한 기술적 해결책을 추구하다가 아무것도 못 만드는 것보다는, 80% 정도의 결과물이라도 실제로 동작하는 서비스를 만드는 게 더 가치 있다는 걸 느꼈어요. ## 앞으로의 계획 현재는 기본적인 기능들이 잘 동작하고 있어서, 다음주에는 사용자들이 문구를 제안할 수 있는 기능을 추가해보려고 합니다. 문구가 선정되면 화면과 이미지에 제안자 정보가 노출되는 방식으로요. 그리고 뉴스레터 기능도 천천히 추가할 계획이에요. 완벽주의에 발목 잡혀서 아무것도 못하고 있다면 한번 써보세요. 매일 다른 문구를 보면서 "완벽하지 않아도 괜찮다"는 걸 자꾸 상기시키다 보니 생각보다 효과가 있더라고요. 완벽하지 않아도 괜찮습니다. 일단 시작해보는 것, 그것만으로도 충분하니까요.