1. CSS 속성을 통해 뚝뚝 끊기는 애니메이션 개선하기
2. 대부분이 모르는 Next에서 SSG가 작동하는 방식
읽으면 많은 분들에게 무조건 도움이 될거라고 장담할 수 있습니다.
글이 조금 기니 천천히 끝까지 읽어보시는걸 추천드립니다.
요즘 가장 많이 사용되는 프레임워크인 Next.js는 Server Side Rendering과 Static Site Generation이 가능하다는 아주 큰 장점이 있다.
이제는 정말 많은 사람들이 Server Side Rendering이 뭔지, 그리고 Static Site Generation이 뭔지를 알고있지만, 그래도 혹시 모르는 사람이 있을까봐 간단히 설명을 하려고 한다.
SSR에 대해 얘기를 하기 전에, 왜 SSR이 필요한지를 CSR의 장점과 단점을 통해 먼저 알아보자.
CSR은 말 그대로 Client 쪽에서 렌더링이 되는 것
Client Side Rendering이 작동하는 방식을 생각해보면
이라는 과정을 거쳐서 SPA 형태로 서비스가 구동이 된다.
난 처음 CSR을 알게 됐을 때 이건 진짜 기적이다라고 생각을 했다. 이걸 생각해낸 사람은 천재일거라고도 생각을 했다.
그 이유는 되게 간단한데, SPA는 한번 빈 html받아옴 -> js 받아옴 -> JS를 로딩하고 화면을 그림 이라는 과정을 거치고 나면, 그 이후에 페이지 이동시에 새로운 html을 받아오지 않는다.
SPA에서 페이지 이동이 발생하는 것 처럼 보이는건 사실 자바스크립트를 통해 구현이 된 것이라고 보면 된다.
SPA에서의 페이지의 이동은 자바스크립트가 감지를 해서 그 경로에 있어야 하는 데이터를 자바스크립트가 새로 그리는 것이다.
이러한 SPA의 작동방식 덕분에 유저는 한번 앱이 로딩되고 나면 페이지 이동시에 흰화면이 번쩍이는걸 보지 않아도 된다. 다시 생각해도 엄청나다
하지만 이 엄청난 SPA에도 큰 단점이 있었는데, 바로 SEO 측면에서 좋지 않다 라는 것이다. 이 말을 쉽게 말하자면, SPA 형태로 구현된 앱은 구글링이 잘 되지 않을거라는 말이다.
왤까?
CSR의 과정을 다시 살펴보자.
여기서 1번 단계를 보면, CSR은 먼저 빈 html에서 시작을 한다는 것을 알 수 있다. 근데 일반적인 검색 엔진 크롤러는 자바스크립트가 실행되지 않는 환경에서 돌아간다.
그말인즉슨, 1번 단계 이후의 작업이 진행이 되지 않기 때문에, 검색엔진 크롤러가 보는 CSR이 적용된 SPA앱은 그냥 빈 html 파일이라는 의미이다.
그렇기 때문에 CSR이 적용된 SPA앱은 검색엔진에 노출이 잘 되지 않는다
또 다른 큰 단점이 하나가 있는데, js가 로딩이 되기 전에 유저는 빈 화면만 계속 보고 있어야한다는 것이다.
결국 두 단점 모두 CSR의 작동방식으로 인한 어쩔 수 없는 문제들이다.
세상에 존재하는 수많은 천재 개발자들이 이 문제를 가만히 뒀을리가 없다. 이제 CSR의 단점을 해결하는 SSR에 대해 알아보자.
SSR은 말 그대로 Server 쪽에서 렌더링이 되는 것
여담이지만, 많은 프로그래밍 용어들은 이름을 통해 어느정도 유추가 가능합니다. 처음 들어보는 용어라면 이름을 다시 한번 생각해보시면 힌트를 얻을 수 있습니다.
정말 반복적이지만, 그만큼 중요한 내용이기 때문에 다시 한번 CSR의 작동 방식을 보자.
그리고 앞서 얘기했던 CSR의 단점들에 대해서 생각을 해보면, 결국 모든 문제들의 원인은 1번, 빈 html을 받아오는 것이라는 사실을 알 수 있다.
그럼 이 1번 과정을 고치면 CSR의 단점을 어느정도 개선할 수 있다는 의미이기도 한데, 그 1번 과정을 개선한 방식이 SSR, Server Side Rendering이다.
Server Side Rendering이 작동하는 과정은
서버
1. 요청에 맞는 html을 서버에서 생성
2. 생성된 html을 클라이언트로 전달
브라우저
3. 생성된 html을 받아옴
4. JS파일을 받아옴
5. JS를 로딩하고 화면을 그림
6. 짜잔
이렇게 여섯단계를 거치는데, 자세히 보면 3~6번이 되게 익숙하다는 것을 알 수 있다. 그 이유는 3번부터 6번은 CSR의 작동방식이랑 똑같기 때문이다.
Server Side Rendering은 결국 Client Side Rendering이 동작하기 전에 사전과정을 거치는 것
사진처럼 유저가 어떤 페이지에 접속을 하면, Next가 HTML파일을 만들고, 생성된 HTML을 유저에게 전달을 한다.
CSR과 SSR의 가장 큰 차이점은
CSR은 빈 HTML에서 시작
SSR은 데이터가 있는 HTML에서 시작
그렇기에 SSR은 CSR의 단점을 보완할 수 있는 것이다.
데이터가 있는 HTML에서 시작하기에 검색 엔진에 노출이 될 수 있는 것이고
데이터가 있는 HTML에서 시작하기에 유저가 빈 화면을 보면서 기다리지 않아도 되는 것이다.
이것만 보더라도 Next을 이용할 이유는 충분하다. 하지만 더 좋은게 있다.
이렇게 보니까 CSR의 단점을 보완한 SSR이 완벽한 것 같고, SSR보다 좋은건 없을 것 같아보이기까지 한다.
하지만 역시 그럴리가 없다.
SSR은 CSR의 단점인 빈 HTML에서 시작하는 과정을 개선하기 위해 먼저 서버에서 데이터가 담긴 HTML파일을 생성한다는 것은 이제 알거라고 생각한다.
근데 이 데이터가 담긴 HTML파일을 생성하는 과정이 오래 걸린다면? 뭔가 조금 이상해질 것 같지 않은가?
다시 SSR의 과정을 살펴보면서, SSR의 잠재적 문제를 같이 알아보자.
ㅤ
서버에서 HTML을 생성하기 위해 필요한 데이터가 엄청 많다고 가정을 해보자.
그럼 Server Side Rendering이 작동하는 과정은
서버
1. 요청에 맞는 html을 서버에서 생성 (데이터가 많아서 생성하는데 10초가 걸림)
2. 생성된 html을 (10초 뒤에) 클라이언트로 전달
브라우저
3. 생성된 html을 (10초 뒤에) 받아옴
4. (10초 뒤에) JS파일을 받아옴
5. (10초 뒤에) JS를 로딩하고 화면을 그림
6. (10초 뒤에) 짜잔
이렇게 변하게 된다.
이런 과정을 거치게되면 어떻게 될까?
그럼 이런 의문이 들 수도 있다.
CSR도 어차피 10초 기다려야하는건 마찬가지 아닌가?
맞다. CSR도 10초라는 시간이 어딘가에 소요되는건 마찬가지다. 하지만 10초가 드는 타이밍이 다르다.
똑같은 가정을 하고 CSR에서의 과정을 살펴보자.
10초를 기다리는건 똑같지만, 어딘가 다르다는 것을 알 수 있다. 그리고 이 차이는 유저에게 아주 크게 다가온다.
한번 상상을 해보자
크롬을 켜서 데이터 로딩이 10초가 걸리는 이상한 유튜브 주소를 입력을 했다고 가정을 해보자.
CSR로 구현된 유튜브:
페이지 이동은 바로 됐지만, 데이터를 받아오는게 오래 걸려서 흰 화면만 보는중.
10초가 지나서야 데이터가 로딩이 되고, 화면이 다 그려짐
대부분의 사람들은 이 상황을 맞닥뜨리면 로딩중인가보다~ 하고 기다린다.
페이지 이동이 됐기 때문에 기다리면 되겠지 라고 생각하게 된다.
SSR로 구현된 유튜브:
분명 유튜브 주소를 입력했는데 페이지 이동이 안돼서 브라우저 초기 화면에서 멈춤.
10초동안 먹통인 상태에서 기다리다가 갑자기 다 그려진 화면이 나타남.
대부분의 사람들은 이 상황을 맞닥뜨리면
이런 반응을 보이면서 페이지를 끈다.
ㅤ
ㅤ
SSR의 또 다른 단점으로는 서버에 부하가 간다는 것이다.
서버 비용 측면에서도 효율적이지 못할거고, 동시 접속자가 많은 경우에는 서버가 터질 위험도 있을 것이다.
이러한 단점들 때문에 SSR도 필요한 상황에서만 사용하는 것이 가장 좋다는 사실을 알 수 있다.
ㅤㅤ
ㅤ
하지만 천재 개발자들이 이것 역시 그냥 지켜보고 있을리가 없다.
SSR의 단점을 생각해보면, 유저가 요청을 했을 때 서버에서 HTML을 생성하기 시작한다는 것이다. 그렇기에 유저가 요청을 하면 HTML 파일을 받아오기 까지 시간이 걸린다는 점인데,
만약 서버에서 이 HTML 파일들을 미리 만들어놓는다면?
CSR의 단점인 빈 HTML의 문제도 해결이 될 것 같고
SSR의 단점인 HTML 생성시간의 문제도 해결이 될 것 같지 않은가?
ㅤㅤ
그래서 나온게 Static Site Generation, SSG이다.
SSG는 Static Site를 빌드타임에 만들어놓는 것
CSR의 단점은 빈 HTML이었고, SSR의 단점은 긴 지연시간이었다.
SSG는 쉽게 생각해서 개발자가 개발할 때 이러이런 페이지들이 데이터를 받아오는데 오래 걸릴 것 같으니, 배포할 때 이 데이터들을 다 받아와서 HTML파일을 미리 생성을 하고, 추후에 유저가 이 페이지에 접속하려 하면 그냥 미리 만들어진 HTML파일을 전송하는 것이다.
이렇게 되면 유저는 빈 HTML파일을 받지 않아도 되고, HTML 파일이 만들어질 때 까지 기다릴 필요도 없다.
베스트 시나리오
물론 SSG는 데이터가 최신이 아닐수도 있다는 단점이 있는데, 이건 ISR, Incremental Site Regeneration을 통해서 해결이 가능하다. 하지만 이 글에서는 다루지 않을 예정이니 찾아보는걸 추천한다.
간단히 설명을 하자면 일정 시간마다 새로 HTML파일을 미리 만들어두는 것이다. 그러면 데이터가 바뀌더라도 다시 바뀐 데이터로 HTML파일이 새로 생길 것이니 문제 해결
ㅤㅤ
ㅤㅤㅤ
ㅤ
이제 그럼 내가 직접 100% CSR 기반 앱을 리팩토링 과정에서 CSR/SSG로 적절히 최적화하면서 겪은 시행착오와, 그 과정에서 알게된 전혀 몰랐던 사실에 대해 얘기해보려 한다.
나 뿐만이 아니라 주위 프론트엔드 개발자들 대부분이 잘못 알고 있던 내용이다. 자세히 천천히 읽으면 도움이 될 거라고 확신한다.
우선 리팩토링 이전에 순도 100% CSR기반인 투두몰 서비스를 살펴보면서 시작을 해보자.
투두몰 서비스에서 판매하는 상품을 자세히 보기 위해 클릭을 했더니
이런 과정을 거쳐서 상품 디테일 페이지로 이동이 되는 것을 알 수 있다.
사실 이 페이지가 로딩이 오래 걸리지도 않고, 베타 테스트 유저들에게도 한번도 피드백이 들어오지 않았던 페이지이다. 그래서 사실 리팩토링을 굳~~~이 할 필요는 없었던 페이지이다.
하지만 내가 알고 있는 지식으로 더 좋게 만들 수 있는데 그걸 하지 않을 이유는 전혀 없다고 생각했다.
일단 리팩토링을 하려면 계획을 짜야한다.
뭘, 어떻게 개선할 것이고, 원하는 목표가 무엇인지를 생각해야한다.
무엇을 개선할 것인가
새로운 페이지로 이동하면서 데이터를 새로 받아오고, 유저가 불필요한 로딩을 겪어야하는 문제
어떻게 개선할 것인가
상품 소개 페이지는 데이터의 변경이 일어날 일이 없기 때문에 Static Site Generation을 적용하여 미리 페이지들을 빌드타임에 만들어 놓는다
원하는 목표가 무엇인가
유저가 상품을 클릭했을 때 최소한의 지연시간으로 원하는 정보를 볼 수 있도록 한다
계획은 다 짰으니 이대로 진행해보자.
어떤 특정 페이지에 SSG를 적용하기 위해선 getStaticProps 함수를 사용하면 되는데, 상품 세부 페이지는 모든 상품들에 공통적으로 사용하기 위해서 Dynamic Route를 이용했다.
따라서 어떤 상품들에 SSG를 적용할건지 Next에게 알려주기 위해 getStaticPaths 함수도 같이 사용했다.
SSG는 개발 환경에서 적용되지 않으니 배포 환경에서 테스트를 해보자.
뭔가 벌써 엄청 빨라진게 체감이 된다.
CSR로 구현했던 앱에서는 페이지 이동이 되고, 로딩 스피너가 돌다가, 데이터를 다 가져오면 화면이 그려졌는데, 그게 평균 1초정도 걸렸다.
반면에 SSG를 적용해서 리팩토링한 앱에서는 페이지 이동이 되자마자 화면이 바로 나타나고, 이미지같은게 최종적으로 로딩되면서 끝이난다. 이미지 로딩 시간까지 포함하면 약 0.5초정도 걸렸지만, 유저에게 전체적으로 보이는 정보들은 거의 바로 나타나는 것을 확인할 수 있다.
이미 충분한 개선이 된 것 같다. 생각보다 쉬웠고, 역시 Next는 짱이라고 생각했다.
다 끝났다고 생각하고 배포를 한 다음, QA를 진행하는 과정에서 문제를 발견했다.
혹시 눈치챘는가?
자세히보면
이런 현상이 발생하는 경우가 있었다.
근데 이상한게, 매번 그런 것도 아니고, 모든 상품이 다 그런것도 아니었다. 그냥 말 그대로 랜덤으로 나타나는 현상이었다. 심지어 어떨때는 거의 2초 가까이 걸려서 CSR보다 느린 경우도 있었다.
여기서부터 조금 이상함을 느끼기 시작했다. 그 당시 내 생각 과정을 한번 나열해보겠다.
이런 생각 과정을 거치면서 갑자기 혼란스러워졌다. 내가 알던게 사실은 다 잘못된 정보였나 싶었다.
그래서 네트워크 탭을 켜서 한번 살펴봤다.
응? 나 SSG적용한거 아니었나? 왜 HTML파일을 안받아오고 JSON 데이터를 받아오지?
혹시라도 실수로 getStaticProps대신 getServerSideProps를 이용했나 해서 확인해봤는데 아니었다. 분명 SSG를 적용한게 맞았는데, 동작하는건 SSR처럼 동작하고 있었다.
랜덤으로 느리게 로딩되는 이유는 뭐고, html을 받아온다 그랬는데 왜 html파일은 안보이고 json데이터가 오는건지가 너무 이해가 안됐고 궁금해서 미칠 지경이었다.
그러다 너무 답답해서 그냥 무의식중에 새로고침을 했는데, 갑자기 이번엔 html파일을 받아오는 것을 발견했다.
? ? ? ? ? ? ? ? ? ? ? ?
아니 얘 뭐야 왜이래 하면서 어떨 때는 json을, 어떨때는 html파일을 받아오는 것을 발견했고, 이거 못찾아내면 나 잠 안잔다 라는 생각으로 몇시간동안 계속 검색을 해봤다.
근데 SSG의 동작 원리를 찾아보면 대부분의 글들은
SSG는 빌드타임에 HTML파일을 미리 생성하고, 추후에 요청이 오면 미리 만들어놓은 HTML을 전송한다
라고 SSG를 설명하고 있었다. 아무데도 json파일을 언급하지 않았고, 심지어 내가 정말 존경하는 코딩앙마님의 Next 강좌에도 html에 관한 언급만 있었고, html파일이 받아와지지 않았는데 페이지 이동이 발생하는 상황에서도 별다른 언급이 없으셨다.
근데 이 영상을 보면서 뭔가 이상한 점을 깨달았다.
이 점을 발견하고 동일한 방식으로 나도 테스트를 해봤더니 동일 현상이 발생하는 것을 발견했다.
클릭 페이지 이동을 통해 SSG가 적용된 페이지로 이동 -> JSON 데이터 받아옴
새로고침 또는 url 입력을 통해 SSG가 적용된 페이지로 이동 -> HTML 파일 받아옴
이걸 발견하고 나니까 뭔가 이상하고 이해가 안되던 점들이 하나 둘 맞춰지기 시작했다.
앞서 언급했던 내 생각 과정을 다시 보자.
나는 SSG는 무조건 HTML파일을 반환하는 거라고 생각을 했기 때문에 2번에서 파일 변경이 발생하나? 라는 의문을 가지게 됐고, 3번에서 SPA가 아닌 MPA로 작동하나? 라는 말도 안되는 생각까지 하게 됐었다.
그러다 문득 이런 생각이 들었다.
이미 HTML 파일이 있는 상황에서는 JSON 데이터를 받아오고, HTML이 없는 상황일 때는 HTML파일을 받아오는건가?
유레카!
뭔가 무조건 정답인 것 같았고, 이걸 증명할 방법만 찾으면 된다고 생각했다.
바로 VSCODE를 키고 SSG 적용했던 프로젝트를 빌드해봤더니
ㅤ
ㅤ
Static Site Generation은 이름과는 다르게 HTML 파일만 생성하는 것이 사실 아니었다.
SSG는 빌드 과정에서
1. 데이터 기반으로 다 그려놓은 HTML파일
2. 미리 받아온 json 형식의 데이터
두개를 생성하는 것이었다
ㅤ
근데 알고보니...
공식 문서에 이미 적혀있던 내용이었다. 적힌 내용을 간단하게만 설명을 하면
Client-Site routing을 통해 SSG가 적용된 페이지로 이동하면, 빌드타임에 미리 만들어진 JSON파일을 받아와서 SSR의 prop 전달처럼 작동한다.
ㅤ
ㅤ
이제 SSG의 진짜 작동 방식에 대해서도 이해를 했으니, 최적화 경험에 대해 마저 얘기를 하려한다.
결국 SSG도 Client-Side routing을 통해 접근하면 Json 데이터를 받아오는 과정이 필요하다는 사실을 알게 됐다.
비록 백엔드로부터 데이터를 받아오는게 아닌, 프론트전용 백엔드 서버에서 빌드타임에 미리 만들어진 json 데이터를 가져오는거라지만, 결국 데이터를 받아오는 과정에서 딜레이가 발생한다는건데... 그럼 SSG의 장점이 크게 없는 것 처럼 느껴졌다.
하지만 역시나 내가 부족했기에 발생한 문제였고, 역시나 방법은 있었다.
Next 프레임워크에서는 Client-side routing을 하기 위해선 방법이 크게 두가지가 있다.
나는 개인적으로 1번을 그동안 계속 사용했었다. Link 태그는 결국 내부적으로 a 태그를 생성하는데, a 태그는 css를 건드려서 귀찮았다. 반면에 router.push는 핸들러에 등록만 해놓으면 되다보니 편하게 느껴졌다.
하지만 Next Link 태그에는 숨겨진 꿀 기능이 있었는데... 바로 prefetch이다.
Prefetch 기능은 유저의 화면에 보이는 Link 태그들, 다른 말로 잠재적으로 눌릴 수 있는 Link 태그들에 대해서 미리 페이지 이동시에 필요한 데이터를 가져온다.
onClick으로 구현된 페이지 라우팅을 전부 Link 태그로 변경을 하고, Prefetch를 적용 해보았다.
자세히보면 클릭은 하나도 하지 않고 스크롤만 하고 있는데, 화면에 들어온 모든 상품들의 데이터가 Prefetch되는 것을 확인할 수 있다.
그럼 장점이 뭐냐고?
이전에 SSG를 적용했음에도 미리 만들어진 JSON 데이터를 가져오는 과정에서 딜레이가 발생하는 것을 확인했었고, 그 딜레이 때문에 오히려 CSR만 적용된 서비스보다 사용 경험이 좋지 않은 것을 확인할 수 있었다.
하지만 Link태그를 이용해서 prefetch를 다 해오면 JSON 데이터를 가져오는 과정에서 발생하는 딜레이 문제를 해결할 수 있었고, 결과적으로 최고의 결과를 얻을 수 있었다.
리팩토링 전부터 리팩토링 후, SSG 문제 해결 후 까지의 결과를 한번 보자.
리팩토링 전, 100% CSR
페이지 이동 후 로딩 스피너가 돌아가다가 화면이 그려진다
리팩토링 후, SSG 적용, prefetch 미적용
클릭을 했음에도 데이터를 받아오기 전까지는 페이지 이동이 되지 않다가 화면이 나타난다
리팩토링 후, SSG 적용, prefetch 적용
유저는 클릭과 동시에 페이지 이동을 하고, 이동한 페이지에는 미리 생성된 화면을 바로 볼 수 있게 됐다.
무작정 알고있던 지식으로 리팩토링을 통해 개선을 하고 싶어서 시작했는데, 중간에 내가 잘 모르고있던 정보, 잘못 알고있던 정보, 해결 방법들을 다 배울 수 있었다.
SSG가 빌드타임에 HTML 파일만 만드는게 아니라 JSON 파일도 만든다는 것은 이번 기회가 아니었다면 절대 알 수 없었을 것 같아 더욱 소중한 경험인 것 같다.
내가 잘못된 지식으로 인해 해맸기 때문에, 다른 분들은 이런 실수를 하지 않았으면 하는 마음에 글을 작성하기로 마음을 먹었다.
사실 리팩토링 시리즈를 작성하기로 마음 먹은 가장 큰 이유가 바로 이 발견 때문이었다.
정말정말로 긴 글이었는데 다 읽으신 분이 계신다면 정말로 감사하다는 말씀을 드리고 싶습니다.
꼭 도움이 되셨기를 바랍니다!
SSG, CSG에 대해서 알아보러 왔다가 더 많은 정보 얻고 갑니다!