단편만화사이트 로쿠로쿠 개발기

 

0부터 100까지 웹사이트 하나 만들고자 했다가 드디어 탈출했습니다.

완성본 화면부터!



근래에 공부하기 시작한 React, Django를 몸에 익히기 위해 간단한 토이 프로젝트를 완성했습니다. 어짜피 휘발될 코드라고 생각해서 어떤 주제의 웹사이트를 만들지는 중요하지 않다고 생각했지만, 평소 취미에 맞춰서 만화사이트를 만들어 보기로 했습니다.


서비스 링크는 https://rokuroku.work 웹과 코드 모두 삭제했습니다!


(홈에 걸린 안내문)


프론트엔드, 백엔드 양쪽 다 무언가 완성하고 배포한 경험이 없었기에 오히려 고민없이 결정할 수 있었습니다. Angular는 예전에 잠깐 건들여봤는데 계속 헤매던 느낌이었고, Vue보다 React가 라이브러리 지원도 크고 점유율이 높다고 해서 프론트엔드 프레임워크는 React로 결정했습니다. 백엔드는, 구상해놓은 사이트가 워낙 경량이고 이걸로 백엔드 관련 학습에 도움이 많이 되진 않을 것 같아서 Django REST API로 간단하게 구현하기로 했습니다.


그런데… 턱턱 막히는 부분이 늘어나고 늘어나고 예상했던 기간보다 훨씬 오래 걸렸습니다. 며칠 집중해서 뚝딱 해결하려 했는데 너무 쉽게 봤었나봅니다. 투자한 날이 합쳐서 2주 정도 되는 것 같습니다. 아무래도 튜토리얼처럼 이 이후에 해야될 일이 뭔지 정확히 모르는 상태에서 선형으로 진행하다보니까 불필요한 작업이나 불필요한 작업이나 바로 뒤에 충돌이 일어날 일을 자주 만들게 됐습니다.


‘N일차 개발기’처럼 날마다 과정을 기록했다면 참 좋았을텐데 그게 제일 아쉽네요.

이미 머리에서 떠나간 것도 많지만 기억에 남는 큼지막한 걸림돌들을 정리합니다.


일단 전체적인 구성은 이렇습니다.

프론트엔드

  • 백엔드 서버도 배포하기 전이라 둘다 localhost로 띄워놓고 연동시켰는데 그렇게 하니까 요청 응답시간이 좀 오래 걸렸습니다. 페이지마다 화면에 보여줄 만화들을 그때그때 보여줄 썸네일들을 다 fetch 받아서 사용하기에 무리가 있다고 느껴졌고, 그래서 LocalStorage를 적극 사용하게 하고 서버<->클라이언트<->로컬스토리지 구조를 최적화해보려고 시간을 많이 썼는데 서버를 AWS로 띄워놓고보니 응답시간이 훨씬 빨라져 있더랍니다. 그렇게 되고 보니 LocalStorage보다 서버를 더 쓰는게 당장 성능에 장점을 보여서 지우게 된 코드가 많습니다.

  • 처음에는 create-react-app으로 프로젝트를 생성해놓고, 필요에 따라 닥치는대로 패키지를 적용시키려고 webpack configuration을 eject해서 수정하다가 충돌이 일어나는 상황이 왔었습니다. 해결방법을 찾아보니 react-app-rewired이라는 패키지를 사용하면 webpack config를 드러내지 않고 새로 쓰는 코드만으로 덮어쓰게 해서 좋더군요.

  • 핸드폰이나 패드로 웹을 킬 때는 hover시 발동되는 스타일 변화를 줄 필요가 없어집니다. 저는 이 이유로 기계가 touch device인지 확인하고 sass에서 참조할 방법이 필요했는데, 저는 root component에서 is-touch-device 패키지를 이용하여 기계를 확인하고 <div ... data-is-touch={this.state.touch}>와 같이 프로퍼티 설정했습니다. 그리고 sass 파일에서 .클래스명[data-is-touch]로 해당 변수를 확인할 수 있게 됩니다.

  • 사이트 화면을 보시면 만화 썸네일이 좌에서 우로, 그리고 아래로 나열되고, 썸네일 사이에 빈 공간을 넣었는데, flexbox의 (저는 layout을 신경써야하는 element는 대부분 flexbox를 사용했습니다.) 나열을 flex-start로 적용시키면 폭을 꽉 채우지 못하고, 그렇다고 한쪽 margin을 살짝 넣은채 꽉 채우면 좌우 균형이 살짝 어긋나게 됩니다. 그래서 저는 flexbox의 나열을 space-between으로 하고 썸네일의 크기를 %로 지정해 썸네일 사이 간격을 조절했습니다. 헌데, 이런 방식으로 마지막 줄에서 썸네일이 꽉차지 않고 왼쪽에만 정렬되려면 한 줄에 들어설 만화 갯수와 전체 갯수를 통해 나머지를 계산해야 합니다. 그리고 그 수가 media width에 따라 달라져서 jsx에서 모두 확인할 방법이 필요했습니다. 이건 window.matchMedia("(min-width: ~~px)").matches를 통해 가능했습니다. angular나 react나 window를 직접 참조하지 않는것이 암묵적인? 룰이라고 생각했는데 이에 대한 개선책은 아직 찾지 못했습니다.

  • 저는 주로 구글 크롬을 켜놓고 localhost 화면을 보면서 레이아웃을 수정했습니다. 그리고 핸드폰으로 볼 때를 가정하여 오른쪽에 켜지는 콘솔창 크기를 조절해서 media query가 읽는 장치 폭을 줄였다고 생각했는데, 이게 알고보니 창크기를 조절한다고 그 사이즈를 css가 정확히 포착하는 것이 아니더군요? 그래서 ngrok을 이용해 직접 개발 과정에 핸드폰으로 접속하며 작업했습니다. 크롬의 개발자도구로 viewport를 다른 device 버젼으로 변경할 수 있더군요.

  • 홈페이지를 바로 접속하는것이 아닌 url을 통해 내부 페이지 중 하나에 직접 접속하는 경우 처음에는 access denied 에러가 나타났었습니다. 원인을 찾아보니 제 프론트엔드가 SPA라 url을 제 경우에는 Root.js에서 해석해야하는데, url을 통해 내부 페이지로 직접 접근할 경우, 예를 들어 /about, Cloudfront에서 S3의 /about 파일을 찾게되고 당연히 그런 파일은 존재하지 않아 에러가 발생하게 됩니다. Cloudfront의 Custom Error Responses 설정에서 400과 403 에러를 /index.html으로 보내 반응하도록 설정하여 해결하였습니다.

백엔드

  • 저는 DRF의 ModelSerializer와 generics APIView를 사용했습니다. 저는 Django의 View와 Serializer가 명확하게 구분되지 않는단 느낌을 받았습니다. 그 둘 사이에서 좀 헤맸네요. 일단 한 모델에서 ForeignKey에 해당되는 모델의 다른 프로퍼티를 참조하려면 View 이전에 Serializer에서 해당 모델의 Serializer를 이미 끌어온 상태여야 합니다.

  • Route 53과 Cloudfront를 이용해 배포한 프론트엔드는 HTTPS에서 요청을 보내고 그때까지 HTTP에만 열려있었던 제 ec2 nginx 서버는 에러를 냈습니다. 빠르게 SSL 인증서를 받을 수 있는 Route 53 + ELB listener를 배치해 HTTPS 요청을 받고 HTTP로 포워딩하여 해결했습니다.

  • Docker를 처음 적용해보려니까 디버깅에 시간이 너무 오래 걸렸습니다. 한번 꼬이면 초기화시켜버리곤 했는데 매번 환경 구성 시간이 길어서 말이죠…

개선이 필요한 부분

  • 아시다시피 만화는 작가 -> 만화 -> 에피소드식으로 계층이 펼쳐집니다. (단편만화에도 단편집이 있으므로 에피소드 개념이 성립합니다.) 그래서 저는 만화 컷이미지의 업로드 경로를 /media/<작가pk>/<만화pk>/<에피소드pk>/<이미지pk>와 유사한 구조로 만들었는데 눈치채신 분들도 계시겠지만 이렇게 하면 상위계층이 바뀔 때 초기화되어 0부터 다시 시작하는 것이 아니고 계속 수가 늘어나게 됩니다. (에를 들어 1/5 다음이 2/1이 아닌 2/6이 됩니다.) 직관적인 url을 위해 조치가 필요합니다.

  • LocalStorage를 이용해 쇼핑몰의 “최근 본 상품”처럼 최근 본 만화나, 하트 표시한 만화를 보여주는 탭을 만들만 합니다.

  • 목표였던 기능은 다 구현했지만 프론트엔드에서 Global Variable과 유사한 기능을 해줄 수 있는 Redux를 사용하지 않았고, 스파게티 코드화를 피하려고 필요 이상의 request를 보냅니다. Redux 공부와 구현이 필요합니다.

  • 현재 Pagination 기능이 존재하지 않습니다. 50개가 채 안 넘는 만화의 갯수로 인해 다행히도 당장 필요하진 않지만, 이와 같은 사이트에서는 Pagination 구현이 필수라고 생각됩니다. 다만, 어떻게 Pagination을 구현할지 고민해보니 방법이 서비스 구조에 따라 다양할 것 같은데 어떤 것이 최선일지 아직 모르겠습니다.

  • Pagination과 같은 맥락에서 Sever Side Rendering에 대한 고민도 있습니다.

  • Typescript가 전혀 사용되지 않았습니다.

  • 필요 이상으로 해상도가 높은 이미지 파일들이 간혹 있고 이것들을 로딩할 때 시간이 오래 걸립니다. 혹시 이미지들을 자동으로 리사이징해줄 툴이 있나 찾아야 할 듯합니다.

총평

시간이 많이 갈렸지만 초행길이다보니 더 나은 퀄리티를 위한 고뇌보다는 방향을 제대로 파악하기 위한 헤맴의 시간이었던 것 같습니다. 많이 아쉽지만 그래도 유사한 과정을 다시 겪는다면 이번보다 훨씬 시간 감축을 할 수 있을 것 같아서 그게 그나마 보람으로 남았습니다.


참고 링크

  • https://hub.docker.com/r/dockerfiles/django-uwsgi-nginx
  • https://nachwon.github.io/django-deploy-2-wsgi/
  • https://item4.github.io/2015-09-29/Make-ELB-And-Apply-SSL(HTTPS)/
  • https://react-etc.vlpt.us/08.deploy-s3.html