본문 바로가기

개발/프로젝트

Technical challenges in Teo Sprint

'나랑 벚꽃 보러 가지 않을래?' 스프린트 프로젝트에서 내가 겪었던 기술적인 도전들을 적어보려고 한다.

1. Tainted Canvas

스프린트를 하는 과정에서 아이폰에서 초대장 생성이 안 되는 이슈가 있었다. 유저가 만든 화면으로 canvas를 생성하고 그것을 url로 바꿔 firebase에 전달하는 로직에서 발생한 이슈였다. 내가 짰던 코드가 아니었기 때문에 코드를 찬찬히 뜯어봤는데 비동기 처리 부분이 뭔가 이상했다. 코드를 동기적으로 처리하기 위해 setTimeout을 사용하고 있었다. await가 제대로 동작하지 않아 시간이 없어 setTimeout을 사용했다고 예전에 들었던 것 같다.

이 문제가 firebase의 비동기 처리 부분에서 일어난 문제라고 생각해 await 코드로 바꿔보기로 했다. setTimeout 로직을 지우고 관련 코드에 로그를 찍어보며 찬찬히 살펴봤다. 함수 앞에 await를 쓴다고 해서 그 함수에 들어가 봤는데 함수가 리턴하는 게 없었다. 함수 내에서 Promise를 반환하는 외부 API를 사용하면서 아무것도 반환하지 않으니 당연히 await가 되지 않는 것이었다. return 문을 적절히 붙이니 코드는 동기적으로 잘 작동하기 시작했다. 이렇게 우선 하나의 문제를 해결했다!

이렇게 비동기 처리를 깔끔하게 하며 문제가 끝난 줄 알았는데... 끝이 아니었다. 이상하게 아이폰이나 맥북 사파리에서 알 수 없는 에러가 나기 시작했다. 알 수 없는 SecurityError가 나왔다.

처음에는 firebase origin 관련 에러라고 생각해 firebase에서 origin 설정을 열어줬다. 그러나 firebase 실행 함수 안에 콘솔을 찍어도 아무 것도 나오지 않았다. 해당 함수는 실행조차 되지 않는다는 것이었다. firebase 관련 코드를 주석처리 하고 canvas 관련 코드로 넘어갔다. 하나하나 콘솔 찍어보며 원인을 찾았다.

catch를 통해 canvas.toDataURL 함수가 에러를 던진다는 걸 알고 이걸 키워드로 구글링했다. canvas에 origin-clean flag가 false면 내 콘솔에 뜨던 SecurityError를 던진다는 공식 문서를 보게 되었다.

https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-todataurl

 

HTML Standard

 

html.spec.whatwg.org

html2canvas로 만드는 canvas의 origin-clean flag가 false로 되어 있기 때문에 SecurityError를 던졌던 것이었다. flag를 true로 바꾸기 위한 검색을 진행했다. origin flag를 바꾸는 방법은 찾을 수 없었다. 지금 생각해 보면 개발자가 임의로 바꿀 수 있는 속성은 아니었다고 생각한다. 아무튼 방법을 계속 찾아보다 flag를 바꾸는 것이 아닌, 새로운 Image 인스턴스를 만들고, 그 이미지의 src에 이미지 url을 넣는 것을 생각했다. 그 인스턴스의 crossOrigin 속성에 'Anonymous'로 바꾸면 origin에 상관없이 사용이 가능했다. 그런데 이미지 url이 있어야 해야 src에 설정을 하는데 toDataURL이 안돼 이미지 url을 받아올 수가 없어 이 방법도 막혔다. 이것저것 서칭하다 canvas가 tainted 즉, 오염돼 있으면 origin 관련 이슈가 나온다는 글을 봤다.

https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image

 

Allowing cross-origin use of images and canvas - HTML: HyperText Markup Language | MDN

HTML provides a crossorigin attribute for images that, in combination with an appropriate CORS header, allows images defined by the <img> element that are loaded from foreign origins to be used in a <canvas> as if they had been loaded from the current orig

developer.mozilla.org

이 글을 읽다 문득 생각이 들었다. 캔버스 요소 중 foreign content가 있으면 canvas가 tainted 된다는데, 내 캔버스 어디에 foreign content가 있지? 라는 생각이 들었다. html 요소들을 살펴보다 border가 눈에 들어왔고, 이거다 싶었다. 팀원들이 textarea를 꾸미기 위해 border를 외부에서 가져왔는데, 이것이 foreign content였던 것이다. 이것을 지우니 간단하게 해결이 됐다. 아이폰과 맥북 사파리에서 안 됐던 이유는 아마 브라우저 별로 origin 정책이 달라서 그랬던 것 같다.

2. 초대장 생성 페이지 최적화

Next를 처음 써봤었고, 성능 최적화 경험도 처음이었다. 다른 페이지는 다른 사람들이 최적화 했기 때문에 나는 내가 맡았던 초대장 생성 페이지에 대한 최적화를 맡았다. 우선 현재 초대장 생성 페이지의 성능을 알아보기 위해 Chrome dev tools의 Lighthouse 탭에 들어갔다. 나머지 점수는 다 안정적이었는데 Performance가 너무 낮았다. 데스크톱은 79였고, 모바일은 64였다. 우리 서비스를 보통 모바일에서 접속하기 때문에 모바일 버전 위주로 최적화하기로 했다. TTI(Time to Interactive)는 16.8s, LCP(Largest Contentful Paint)는 23.1s가 나왔다. Performance 탭에 가서 보니 수치가 달랐다. 여기는 수치가 ms 단위로 나왔다. 두 탭의 시간이 왜 이렇게 차이 나는지 찾아보니 차이가 있었다. Lighthouse에서의 시간과 Performance에서의 시간이 다른 이유는 Simulated Network Throttling과 CPU Throttling 때문에 Lighthouse에서는 보통 Performance의 시간보다 시간이 더 길게 나오는 것이었다.

https://stackoverflow.com/questions/64791933/lcp-time-between-lighthouse-and-performance-google-chrome

 

LCP time between LightHouse and Performance - Google Chrome

With google chrome chrome dev, I am running a lighthouse Analysis for mobile. Lighthouse shows a 7.0 seconds delay for Largest Contentful Paint (LCP): I decide to dive into this and click on: &quo...

stackoverflow.com

Lighthouse의 지표들 - 최초 수치
Performance 탭의 지표들 - 최초 수치

 

Next가 처음이다 보니 최적화를 위한 여러 방법들을 찾아보았다. 처음에는 페이지를 SSG로 바꾸면 더 빠를 것이라 생각해서 SSG 설정을 넣었다. 그런데 나중에 알고보니 Next가 기본적으로 SSG 렌더링을 지원하기 때문에 전혀 달라진 것이 없었다. 분명 성능은 약간의 개선이 있었던 것 같은데, 그저 우연이었나 보다.

여러 요소의 최적화를 고민하다 Next Image의 priority 속성을 사용해보기로 했다. 기존에는 Creation 페이지의 display 영역 배경을 div 요소의 background-image로 가져왔었다. 이 배경에 Next의 priority 속성을 사용하기 위해 background가 아닌 Next Image로 배경을 렌더링하도록 수정했다. 그렇게 코드를 수정하니 유의미한 변화가 있었다! Lighthouse의 LCP 수치는 17.6s(23.3s -> 5.6s)로 개선되었다. LCP인 display 영역을 우선 렌더링 하느라 TTI가 약간 늦어졌지만, 유저가 First Paint 이후 LCP를 바로 볼 수 있다는 점에서 성능의 큰 상승이었다. Performance 탭에서도 확인할 수 있었는데, First Paint에서 LCP까지의 시간이 약 86ms(281.9~367.9ms)로 크게 줄었다. 처음 579.2ms보다 약 6.7배 빨라진 것이었다.

Lighthouse의 지표들 - Next Image priority 적용 후
Performance 탭의 지표들 - Next Image priority 적용 후

 

이번 스프린트와 서비스 유지 보수 과정을 통해 다양한 경험을 할 수 있었다. 개발적으로도 성장했고 실제 유저를 받아 보며 피드백도 들을 수 있어 정말 즐겁고 뜻깊은 경험이었다!!!

'개발 > 프로젝트' 카테고리의 다른 글

테오의 스프린트 14기 기술 회고  (1) 2023.03.03