지금 근무하고 있는 회사에서 기존 웹 서비스를 새롭게 리뉴얼하는 프로젝트를 진행했다.
기존 서비스는 빠른 속도로 우선 런칭을 하기 위해 express
+jquery
(일명 4드론 전략)을 사용해서 개발해 그 확장성과 유지보수성이 굉장히 떨어졌기 때문이다.
때문에 리뉴얼 프로젝트를 진행하기로 결정했고, 새로운 조합은 Spring
+ Next.js
였다.
원래는 react
로 가려 했으나, 아직은 불안정한 SEO 이슈 해결 및 빠른 초기 렌더링 속도를 위해 Next.js
로 결정하였다.
긴 프로젝트 기간을 거쳐 드디어 리뉴얼 끝! 런칭을 진행했다.
Next.js
를 사용한 회사 서비스는 처음으로 만들어본 것이었기 때문에 감개무량 + 걱정 반 + 기대 반 인 상황이었다.
그래서 아주 조마조마 하면서 배포를 시작했다.
당연히 이런말을 하면 안됐다.
오픈하고 한 5분정도 까지는 문제가 없었는데, 사람들이 조금 더 접속하며 갑자기 문의가 들어오기 시작했다.
제가 원했던 데이터가 아닌 다른 데이터가 보여요 😦
다른 데이터가 자꾸 보이는데 어떻게 된건가요?? 🤨
(큰일났다)
100% 잘된다는 보장은 당연히 없기에 (특히 개발업계는 더더욱) 문제가 발생하지 않을거라고는 생각하지 않았다.
하지만 지연 정도의 이슈를 생각했지, 문의 내용의 증상은 예상하지 못했었다.
Don't cry spilled over milk. 😢
이미 엎질러진 물, 돌이킬 수 없기 때문에 곧바로 동료 백엔드 개발자와 문제가 무엇인지 파악부터 진행했다.
일단 문의가 들어온 내용들을 토대로 백엔드 부분의 이슈인지 프론트 엔드의 이슈인지 부터 확인했다.
증상등을 기반해 확인해 본 결과 프론트엔드 Next.js
의 SSR
부분에서 동시성문제(race-condition)이 발생했던 것이었다.
이제까지 React.js
의 CSR
환경에서만 개발했다 보니 Next.js
의 SSR
이 client
가 아닌 Node.js
에서 실행된다는 것을 간과해서 벌어진 일이었다.
그나마 다행이었던 점은 초기 사용자 수가 많지 않았고, 약 3분 정도의 시간으로 어떤 부분이 문제인지 까지 파악이 됐던 것이다.
그렇지만 실제 운영중인 서비스는 3분도 짧은 시간이 아니기에 최대한 빨리 서비스를 정상화 하기 위해 문제가 되는 코드들을 확인하고 패치를 진행했다.
Cookie
값을 가져와 사용하는 로직들이 있었는데, Cookie
를 인자값으로 받게 하는게 아닌, 라이브러리를 통해 Cookie
값을 가져오는 식으로 개발했다. (react-cookie
같은)
개발 당시에는 CSR
환경에서 문제가 없었지만, SSR
은 쿠키 값이 없기 때문에 같은 로직이 돌아가지 않았다.
그래서 이를 해결하고자 SSR
시작 시점에서 Context
의 Cookie
를 추출해 라이브러리를 통해 저장하고, SSR
이 끝나는 시점에 다시 Cookie
값을 업데이트 해서 브라우저로 전달했다.
문제는 바로 여기서 발생했다.
SSR
은 사실 Node.js
에서 구동되기 때문에 하나의 유저가 프로세스를 타고 있을 때 다른 유저가 프로세스를 타게되면, 라이브러리로 어딘가에 저장된 Cookie
값을 덮어씌우거나 다른 값을 가져오게 됐던 것이다.
SSR
의 로직 및 데이터들의 사용은 모두 Request-bound
안에서 진행이 되었어야 했는데, 그 부분을 생각하지 못해서 발생한 이슈였다.
결국 SSR
과 CSR
로직을 분리해서 SSR
의 로직들은 모두 인자값을 직접 받도록 고쳐 배포를 진행하고 문제를 해결했다.
정말 정신없이 문제를 해결하고, 왜 이런 일이 생기게 되었는지 정리해보았다.
앞서 작성했듯이 SSR
을 이용한 회사의 서비스 개발은 처음이었다.
때문에 어떤식으로 코드를 작성해야 하는지 능숙하지 않았고, SSR
코드를 작성하는 부분에서 잘못된 판단을 했다.
예를 들어 axios
라이브러리로 쿠키값을 포함한 API 요청을 한다고 생각해보자.
CSR
환경과 달리 SSR
에서는 axios
의 withCredential
옵션을 주더라도 정상적으로 동작하지 않는다.
왜냐하면 Node.js
는 쿠키 값이 없기 때문이다.
따라서 직접 요청에 cookie
값을 주입해주는 방법을 사용해야 한다.
이런식의 CSR
과 SSR
의 차이점이 발생되는 경우가 많았는데, 그럼 결국 CSR
용 코드 따로, SSR
용 코드 따로 만들어야 하는건가? 하는 고민이 계속해서 쌓이게 되었다.
결론적으로 개발 당시 내가 판단한 것은 "같은 코드를 사용하자" 였다.
프론트엔드 개발을 하며 가장 많이 보였던 키워드 중 하나는 재사용 이었다.
중복되는 코드를 최대한 방지하고, 재사용하고, 최적화하는게 프론트엔드가 해야할 일이라고 생각했다.
때문에 당시 생각으로 CSR
용 따로 SSR
용 따로 만드는 건 기능은 동일한 두개의 코드를 각각 만드는 것과 다름없게 느껴졌고, 잘못 만드는 코드라고 생각했다.
그래서 최대한 그 둘의 중복되는 코드들을 하나로 사용하도록 개발했다.
앞서 말했듯이, SSR
구간은 Browser(Client)
가 아닌 Node.js(server)
에서 실행되는 코드였고, 그 말인 즉슨 Frontend
처럼'만' 생각해서는 안되는 것이었다.
만약 React.js
만 사용했었다면 문제 없을 상황이었지만, Next.js
는 프론트엔드이더라도 Server
의 형태를 띄고 있기에 그 부분에서 생길 수 있는 이슈를 고민해봐야 했다.
문제를 해결한 후에 동료 백엔드 개발자분과 이야기를 나누며 그 부분을 좀 더 확실히 인지했다.
"백엔드에서 어떤 값을 입력받을 경우, DTO를 만들어 레이어 별로 직접 전달하도록 설계해요."
"이를 통해 각 계층 간의 의존관계를 안전하게 유지하고 동시성 문제와 같은 이슈를 피할 수 있어요."
(사실 컴포넌트도 무조건 모든걸 재사용 하는 것이 아닌, 상황에 따라 추후 개발 방향을 보고 같은 모양이라도 다른 파일로 만들기 때문에 재사용에만 초점을 줬던 것은 시야가 좁았던 확실한 판단 미스 였다.)
(다행히 이런 일은 일어나지 않았다....)
Next.js
를 그저 SEO
에 좀 더 최적화 된 React.js
정도로만 생각했던 안일함에 문제가 있었다.
SSR
과 CSR
의 장점을 모두 갖는 것의 이면에는, '잘' 사용하기 더 어렵다는 것을 생각하지 못했던 것에 아쉬움이 남아 있다.
이번 일을 계기로 Next.js
를 사용하는 것이 간단한 것이 아니라는 것을 알게 되었고, 새로운 기술을 접근하는 데에 조심하고 잘 알아봐야 한다는 것을 더더욱 깨닫게 되었다.
새로운 기술을 도입할 때는 단순히 그것의 좋은 면만 바라보는 것이 아니라, 그에 따라오는 더 신경써야 하는 점 등을 고려하는 것이 중요하다.
Next.js
를 그저 SEO
, SSR
처리기 정도로 생각했던 사람들이 있다면, 이 글을 보고 조금 더 진지하게 임해 나 같은 일이 없도록 바란다. (ㅠㅠ)
나중에 기회가 된다면, Next.js
를 서비스에 사용하는 회사들이 어떤식으로 코드를 작성하는지 알아보고 싶다.
+ 읽어주셔서 감사합니다.
+ 오타, 내용 지적, 피드백을 환영합니다. 많이 해주실 수록 제 성장의 밑거름이 됩니다.
저도 이거때문에 고생많이 했었습니다... 화이팅하세요!