SSR 도입

천영석·2021년 9월 9일
0

SSR이란 Server Side Rendering으로 서버 사이드에서 렌더링에 필요한 html을 모두 구성해서 클라이언트로 보내준다는 의미이다. 즉, 클라이언트인 브라우저가 받는 index.html은 파싱만 완료하면 더 이상 DOM을 변경할 필요가 없다.(CSR에 비해서 그렇다는 것이고, js로 DOM을 조작할 수 있다.)

이렇게 완료된 페이지가 넘어온다면 어떤 점이 좋을까? CSR에 비해 로딩 속도가 빨라진다. CSR은 초기에 모든 페이지에 필요한 js파일을 가져와야 하고, js파일을 가져온 후에 DOM을 조작해서 변경해주는 것이기 때문에 아무래도 SSR보다는 느릴 수밖에 없다. 하지만 CSR에서도 코드 스플리팅을 해서 각각의 페이지에서 사용되는 js파일만을 가져오게 한다면 그렇게 차이날 것 같지는 않다.

다른 장점으로는 많이 언급되는 SEO 최적화가 있다. 그럴 수 밖에 없는 것이, 모든 정보가 담긴 index.html을 보내주는 것이기 때문에 검색 봇이 해석을 잘 할 수 있는 것이다. CSR의 index.html을 보면 <div id="root"></div> 이렇게 body에 하나의 div만 존재하는 것을 볼 수 있다. 당연히 검색 봇은 해당 html에는 아무 내용이 없구나 하고 넘어갈 것이다. 이것마저 크롬에서는 어느 정도 해결을 해줬다고 한다. 하지만 모든 사용자가 크롬만 사용하는 것이 아니기 때문에...

항상 이론적으로만 알고 있던 것이라서 경험도 해보고 싶고, 초기 렌더링 속도도 줄이고 싶으면서 seo 최적화도 해보고 싶어서 ssr을 초기 메인 페이지에만 도입해보기로 했다. 그 과정을 적어보려고 한다.

처음에 우여곡절이 많았다. 우선 노드 서버를 개설해야 했고, express를 통해 쉽게 서버를 개설할 수 있었다. 여기까지는 괜찮았는데, express에서 클라이언트의 App을 import하는 과정이 문제였다.

App의 import문을 express에서도 읽을 수 있게 하기 위해 바벨을 사용했다. 기존에 클라이언트에서 사용하던 preset-env와 preset-react를 사용해서 jsx ⇒ js, ESM ⇒ CommonJs로 변환했다. 모든 파일이 변환되었고, 실행을 시켰더니 svg를 import할 수 없다는 오류가 발생했다. 여기에서 멘붕이였다.

svg를 당연히 import할 수 없었다. 왜냐하면 svg는 ReactComponent로 사용되고 있으며, 웹팩으로 Svgr을 사용하고 있었기 때문이다. 즉, 웹팩을 사용하지 않으면 불가능한 것이었다. 하지만 아무리 블로그를 찾아봐도 웹팩을 사용하지 않는 것 같았고, 몇번의 삽질 끝에 웹팩은 꼭 써야되겠다는 생각을 하게 되었다. 그러다가 아래의 블로그를 발견했다.

https://www.digitalocean.com/community/tutorials/react-server-side-rendering

정말 내가 원하는 블로그였다. 웹팩에 바벨을 사용하면서, 쓸데 없는 플러그인을 사용하지도 않고 있었다. 블로그를 참고하면서 우리 사이트만의 웹팩 설정을 만들게 되었다. 그 후 빌드를 했고, index.js파일 하나에 트랜스파일링까지 완료된 결과물을 볼 수 있었다.

아, 여기에서 webpack-node-externals을 사용했는데, 의존성이 있는 라이브러리에서 실제로 사용되고 있는 부분만 추출해서 빌드를 해주는 것이라고 해서 사용했다. 실제로 express는 엄청나게 많은 의존성이 있어서 express만 설치하고 빌드해도 용량이 꽤 큰 것으로 알고 있다. 현재 webpack-node-externals 를 사용했을 땐 148KB, 사용하지 않았을 땐 1.1MB가 넘어가는 것으로 보인다. 꼭 사용해야 할 것 같다.

모든 것이 끝난 줄 알았는데, 또 난관이 있었다. 노드 환경에서는 window가 존재하지 않는데, 처음부터 window를 읽는 코드가 존재했다. 그래서 window의 typeof를 검사해서 return하는 식으로 구현했다.

또, 문제가 있었는데 SSR을 하기 위해 필요한 react-dom/server가 Suspense를 지원하지 않는다는 에러 문구였다. 아직 리액트에서 지원하지 않는 것 같다. 그래서 Suspense도 window의 typeof를 검사해서 노드 환경일 때는 Suspense를 동작시키지 않도록 구현했다.

그러고나서 생각해보니 react-router-dom을 사용하고 있는데, 어떻게 주소를 읽어서 어떤 url의 컴포넌트를 호출시키지? 라는 생각이 들었다. 그래서 react-router-dom 사이트에 들어가보니 ssr을 위한 파트가 있었고, 다행히도 StaticRouter가 존재했다. 서버에 요청하는 url의 path를 보고 해당 url에 해당하는 컴포넌트를 호출하는 것이었다. 이는 서버에서만 설정하면 돼서 편했다.

잘 될 것 같았는데... 이번에는 Browser history가 존재하지 않는다는 오류가 발생했다. 이는 BrowserRouter가 App을 감싸고 있는 형태가 아니라서 발생하는 오류였다. App을 읽을 때 history가 존재하지 않기 때문이다. 그래서 App을 감싸주도록 변경했다. 이제 오류가 절대 없을 것이라 생각했고, 잘 동작했다.

하지만 또 문제가 있었다. body에 덕지덕지 style 태그가 붙어 있는 것이었다. 도저히 봐줄 수가 없었고, emotion을 사용하고 있었기에 사이트에 들어가봤다. 다행히 emotion도 ssr을 지원하고 있었다. ssr을 지원하고 있기 때문에 스타일링이 가능한 것이었지만, body에 style을 두지 않고 head에 두는 방법도 지원하고 있었다. body에 두면 nth-child를 사용했을 때 오류가 발생할 수도 있다고 한다. 그렇기도 해서 바로 head로 바꾸는 작업을 했다. 이건 공식 문서가 너무 잘 되어있어서 링크로 대처하겠다.

https://emotion.sh/docs/ssr

이제 모든 SSR이 완료됐다. nginx에 배포 후 proxy를 연결해두고, 서버를 open하기만 하면 될 것 같다. SSR을 하면서 느낀 것은 개념 자체는 어렵지 않은데, node 환경을 처음 접해봐서 그 환경에 적응하는 것이 꽤나 어려웠던 것 같다. 그래도 생각보다 할만했다. 아직 동적으로 데이터를 주입하는 과정은 거치지 않아서 할 수 있는 말인 것 같다.

추가적으로 해야 하는 것은 요청이 들어왔을 때, 사용자를 확인하고 토큰이 유효하다면 사용자의 데이터를 html에 처음부터 담아서 보내주는 것이다. 즉, 동적으로 사용자의 데이터를 담아서 보내줘야 하는데 어떻게 해야할지 감이 오지는 않는다.

아직까지 잘 모르겠는 것들

  • SSR에서 초기에 사용자를 판단하기 위해서는 토큰을 무조건 쿠키에 담을 수 밖에 없는걸까?
  • 코드 스플리팅을 하면 SSR의 장점인 초기 로드 속도도 CSR에서도 충분히 커버할 수 있는 것 같은데, 그럼 SSR을 사용하는 이유는 seo 뿐인걸까?
profile
느려도 꾸준히 발전하려고 노력하는 사람입니다.

0개의 댓글