'블렉스 이야기' 시리즈MPA 웹사이트를 SPA로 변경하며

배진오

@baealex

소비적인 일보단 생산적인 일을 좋아합니다.

내가 처음 SPA(Single Page Application)라는 것을 알게된게 작년 9월 쯤이었던 것 같다. 당시에는 자바스크립트에 대해 정말 무지해서 React, Vue 같은 프레임워크들이 어렵게 느껴져 익히는 걸 미뤘었다. 그 핑계는 내 프로젝트(BLEX)엔 SSR이 매우 중요하다는 것이었다. 언젠가는 이 프로젝트를 React로 바꿔보고자 하였지만 그것은 기약없는 약속이었다



최근이라 말하기엔 좀 그렇지만 1-2개월 전쯤 GitHub도 SPA로 변경된 듯 보였고 점점 (기술적으로) 트랜디한 사이트들은 다 SPA로 개발하는 것 같았다. 슬슬 똥줄이 타기 시작했다. 더 미뤘다가는 기술스택에 뒤쳐져 저 나락으로 떨어질 것 같은 불안감이 들었다. 그리하여 내가 이 프로젝트를 SPA로 변경해야 만 하는 이유를 만들어 스스로를 채찍질 하였다.

  • 나중에 백엔드 언어 바꾸는 상상을 해봐... 얼마나 편해지겠어?
  • 아웃바운드 최소화 할 거라며? 지금이 그때야!


그래, 일단 저지르고 보자



"아 진짜 해봐야겠다" 생각을 하고 ReactSSR을 정말 쉽게 만들어준다는 라이브러리 Next에 대해서 알아보기 시작했다. 슬쩍 사용해보니 왠걸... 정말 쉬웠다. 디렉터리 구성도 내가 React를 사용할 때 구성하던 것과 비슷했고 "아니 진짜 이렇게 쉽게 된다고?" 싶을 정도로 간단하게 SSR이 구현된다.

코드 파일에 Node를 같이 포함해서 개발한다고 생각하면 맘이 편하다. 컴포넌트에 Node에서 생성한 props 던져주면 초기 컴포넌트 그리는 걸 서버에서 해주는 것 같더라. 다만 그렇기 때문에 일부 라이브러리들은 Next용으로 따로 받거나 window && 코드를 삽입하여 서버에서 동작하는 코드와 클라이언트에서 동작하는 코드를 분리하거나 전부 호환되도록 짜야한다.

일단 기존에 존재하던 기능들을 쭉 나열해서 할 수 있을 것 같은 일부터 하나씩 진행했다. 그냥 출력만 되던 부분들이나 기존에 비동기 통신으로 구성되었던 부분은 쉽게 이식시킬 수 있었는데 포스트 작성이나 기존에 장고에 의존적이던 코드, jQuery에 의존적인 라이브러리를 대체하거나 새로 구현하는 부분에서 상당한 시간이 걸렸다.


알게 된 것

ReactDjango에 대해서 좀 더 다양한 사실을 알게 되었다. 솔직히 말하면 알아야 했던건데 여지껏 모르고 있었던게 맞는것 같다. 저수준에 대한 이해가 매우 중요함을 항상 깨달으면서도 금세 잊어버리게 된다. 어떤 것들을 새롭게 알게 되었냐면


Session ID

프론트엔드와 백엔드가 분리된 상태에서는 어떻게 로그인을 하며 유지하는지 몰랐는데 이 부분을 알게 되었다. 이론적으로는 알고 있었지만 분리된 상태에서 장고에서 어떻게 처리를 해줘야하는지 몰랐다. 장고로 풀스택 개발할때는 그냥 아래와 같이 명령어를 쳐주면 되니 그냥 그려러니 했었다.

auth.login(request, user)

그냥 이렇게 하면 된다는 걸 알았을 뿐 위 동작이 어떤 동작을 수행하는지 몰랐다. 위 동작은 클라이언트에게 set-cookie 헤더에 Session ID를 걸어주는 역할을 수행한다. (내 사이트의 패킷을 분석하며 알아낸 사실이었다.) 그래서 분리된 상태에서도 같은 메서드를 사용하여 로그인을 시켜줄 수 있었다.

또한 Session ID가 쿠키이므로 클라이언트에서 접근이 가능할 것이라 생각했으나 불가하다는 것을 새롭게 알았다. (클라이언트에서 로그인 상태를 파악하려다 알아낸 사실이었다.) set-cookie 헤더에서 HttpOnly로 설정된 경우 클라이언트에서 접근할 수 없다.


CSRF

CSRF@csrf_exempt 데코레이터를 통해 부분적으로 해제할 수 있는데 전역적으로 해제하고자 하였다. 원레는 백엔드에서 CSRF 토큰을 가져와서 처리하려 했으나 Django에서 템플릿을 렌더링하지 않는 이상 불가능(?) 한 것 같았다. 물론 중간에 한 번 돌아가면 쓸 수 있을 것 같지만 그렇게까지 해야할까... 여하튼 전역적으로 해제하려면

from django.utils.deprecation import MiddlewareMixin

class DisableCSRF(MiddlewareMixin):
    def process_request(self, request):
        setattr(request, '_dont_enforce_csrf_checks', True)

위와같이 코드를 작성하고 settings의 미들웨어에 추가하면 된다. 종종 전역적으로 처리할게 있다면 유용할듯 보인다.


withCredentials

타 사이트간에 요청이 불가한건 알았지만 CORS를 허용하면 끝인줄 알았는데 쿠키는 또 따로 해제를 해줘야 했다. 현재 비동기 통신 라이브러리로 Axios를 사용하는데 요청할때

axios({
    method: 'PUT',
    url: 'http://localhost:8000/api/apple'
    withCredentials: true
});

위와같이 보내면 쿠키도 함께 보낸다. 하지만 장고에서도 이를 허용해야 한다. (이것땜에 시간 진짜 많이 잡아먹었다. 😥)

CORS_ALLOW_CREDENTIALS = True


기타등등

그동안 잘 모르던 React의 다양한 사용 방법도 알게 되었고 더구나 코드를 줄이기 위해 다양한 최신 문법을 익히게 되었다. 이는 NextBabel등이 기본적으로 적용되어 가능한 일이라 생각한다.


개선한 것

기존에 비해서 개선된 점이라면 서버에서 해오던 많은 것들을 클라이언트에게 미뤘다는 것이다(?) 사용자 입장에선 개선이 아닐 수 있지만 서버 입장에선 확실한 개선이며 서버의 개선은 사용자 환경을 개선한다고 생각한다. 훗날에는 이미지 변환 같은 작업도 떠밀어야지(?) 여하튼 이는 클라이언트의 코드를 백엔드의 코드처럼 구조적으로 짤 수 있었던 덕분이라 생각한다. 왜 프론트엔드 프레임워크를 도입하는지 jQuery가 점점 사라져가는지 약간은 알 것 같다.


마크다운 변환

이 블로그는 마크다운으로 글을 작성하는데 사용자가 글을 작성할때 보여지는 미리보기와 서버에 실제 저장되는 데이터가 약간씩 달랐다. 이는 내가 자바스크립트에 익숙하지 않았으며 글쓰기 자체가 라이브러리에 굉장히 의존적이라 쉽게 손을 대지 못했기 때문이었다. 미리보기와 같은 기능으로 해결하고자 하였으나 그것은 근본적인 해결책이 아니었다.

SPA로 변환하며 글쓰기와 같은 기능들은 일부분만 라이브러리를 사용하고 주도권은 어떻게든 내가 가지고자 하였는데 여기서 시간을 많이 소비했다. 기존에 사용하던 마크다운 파서(Parsedown)와 유사한 라이브러리를 찾는데도 어려움이 있었다. 여하튼 서버와 같은 결과물을 토출하기 위해서는 서버에서 같은 라이브러리를 사용하거나 같은 방법으로 파싱하도록 하는 것을 고민했는데 최종적으론 사용자가 파싱해서 보내는 것이 최상의 선택지라 생각되었다.


글 임시저장

이건 사실 개선했다기 보다는 불가피한 선택이었다. 기존 MPA로 제작된 경우 타 페이지로 이동하는 것을 막을 수 있어 사용자의 실수를 차단할 수 있는데 SPA로 변경한 경우에는 뒤로가기나 페이지 전환을 막는게 불가하고 다시 뒤로 돌아와도 남아 있는게 없다. 이런 경우를 대비해서 임시저장을 최대화하여 글이 날아가는 현상은 막아야 했다.


개선한게 이것밖에 없나...;


더 개선해야 할 것

Functional

나는 ReactClass Component 기반으로 익혔고 그게 쉬웠다. 근데 요즘은 다 Functional Component로 개발하는 것 같고 문법도 내가 잘 모르는 문법으로 작성되어 솔직히 한번에 이해하기 어렵더라. 그런 코드를 보고 내 코드를 보니 약간 구식같은(?) 느낌이 들긴한다. 나에게 유용하다면 좀 더 유행에 근접할 수 있도록 노력해야지.


Global State

로그인 상태를 관리하기 위해서 전역 상태를 관리할 필요가 있었다. 지금까지 React는 기본적인 작업에만 사용해서 그냥 props...props...props...로 연명하고 있었는데 이렇게는 관리가 도저히 불가능 할 것 같았다. MobXRedux와 같은 라이브러리를 새롭게 익히고자 하였으나 러닝커브가 높아서 그냥 나만의 방식으로 처리했다. 추후에는 반드시 공부해봐야 겠다.


FormData

POST 방식으로 new FormData()에 파일 및 데이터를 담아 보낸 경우 장고의 request.POST에 잘 계시는데 PUT 방식으로 보낸 경우 이상한 데이터가 전송되었다. 그래서 현재는 이미지 업로드는 가능하지만 이미지 수정이나 그런게 불가능하다. 이것에 대한 원인을 좀 더 파악할 필요가 있으며 없다면 이미지만이라도 POST 방식으로 처리해야겠다.

이미지의 바이너리를 FormData 양식으로 전달하면 될 거 같긴한데 한번도 안해봐서 약간의 실험이 필요하다.


TypeScript, ESLint

TypeScriptESLint도 함께 적용해서 개발하고 싶었는데 설정하는거 하다가 너무 오래걸려서 시간이 아까워서 잠시 미뤘다... Next가 기본적으로 TypeScript를 지원해서 엄청 쉽다는데... 뭘 잘못한건지 일단 조만간 적용하도록 해야겠다.


Docker Build

도커에서 빌드를 효율적으로 하는 방법을 모르겠다. Next를 빌드하는 부분은 캐시를 전혀 사용할 수 없는걸까? 매번 실행시 빌드하는 시간이 상당히 아깝게 느껴진다. 개선할 수 있는 방법을 찾도록 해야겠다.


느낀것

난 개발을 좋아한다고 생각했다. 개발이 매우 재밌다고 생각했다. 근데 요즘들어 내가 진심으로 개발을 좋아하긴 했던건지 의구심이 들기 시작한다. 프론트엔드 개발을 좋아하고 관심도 많다고 생각했는데 생각보다 막 재밌게 개발했던 건 아닌 것 같다. 익숙하지 않아서 그런건지 아니면 원래 실증을 잘 내는 내 성격 때문인지... 중간중간 재밌기도 했지만 뭐랄까... 이런 기분이 처음이다. 재미없거나 짜증나는 감정이 앞선다. 부정적인 감정을 지배하는 근원은 잘 모르겠지만. 무튼 그랬다.

그리고 이전부터 느꼈지만 혼자 작업하면 백엔드와 프론트 왔다갔다 하는거 이젠 진절머리 난다. 🤣 지금은 더욱이 중간중간 언어도 바뀌고 언어의 특성도 다르고 뭔가 생각의 흐름이 확 바뀌는 부분에서 갑자기 뇌정지가 많이 왔다.



또한 나는 크롬만을 생각하며 개발을 진행했는데 이는 Next에서 어느정도 트랜스파일링이 될거라 생각하지만 여하지간 다른 브라우저는 아무런 신경을 쓰지 않았다는 점이 뭐랄까. 개발이란 것을 제대로 하고 있는 건지? 스스로에게 실망감이 컸다. 이렇게 놓고보니 난 뭘 위해서 이 프로젝트를 진행한 걸까? 부족한건 늘 그렇듯 차차 채워가면 되니까 화이팅하자 😀

'블렉스 이야기' 시리즈

블렉스는 제가 애정을 가지고 개발하는 하나의 취미이자 한명의 자식(?)이며 처음으로 서비스를 시작한 프로젝트입니다. 혼자서 풀사이클 개발을 하다보니 부족한 점이 많을 수 있지만 부족한 점은 채워나가면 된다고 생각합니다. 이곳은 블렉스의 부족한 점을 채워나가는 모습을 그려놓은 시리즈입니다.
😥 작성된 댓글이 없습니다!
댓글을 작성하기 위해 로그인이 필요합니다.