Rendering, 조건부 렌더링

huni_·2022년 7월 12일
0

브라우저

목록 보기
1/2
post-thumbnail

브라우저에 대해서 알아보자

웹 페이지를 그리는 다양한 렌더링 방식
Rendering Pattern들에 대해 알아봅시다.

렌더링은 브라우저 화면에 웹 페이지를 그리는 것입니다.
1. 먼저 유저가 브라우저를 통해 서버에 요청을 보냅니다.
2. 서버는 이에 응답으로 HTML, CSS, JavaScript를 보내줍니다.
3. 브라우저는 이를 해석해서 웹 페이지를 그립니다.

브라우저가 해석 할 수 있는 언어는 HTML, CSS, JS 이 세가지입니다.

렌더링을 수행하는 브라우저의 프로세스를 렌더러 프로세스라고 합니다.

  1. DOM Tree 생성
  2. CSSOM Tree 생성
  3. 1번 2번을 결합하여 Render Tree를 만든다.
  • 1,2,3번 과정 === Construction 과정
  1. Render Tree를 배치하는 Layout 과정
  2. 화면에 직접 그리는 Paint 과정
  • 4,5번 과정 === Operation 과정

CRP(Critical Rendering Path) 중요 렌더링 경로

브라우저가 화면에 HTML, CSS, JS를 그리는 과정(절차)를 라고 합니다.

CRP를 알아야하는 이유는 우리가 작성한 코드가 렌더링에 직접적으로 영향을 미치기 때문입니다.
예를 들어 특정 요소를 안 보이게 처리하고 싶을 때 display: none을 사용한다면 Tree들을 만드는 Construction 과정부터 Operation과정까지 다시 진행하기 때문에 렌더링 과정에 있어서 비효율성을 초래합니다.
따라서 opacityvisibility속성을 사용하는 것이 더 효율적입니다.

이전 예시에서 알 수 있는 사실은 렌더링은 UX(User Experience)와 DX(Developer Experience)에 직접적으로 영향을 미친다는 것입니다. 개발자가 작성하는 코드에 따라 렌더링의 속도가 달라질 수 있고 개발자는 어플리케이션의 첫 사용차(First User)이기도 하므로 DX에 영향을 미칩니다. 또한 개발자가 채택한 렌더링 패턴은 최초 유저인 개발자에게 영항을 미치고 이는 UX에 영향을 미칩니다.

따라서 우리는 올바른 렌더링 방법을 채택하는 것이 중요합니다.
그렇다면 어떤 렌더링 패턴이 올바른 것일까요? 또, 올바르다는 기준은 무엇일까요?
올바른 렌더링 패턴이란, 우리의 어플리케이션의 특성에 맞고, UXDX를 모두 높여주는 패턴을 말합니다.
어플리케이션을 만들 때 고려해야 할 요소는 검색엔진 최적화, Web Performance등이 있습니다. 검색엔진 최적화는 SSR(Server Side Rendering) 해결할 수 있습니다.

Web Performance에서 고려할 점은 구글에서 제안하는 웹 사용자 경험에 지표인 Core Web Vitals입니다.

  • TTFB(Time To First Byte): 웹 페이지 컨텐츠의 첫 byte가 브라우저에 도달하는데 걸리는 시간
  • FCP(First Contentful Paint): 초기 DOM 컨텐츠를 렌더링하는데 걸리는 시간
  • LCP(Largest Contentful Paint): 가장 큰 컨텐츠(보통 중요한 컨텐츠일수록 크기가 큼)를 페이지에 렌더링하는데 걸리는 시간
  • TTI(Time To Interactive): 컨텐츠와 상호작용까지의 시간(CSR에서는 TTV(Time To View)와 TTI가 같고 SSR에서는 TTV와 TTI가 다르다.)
  • CLS(Cumulative Layout Shift): 누적 레이아웃 이동으로 사용자가 예상치 못한 레이아웃 이동을 경험하는 빈도를 수량화. 시각적 안정성을 측정할 때 중요한 사용자 중심 메트릭
  • FID(First Input Delay): 사용자가 페이지와 처음 상호 작용할 때(버튼 클릭 등) 부터 해당 상호 작용에 대한 응답으로 브라우저가 실제로 이벤트 핸들러 처리를 시작하기까지의 시간을 축적하는 지표.

Core Web Vitals는 '어떤 방식으로 렌더링을 하냐'에 따라 수치가 달라집니다.

'어떤 방식으로 렌더링을 하냐'는 어떤 렌더링 패턴으로 페이지를 렌더링 할 것인가로 귀결됩니다.


CSR (Client Side Rendering)

React 이후의 웹구조. 최근 웹트렌드인 SPA(Single-Page-Application)이다. 물론 CSR과 SPA가 같은 개념은 아님 자바스크립트가 애플리케이션 구현에 핵심적인 역할을 담당하고 있음.

CSR방식은 최초 요청시에 HTML을 비롯해 CSS, Javascript 등 각종 리소스를 받아온다.
이후에는 서버에 데이터만 요청하고, 자바스크립트로 뷰를 컨드롤한다.
당연히 초기 요청 때 SSR보다 많은 리소스를 요청하기 때문에, 렌더링은 속도는 SSR이 더 빠르다.
하지만 이후 다른 페이지로의 이동시에는 SSR보다 빠른 페이지 전환 속도와 더 나은 사용자 경험을 제공한다.
만약 인터넷 속도가 엄청 느리다면, 유저는 제대로된 화며을 한참 후에나 만나볼 수 있을 것이다. 일단 처음 받게될 HTML은 빈 페이지일 테니까.

👍 장점

  • 첫 로딩만 기다리면, 동적으로 빠르게 렌더링이 되기 때문에 사용자 경험(UX)이 좋다.
  • 서버에게 요청하는 횟수가 훨씬 적기 때문에 서버의 부담이 덜하다.

👎 단점

  • 모든 스크립트 파일이 로드될 때까지 기다려야 한다.
    • 리소스를 청크(Chunk) 단위로 묶어서 요청할 때만 다운받게 하는 방식으로 완화시킬 수 있지만 완벽히 해결할 수는 없다.
  • 검색엔진의 검색 봇이 크롤링을 하는데 어려움을 겪기 때문에 검색엔진 최적화(Search Engine Optimization)의 문제가 있다.
    • 구글 봇의 경우는 JS를 지원하지만, 다른 검색엔진의 경우 그렇지 않기 때문에 문제가 된다.

Core Web Vitals를 기준으로 렌더링 패턴들을 봤습니다.

CSR의 렌더링 과정은 먼저 유저의 요청에 의해 브라우저가 프론트엔드 서버로 HTML을 요청합니다. 서버는 빌드 타임에 미리 생성해 둔 HTML을 응답합니다. 이 HTML에는 로더나 skeleton UI가 들어 있습니다.

그 후에 head 태그에 정의된 CSS와 같은 렌더링 차단 리소스(다운로드되기 전까지 렌더링을 막음)를 다운 받는데, 보통 이런 리소스는 브라우저에 캐시 처리합니다.

다음으로 body 태그의 마지막 부분에 위치한 React 앱이 들어있는 bundle을 프론트엔드 서버에 요청합니다.
bundle을 body태그의 가장 아래에 위치시키는 이유는 자바스크립트는 문서를 파싱하다가 JavaScript를 만나면 파싱을 중지하고 JavaScript 엔진에게 권한을 넘겨서 JS를 실행하기 때문입니다.(Parse Blocking Resource)

React 앱이 실행되면 이제 컨텐츠(데이터)를 받아오기 위해 API 서버로 요청을 보내고 응답을 받아서 유저에게 컨텐츠를 보여줍니다.

위 내용을 정리하면 유저는 애래와 같은 요청을 기다려야 합니다.
1. HTML 파일을 요청해서 응답을 받고
2. JavaScript 번들을 요청해서 응답을 받고
3. 번들을 실행하고
4. API 서버로부터 응답을 받고 데이터를 렌더링

CSR을 Core Web Vitals 관점으로 보겠습니다. 우선 오른쪽 아래에 표시된 파란색 표시는 브라우저에서 일어나는 과정을, 빨간색 표시는 서버에서 일어난 과정을 뜻합니다. 또한 Network 부분과 Main Thread 부분을 나눴습니다. (Main Thread는 브라우저의 렌더러 프로세스의 Main Thread를 말합니다.)
유저가 HTML을 요청하고 서버가 이에 응답하여 브라우저에 컨텐츠의 첫 byte가 도달하는 순간을 TTFB라고 합니다. 이후에는 HTML을 파싱하고 body태그 맨 아래에 script 태그에 도달하면 bundle.js파일을 서버에서 받아오기 위해 요청을 보냅니다.
이 때 브라우저에서는 Operation과정이 수행되고 bundle.js파일을 서버에서 받아오기 위해 요청을 보냅니다.
이 때 브라우저에서는 Operation 과정이 수행되고 bundle을 가져오면 이제 JavaScript를 Evaluation하면서 다시 Operation을 실행합니다.
이 과정을 마치면 HTML에 JavaScript가 연결되어서 유저가 버튼을 클릭하면 연결된 로직이 수행됩니다. (TTI)

이제 서버로부터 데이터를 받아오기 위해 /api/buildings라는 엔드포인트에 요청을 보내고, 데이터를 받아옵니다.
데이터를 받아오면 브라우저는 바뀐 부분을 다시 그리는데 이 과정을 hydration이라고 합니다.

hydrate수분을 공급하다라는 뜻을 가지고 있습니다. 왼쪽 브라우저에는 skeleton UI가 있고 오른쪽 브라우저에는 빌딩 데이터가 채워져 있습니다.
hydration이란, API 요청을 통해 데이터라는 수분을 페이지에 공급해주는 것이라고 할 수 있습니다.

hydration

페이지가 브라우저에 로드되고 자바스크립트 코드가 실행되면서 페이지가 인터렉티브하게 동작하는 상태가 되는 과정


SSG (Static Site Generation)

정적 페이지 생성 방식으로 90년대 많이 사용하던 방식으로 현재는 변경되지 않는 웹 페이지의 최적화에 쓰임

순서

  1. 개발자가 HTML, CSS 파일 묶음을 생성(정적파일)해서 웹서버를 생성하여 거기에 묶음(정적파일)을 업로드함. 클라우드에 올려둠
  2. 클라이언트에서 HTTP GET 요청을 보내면 서버 내부에서 HTML, CSS, 이미지 파일 등 정적파일 등을 불러와서 응답함. 따로 HTML을 서버에서 생성하는 것이 아닌 올려둔 것 그대로 보내줌
  3. 서버로부터 받은 정적파일들을 클라이언트에서는 받아서 이 파일을 가지고 클라이언트 브라우저 내에서 DOM 등을 생상해 브라우저에 화면을 그림

장점

웹서버가 가벼워짐. 따로 빌드를 하지 않고 요청만 처리하면 됨

단점

HTML에 변경사항이 생길 경우 다시 빌드해서 올려야되고 사용자 요청에 따라 데이터가 변화하면 모든 경우의 수를 다 HTML 파일로 만들어서 올려야 됨.


SSR (Server Side Rendering)

SSG의 단점을 개선시켜서 동적인 페이지 생성을 하기 위해서 등장함. 클라이언트의 요청에 따라 HTML 파일을 동적으로 생성해서 응답해줌.

순서

  1. 개발자가 빌드해서 서버에 배포를 함
  2. 클라이언트가 서버의 특정 URL로 HTTP GET 요청을 보내면 서버의 해당 URL과 연결되어 있는 HTML 파일 생성 로직을 실행해서 HTML파일 (CSS, JS포함)을 클라이언트에게 반환함
  3. 클라이언트에서는 넘겨 받은 HTML 파일을 해석 후 브라우저 화면에 렌더링

SSR에선 브라우저가 페이지를 요청할 때마다 해당 페이지에 관련된 HTML, CSS, JS 파일 및 데이터를 받아와서 렌더링을 시킨다.


과거 그리고 현재도 그렇지만, 많은 웹사이트들은 페이지를 이동할 때마다 서버에 새로운 페이지에 대한 요청을 하는 방식을 택하고 있다.
이 방식이 SSR 이다. 서버에서 렌더링을 마치고, Data가 결합된 HTML파일을 내려주는 방식이다. 새로운 페이지로 이동할 때마다 서버에 요청하여 페이지를 받아야 하기 때문에, 받아오는 시간동안 깜빡거리는 현상을 마주할 수 있다.
한편, SPA(Single Page Application) 기법이 대두되면서 CSR방식이 각광받기 시작했다.

👍 장점

  • 초기 로딩 속도가 빠르기 때문에 사용자가 컨텐츠를 빨리 볼 수 있다.
  • JS를 이용한 렌더링이 아니기 때문에 검색엔진 최적화가 가능하다.

👎 단점

  • 매번 페이지를 요청할 때마다 새로고침 되기 때문에 사용자 경험이 SPA에 비해서 좋지 않다.
  • 서버에 매번 요청을 하기 때문에 서버의 부하가 커진다.

다음으로 SSR(Server Side Rendering) 입니다. 유저가 프론트엔드 서버에 HTML 파일을 요청하면 서버는 API 서버에 요청을 보내서 데이터를 받어서 컨텐츠를 채우고 완성된 HTML 파일을 클라이언트에게 보내줍니다.

기존 React 앱이 실행된 후에 보내던 API 요청을 서버에서 미리 보내는 것입니다. 따라서 유저는 한번의 요청만으로 보고 싶은 컨텐츠를 바고 보게 됩니다.

SSR을 Core Web Vitals 관점으로 보겠습니다. SSR이 이루어진 후 첫 바이트가 유저에게 도착하고, 유저에게 도착한 HTML은 컨텐츠를 포함하기 때문에 그 HTML을 렌더라는 순간 FCP(First Contentful Paint), LCP(Largest Contentful Paint) 가 동시에 발생합니다. 마지막으로 JS를 로딩하면 유저가 인터렉션 가능한 페이지가 되므로 TTI는 마지막에 일어납니다.

여러분의 컨텐츠가 빈번하게 바뀐다면 CSR과 SSR을 같이 활용할 수 있는데, SSR을 마친 뒤에 hydration과정이 추가됩니다. hydration과정에는 react-query나 swr같은 data-fetching 라이브러리를 주로 활용합니다.

React에서는 SSR을 구현하기 위해 React 프레임워크인 Next.js를 사용합니다.

Next.js는 기본적으로 모든 페이지를 pre-render(미리 렌더링)합니다.
Client Side JavaScript가 페이지의 HTML을 다 그리는 것이 아니라 미리 각 페이지를 위한 HTML을 생성합니다.

각각 생성된 HTML 페이지에는 최소한의 자바스크립트가 연결되고 Client Side에서는 이 자바스크립트로 hydration과정이 일어납니다.

Next.js의 pre-rendering 형태는 SSG(Static Site Generation)SSR(Server Side Rendering)으로 나뉘며 각 페이지별로 다르게 적용할 수 있습니다.

예를 들어 Page A는 SSG로, Page B는 SSR로, Page C는 CSR + SSR로, Page D는 CSR로 렌더링 할 수 있습니다.

Next.js의 pre-rendering 형태 중 SSG에 대해서 먼저 알아보겠습니다. SSG는 빌드 타임에 HTML 페이지를 생성합니다.
데이터를 받아오는 API 요청 또한 빌드 타임에만 실행합니다. 따라서 API 서버의 부하가 줄어듭니다. 또한, 만들어진 컨텐츠를 CDN에 캐시함으로써 사용자에게 컨텐츠를 매우 빠르게 전달할 수 있습니다.
단점으로는 빌드 타임에 컨텐츠를 만들기 때문에 데이터에 취약합니다.

SSGCSR을 곁들이면 변하지 않는 부분은 변하지 않게 고정하고, 유동적으로 데이터를 fetching 해야하는 부분은 CSR로 처리할 수 있습니다. (hydration) 또한, 두 렌더링 방식을 합쳤기 때문에 장점 또한 합쳐집니다.

ISR(Incremental Static ReGeneration)은 SSG에서 revalidation 옵션을 추가한 것입니다. 빌드 타임에 HTML을 생성하는 것은 동일하나 일정 주기마다 데이터의 최신 여부를 검사해서 업데이트된 데이터로 페이지를 다시 생성합니다.

마찬가지로 CSR을 곁들이면 바뀌긴 하는데 자주 변하지 않는 데이터는 ISR의 렌더링 방법을, 동적으로 계속 바뀌는 데이터들은 CSR로 처리할 수 있습니다. 예를 들어 블로그 글(자주 안 바뀜)과 댓글(자주 바뀜)을 이 방식으로 처리할 수 있습니다.

마찬가지로 SSG 또한 Core Web Vitals 관점으로 보겠습니다.
SSG로 만들어진 사이트이기 때문에 빌드 타임에 이미 HTML이 완성되어 있습니다. 따라서 서버에서는 마땅히 할 일이 없기 때문에 HTML을 바로 주고 클라이언트는 그 HTML을 바로 렌더링합니다. CSR을 곁들인다면 뒤에 hydration 과정이 추가되겠죠.

다시 SSR입니다. SSR이야기를 다시 꺼낸 이유는 Next.js SSR의 Page 단위 data fetching에 있습니다.
getServerSideProps라는 함수는 API서버로부터 데이터를 가져온 후에 해당 데이터를 사용한 props를 리턴해주면 페이지 컴포넌트는 그 props를 바로 사용할 수 있습니다. 만약 CSR에서 SSR로 마이그레이션한다고 하면 기존 클라이언트 사이드의 data fetching 로직을 getServerSideProps로 옮기면 됩니다.
하지만 여러 컴포넌트에서 여러개의 데이터를 fetching한다면 처리가 어려워집니다. 또한 props drilling을 하면 관심사의 분리가 되지 않으므로 (위 예제에서 A 컴포넌트는 data에 관심이 없음) 좋지 않은 해결 방법입니다.

이를 해결하는 것이 RSC(React Server Component)입니다. RSC는 컴포넌트 개별 단위별로 서버에서 data fetching을 할 수 있으며 서버의 리소스에 자유롭게 접근할 수 있습니다. 또한 서버컴포넌트는 클라이언트로 전송되는 번들에 포함되지 않으며, 클라이언트의 상태를 유지하며 refetch할 수 있습니다.

결국 이런 렌더링 패턴들을 알아야하는 이유는 렌더링 패턴이 UX와 DX에 직접적으로 연관이 있기 때문입니다.

우리는 흔히 이런 개발 사이클을 돌곤 합니다. 엄청난 아이디어가 떠오르고, 개발을 한 후 배포를 합니다.

그렇다면 이 과정에서 렌더링 패턴은 언제 고려되어야 할까요?

정답은 없습니다.
어디서든지 렌더링 패턴을 고려할 수 있지만, 어플리케이션이 어떤 컨텐츠를 렌더링하냐에 따라 적용할 패턴이 바뀔 것 같습니다.

저는 특정 패턴이 좋고 나쁘고를 주장하는 것이 아닙니다.
단지 웹을 렌더링하는 방법에는 다양한 렌더링 패턴이 존재하고, 이 패턴들은 모두 trade-off가 있을 뿐입니다.

이 블로그 원본 url


위에서는 렌더링에 전반적인 내용을 알아봤습니다.
이 렌더링 방식을 조건을 걸어서 사용하는 방법이 있습니다.

비동기 통신과 조건부렌더링

javascript는 작성된 코드가 상단에서부터 순서대로 실행되기 때문에 데이터를 요청하고 응답을 받아오는 동안 화면에 그려질 데이터의 내용이 undefined 이므로 첫 화면이 그려지는 시기에 데이터를 불러오면서 에러가 발생합니다.

이 부분이 효율적으로 실행되기 위해서 화면을 미리 그려놓고 데이터를 그려주기 위해서 조건부렌더링을 사용합니다.

조건부 렌더링에는 &&연산자, 삼항연산자, 옵셔널체이닝이 있고 추가로 ES11문법에서 나온 nullish coalescing도 있습니다.

그렇다면 사용 방법은 어떻게 될까요?

삼항 연산자 === ? :

제일 처음에는 삼항 연산자를 썼습니다.

data는 동기적으로 받아와야하는 데이터입니다. 하지만 데이터가 오기 전에 이미 return 부분에서 rendering을 해주고 있기 때문에 삼항 연산자를 써서 데이터가 있을 때, 없을 때를 모두 적어줘야 했습니다.

data ? data.fetchProfile : undefined

⭐ 삼항 연산자의 단점과 장점

참과 거짓을 모두 작성해야 합니다.
여러 줄일 경우 가독성이 매우 떨어집니다.

그렇기에 한 줄일 경우 매우 가독성이 좋습니다.

&& 연산자

그 이후에는 && 연산자를 썼습니다.

data && data.fetchProfile

&&연산자데이터가 없을 경우 자동으로 undefined를 반환해줍니다. 데이터가 없을 때 따로 div를 쓸 필요가 없으면 else 부분을 쓸 필요가 없죠. 하지만 이 코드조차 길다고 느껴집니다.

&&연산자앞의 값이 참일 경우에만 뒤의 값을 보여주었는데, 반대로 앞의 값이 거짓일때 뒤의 값을 보여주는 경우도 있습니다, Nullish coalescing 연산자라 불립니다.

??연산자앞의 값이 빈 값이면 뒤의 값을 보여주며 ||연산자앞의 값이 거짓(false)일 경우 뒤의 값을 보여줍니다.

data ?? data.fetchProfile

data || data.fetchProfile

⭐️옵셔널 체이닝 (Optional-Chaining)

optional-chaing이란 기존의 && 연산자를 쓰면서 길어졌던 코드를 더욱 간결하게 사용하는 연산자 입니다.

optional-chaing은 최신 문법입니다. 무려 ES2020에서 나온 것이죠. 그래서 아직 모르는 사람이 많을 수도 있습니다.

(ES2020에 대해 궁금하신 분들은 따로 검색해보세요! 자바스크립트는 매년 새로운 버전이 나오면서 추가적인 기능들이 나오고 있습니다.)

data?.fetchProfile

optional-Chaining? 연산자 앞 객체의 참조가 undefined || null 이라면 undefined를 리턴해줍니다.

위에 있는 삼항연산자, && 연산자와 똑같은 기능을 하는 것이죠.

하지만 훨씬 간단해졌습니다.

아래 사진을 보고 어떤 것이 가장 가독성이 좋은지 살펴보세요!

Nullish-coalescing

옵셔널 체이닝 처럼 많이 사용하는 것은 아니지만, 같은 ES11문법에서 나온 nullish coalescing은 앞에가 null과 undefined일때만 렌더해주는 연산자 입니다.

// data가 null 또는 undefined 일 때만 data.fetchProfile 을 보여줘
data ?? data.fetchProfile

최근에 나온 언어라 자주 사용하지 않고 있습니다.

// data가 참일 때 data.fetchProfile 을 보여줘
data && data.fetchProfile

// data가 거짓일 때 data.fetchProfile 을 보여줘
// data가 참일 때 data 을 보여줘
data || data.fetchProfile

// data가 null 또는 undefined 일 때만 data.fetchProfile 을 보여줘
data ?? data.fetchProfile

profile
FrontEnd Developer

0개의 댓글