이 글은 ASAC 06기를 수강하며 강의 자료 참고 및 추가 자료 수집을 통해 작성된 글입니다.
Rendering
렌더링은 유저에게 보여지는 웹 페이지를 만드는 것을 의미한다.
맥락에 따라서 두가지 의미로 사용될 수 있다.
- HTML 문서를 완성함 (서버 혹은 브라우저)
- 브라우저에서 렌더 트리를 생성함
- HTML -> DOM Tree 변환
- CSS -> CSSOM Tree 변환
- DOM Tree + CSSOM Tree => Render Tree
그리고 완성된 Render Tree를 화면에 픽셀로 그리는 것을 페인팅이라고 한다.
1번 의미(HTML 문서를 완성한다)에서의 렌더링은 서버 혹은 브라우저에서 일어날 수 있다. 위치에 따라서 SSR 혹은 CSR이라고 부른다.
2번 의미에서의 렌더링(렌더 트리를 생성)은 항상 브라우저에서 일어난다.
https://web.dev/articles/critical-rendering-path/render-tree-construction?hl=ko
브라우저 렌더링 절차
렌더 트리를 만드는 브라우저의 렌더링 절차는 다음과 같다.
- HTML -> DOM 트리가 된다.
HTML ~= DOM : HTML은 단지 텍스트. DOM은 HTML 조작을 위해 제공되는 API(객체) - CSS -> CSSOM 트리가 된다.
CSS ~= CSSOM : CSS는 단지 텍스트. CSSOM은 CSS 조작을 위해 제공되는 API(객체) - DOM 트리 + CSSOM 트리 => RENDER 트리
- Render Tree -> Layout(Reflow) -> Paint(Repaint)
4-1. 레이아웃
뷰포트를 기준으로 매번 HTML이 바뀔 때마다 상대 단위를 계산
ex) 픽셀단위 = 절대적. 항상 고정값을 가진다.
rem, em, vh, vw = 상대적. 레이아웃이 바뀔 때마다 재계산된다.
4-2. 페인트
사용자가 보는 화면을 그리는 것 - HTML + CSS 가 웹 브라우저에 의해 페인팅되어 유저에게 보여지면, 이제 JS를 통해 인터랙션 이벤트 등을 적용한다.
HTML + CSS가 빠르게 로드 되더라도, JS 로드 시간과 차이가 있다면, 사용자 경험이 나빠진다.
=> TTI(Time To Interactive) : HTML + CSS 로드된 뒤 JS를 통해 이벤트가 비로소 적용됐을 때
TTI를 높이는 것이 관건이다!
정적, 동적 웹페이지
- 정적 웹페이지
-> 실시간성이 보장되지 않는다. 요청마다 항상 같은 웹 페이지 반환
-> 페이지 내의 데이터를 수정하기 위해서는 페이지 자체를 수정하고 재배포해야 한다. - 동적 웹페이지
-> 실시간성이 보장된다.- 페이지 내에 데이터를 포함하는 경우 데이터는 매번 application에 의해 DB로부터 조회되고, 페이지에 바인딩된다.
- 사용자 인터랙션에 의한 이벤트 발생으로 페이지가 변경될 수 있다.
Rendering Pattern
웹 페이지는 정적 웹 페이지와 동적 웹 페이지로 나뉜다.
그리고 동적 웹 페이지 렌더링 위치에 따라서 SSR, CSR로 나뉜다.
(여기서 말하는 렌더링은 HTML 문서의 완성을 의미한다.)
다음은 렌더링 패턴의 종류이다.
정적 웹 페이지
- SSG동적 웹 페이지
- SSR (MPA)
- CSR (SPA)정적 + 동적 웹페이지Hydration
SSG
SSG(Static Site Generation)은 정적 페이지 생성을 의미한다.
SSG 하위 분류
Next.js 공식 문서 : https://nextjs.org/docs/pages/building-your-application/rendering/static-site-generation
GitHub Discussion : https://github.com/vercel/next.js/discussions/58593
SSG는 두가지로 나뉠 수 있다.
- Static Websites without Data
- SSG with Data
1번은 일반적인 의미에서의 Static Websites 를 의미한다. HTML, CSS, JS 파일을 작성하는 것만으로도 Static Website를 만들 수 있다. HTML 파일 자체가 완성된 웹 페이지이다.
2번은 Next.js 와 같은 Static Site Generator를 사용하는 경우인데(Next.js 기준 설명 주의), 정적 페이지이지만 API를 호출하고 데이터를 결합할 수 있다. (물론 Next.js를 사용하면서도 API 호출을 하지 않는 정적 사이트를 생성할 수 있다.) JSX, CSS를 개발하고 빌드한다.
특징
- HTML + CSS + JS를 스토리지(AWS S3와 같은 정적 리소스 저장소) 혹은 서버에 올려서 호스팅한다. 불변하는 페이지이기 때문에, 웹 서버가 불필요하다. 기술 블로그 같은 호스팅에 유용하다.
- HTML + CSS + JS를 CDN 에 캐싱하여 네트워크 트래픽을 줄일 수 있다.
- CDN은 글로벌 캐시. 국가마다 캐시 서버 존재
- 물리적 거리가 존재하는 캐시에 접근할 필요가 없다.
- build time에 렌더링한다.
- 웹 페이지 버전 변경을 하려면, 매번 파일을 변경해줘야 하여 번거롭다.
- 자주 바뀌지 않는 웹 페이지는 정적 페이지로 만드는 것이 유리하다.
- 이미 빌드 타임에 완성되어 스토리지 혹은 서버에 저장된 정적 웹 페이지를 반환만 하면 되기 때문에 매우 빠른 웹 페이지 반환 속도 그러나 실시간성에 위배될 수 있다.
- 구글과 같은 SEO에 유리
- 웹 크롤러가 웹 페이지를 수집할 수 있다.
- 배포 절차
- 빌드 (이때 렌더링)
- 업로드 + 호스팅
- 호스팅(Hosting. 대여) 종류
- 서버 호스팅 (부수적)
-> 스토리지로도 배포가 가능하기 때문에 서버 호스팅은 필요하지 않다. - 도메인 호스팅 (필수)
- 서버 호스팅 (부수적)
SSG with Data vs SSR
SSG with Data는 fetch한 데이터를 정적 페이지에 결합하여 웹 페이지를 완성시키는 과정(= 렌더링)이 필요하다. 이를 Pre-Render 라고 부르며, 브라우저로 전송되기 전에 Pre-Render가 된다. 그럼 여기서 SSG with Data는 SSR과 같지 않은가 하는 의문점이 들 수 있다.
SSR과 SSG with Data의 차이점은 바로 렌더링 시점이다. (물론 여기서의 렌더링은 렌더트리를 만드는 것이 아닌 웹페이지의 완성을 의미함.)
SSG with Data의 렌더링 시점은 데이터가 없는 정적 사이트와 마찬가지로 빌드 타임이다. 그래서 여전히 실시간성에 위배될 수 있다.
하지만 이후 설명할 SSR의 렌더링 시점은 런타임이다.
1000개의 유저 정보 페이지가 필요하다고 가정하자.
-> SSG with Data는 빌드타임에 웹 페이지가 생성되므로 1000개의 유저 정보 페이지 각각 빌드하여 모두 배포해야 한다.
-> SSR은 런타임에 웹 페이지가 생성되므로 1개 페이지만 배포하면 된다.
SSR
SSR 은 Server Side Rendering 의 약자로 동적 웹 페이지의 렌더링을 서버에서 수행하는 것이다.
사용자가 보는 페이지의 변화를 위해서는 매번 새로운 웹 페이지에 대한 http 요청이 필요하기 때문에, MPA(Multi-Page-Application) 이라고도 한다.
또한 렌더링이 서버에서 수행된 이후로 재렌더링이 되지 않기 때문에 정적 렌더링이다.
동적 페이지를 반환하는 웹 애플리케이션 서버(WAS) 활용
: 톰캣 (스프링 부트 + 타임리프)
특징
- 쇼핑몰과 같이 실시간성이 중요한 웹 페이지 형태.
- Static Websites는 모든 정적 웹 페이지를 저장하고 있어야 했는데, SSR은 뷰 템플릿과 모델을 조합하여 요청에 따라서 웹 페이지를 생성하기 때문에 용량 관리에 효율적이다.
- 요청을 받음에 따라서 실시간 데이터 조회 후 페이지를 생성함
웹 페이지를 만드는 주체는 애플리케이션이다.
템플릿 엔진 -> 모델 + 뷰 템플릿 하여 뷰 생성 - CSS, JS는 불변, DOM은 가변 (모델에 따라서 달라짐)
- DOM을 캐시에 저장하는 경우 실시간성 위배 문제?
- 요청을 받음에 따라서 실시간 데이터 조회 후 페이지를 생성함
- 서버 필요
- 서버가 생성해서 반환하기에 웹 페이지 반환 속도는 DB 조회 속도, 웹 페이지 생성 속도에 의존한다.
- 웹 서버의 CPU, 메모리 자원이 사용되기 때문에 AWS 같은 클라우드 사용 시 비용 부담이 있을 수 있다.(단순 스토리지보다 서버 사용 비용이 더 크기 때문이다.)
- 심화 : 비용 감소를 위한 Serverless 적용 (ex. AWS lambda)
호출 수가 적다면 서버리스는 효율적이다. 그러나 호출 수가 많으면 차라리 서버를 따로 두는 게 낫다.
- 심화 : 비용 감소를 위한 Serverless 적용 (ex. AWS lambda)
- 구글과 같은 SEO 가능
- 크롤러가 페이지를 조회했을 때 온전하게 존재한다. 서버가 이미 만든 완성본(서버 사이드 렌더링)을 가져가기 때문에.
- 런타임에 렌더링, 웹페이지 생성
- 실시간성을 취하지만, 웹 서버가 웹 페이지를 다 만드는 데까지 너무 오랜 시간이 소요된다.
CSR
페이지를 여러 개 주지 않고 하나의 페이지를 반환한다. 그렇기 때문에 이를 SPA(Single Page Application)이라고도 부른다. 렌더링(웹 페이지의 완성)을 브라우저에서 수행한다.
웹 브라우저 내 자바스크립트를 활용해 동적으로 페이지를 렌더링한다. 그렇기 때문에 자바스크립트의 용량이 매우 크다.
또한 사용자 인터랙션에 의해 매번 리렌더링, 리페인팅이 발생할 수 있으므로 동적 렌더링이다.
특징
- 클라이언트(웹 브라우저)가 렌더링을 하기 위해서 HTML + CSS + JS를 다운로드 받는다. 웹 브라우저에서 렌더링과 페인팅이 모두 발생한다.
- 기존 웹(Static Websites, SSR)은 필요한 페이지마다 요청을 했으나, CSR은 초기 한 번의 http 요청(깜빡임) 후에 인터랙션마다 내부적으로 라우팅, 레이아웃 재계산, 리렌더링, 리페인팅이 수행된다. -> http 요청을 다시 하지 않는다.
- 마치 여러 개의 페이지를 두고 요청을 하는 것처럼 보이지만 사실 하나의 페이지 내에서 라우팅이 되는 것이다.
- 이러한 이유로 부드러운 화면 전환이 가능하다.
- 마치 여러 개의 페이지를 두고 요청을 하는 것처럼 보이지만 사실 하나의 페이지 내에서 라우팅이 되는 것이다.
- 모바일 앱과 같이 실시간 유저 인터페이스가 중요한 웹페이지에서 사용한다. 페이지 이동이나 버튼에 따른 동작 부드럽기 때문에.
- spa 등장 배경 : Fast Route Transition
(모바일 앱과 유사한 화면 전환 및 사용성) => spa route
빠른 화면 전환, 모바일 앱과 유사한 화면 전환.
- spa 등장 배경 : Fast Route Transition
- JS를 전달하면, 클라이언트(웹 브라우저)가 JS를 실행하여 페이지 전이(transition)를 수행한다.
-> 템플릿 엔진의 역할을 자바스크립트가 하게 된다.- 서버 불필요 (API 사용하지 않는다면)
- 데이터는 서버에 의해서 API로 제공되기 때문에, 데이터를 사용하지 않는다면 서버 불필요.
- Static Websites와 마찬가지로 스토리지에 배포하거나 서버를 사용 (서버 없어도 무방하다)
- 웹 크롤러는 빈 HTML, API를 통한 데이터를 보게 된다.
=> SEO 불가능
웹 페이지를 크롤링해야 하는데, 웹 크롤러는 자바스크립트를 실행할 수 없다. - 유저에게 최적의 사용성(UX)를 제공하려다보니 Bundled Js 크기가 너무 크다 : Initial Loading 문제
-> 네트워크 비용을 많이 쓰고 초기 로딩이 SSR에 비해서 느리다. - 구글과 같은 SEO가 불가능
- 요청 초기 크롤러는 빈 HTML을 보게 된다.
- 사용자 요청에 따라 라우팅 되지만 실제로는 하나의 페이지만 가지고 있기 때문에, 각각의 URI에 대한 SEO가 매우 어렵다.
- SPA 자체는 SEO 친화적이지 않은 솔루션이다.
- 페이지 전환 및 사용자 인터랙션에 따른 동작이 부드럽지만 Bundled JS가 너무 무겁다.
- 서버 불필요 (API 사용하지 않는다면)
빈 HTML이란?
CSR을 하는 대표적인 프론트엔드 프레임워크인 리액트를 예로 들어보자.
리액트 프로젝트에는 public 디렉토리 하위에 index.html 이라는 오직 하나의 페이지(HTML)만 존재한다. index.html은 일반적인 html 구조를 가지고 있다.
먼저 head에는 메타 태그 및 link들이 정의되어 있다.
메타태그는 두 가지 용도를 위해 사용된다.
- 반응형 웹을 위한 정보
ex) viewport 크기 - SEO를 위해 웹 페이지에 어떤 정보가 담겨있는지 알려주는 용도
하지만 body에는 id가 root 인 div만을 가지고 있다.
화면에 보여지는 모든 요소들은 root 내부에 구현이 되며, *렌더링 전까지는 볼 수 없다. *
<!-- ./public/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
그리고 index.html 의 렌더링을 위해 실행되는 JS 파일은 index.js 이다.
// ./src/index.html
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
index.js에서 root에 대한 DOM을 선택하여 렌더링을 수행하도록 정의되어 있다. 이 index.js는 브라우저에 의해 실행되며, 이때 렌더링(웹 페이지 완성)을 하게 된다.
SSG, SSR, CSR 비교
SSG | SSR | CSR | |
---|---|---|---|
렌더링(웹 페이지 완성) 시점 | 빌드 타임. Data Fetch가 일어나는 경우 데이터와 결합을 Pre-Rendering이라고 부름 | 런타임에 서버에서 뷰 템플릿과 모델 결합(뷰 리졸버와 템플릿 엔진) | 런타임에 브라우저에서 자바스크립트 실행으로 |
사용 | 자주 바뀌지 않는 정적인 페이지에 사용 | BackOffice(사내에서 사용) 용도로 사용. UI, UX가 중요하지 않은 경우 | UI가 중요하고 사용자 인터랙션이 잦은 페이지. 유저 인터랙션 시 부드러운 화면 전환. http 요청은 초기 1번만 일어나고 이후 내부적으로 라우팅 |
초기 로딩 속도 | 제일 빠름 | SSG만큼은 아니지만, 빠르다. | 브라우저에서 자바스크립트 실행해야하므로 느리다 |
SEO | 가능 | 가능 | 불가능. 초기 빈 웹페이지로 크롤러가 읽을 수 없음 |
단점 | 실시간성에 위배된다. 버전 변경 시 매번 파일 변경해줘야 하는 번거로움 | 도메인 입력 시 매번 http 요청이 발생한다. 그래서 화면 전환 시 깜빡임이 존재하고 UI, UX 한계가 있다. | 초기 로딩이 매우 느리다. 무거운 Bundled JS를 가지고 있다. |
Hydration
Hydration 은 MPA + SPA (SSR + CSR) 방식과 같다.
Hydration은 수분을 더한다는 의미로, 부분적으로 CSR 렌더링을 하는 것을 의미한다. (1개 페이지 전체를 CSR 하거나, 페이지 내부 컴포넌트 부분부분을 CSR 한다.)
사용자 인터랙션과 관련된 부분은 Hydration(CSR, SPA)로 처리한다. 런타임에 자바스크립트가 사용자 인터랙션에 의해 발생하는 이벤트에 따라서 레이아웃을 재계산하고 리렌더링 및 리페인팅을(동적 렌더링) 수행할 수 있도록 한다. 그렇기 때문에 웹 페이지 전환 시 화면의 깜빡임이 존재하지 않는다.
하지만 Hydration 적용 시 JS가 늦게 로드되기 때문에 TTI는 느릴 수 있다.
또한 CSR의 Bundled JS가 너무 커서 초기 로딩이 오래 걸리는 문제를 해결할 수 있다. (Bundled JS를 각각 분리하여, CSR에 필요없는 Bundled JS는 스토리지에 저장하거나 브라우저로 전송하지 않는다.)
또한 SEO가 가능하다.
- SSR with Hydration = MPA + SPA
- SSG with Hydration
- ISR
SSR with Hydration
SSR with Hydration은 사용자 인터랙션에 따른 처리가 부족한 SSR의 단점과 SEO가 불가능한 CSR의 단점을 서로 보완하기 위해서 SSR과 CSR을 결합한 방식이다.
SSR은 런타임에 서버에서 완성된 웹 페이지를 브라우저에서 렌더 트리를 생성하고 페인팅만 하면 되기 때문에, SEO가 가능하다.
SSR with Hydration은 Next.js, Nuxt.js, SveltKit 과 같은 모든 종류의 Meta Framework가 제공하는 기능이다.
(Meta Framework = Framework of Frontend Javascript Framework(ex. React))
Next.js 예시
Next.js 버전에 따른 차이
- version < 14 => Page Router
- 페이지 라우터 : ssr, csr 적용이 페이지 단위 적용
어떤 페이지는 한가지만 선택 가능
- 페이지 라우터 : ssr, csr 적용이 페이지 단위 적용
- version >= 14 (최신 버전) => App Router
- 앱 라우터 : ssr, csr 적용이 컴포넌트 단위 적용
-
- SSR용 자바스크립트
- CSR용 자바스크립트3 종류의 Bundled JS
-> Storage에 저장 및 브라우저로 전송된다.
- 미들웨어(Edge)
- 스프링의 필터 체인과 비슷한 역할을 한다. ex) valiadation, security 등
- CDN은 수행에 책임이 없다. 반면 EDGE function은 일종의 CDN임에도 함수를 수행할 수 있다. 즉, 함수를 직접 수행하여 해당 내용을 캐싱 가능하다.
SSG with Hydration
React 같은 프레임워크로 개발 및 빌드한 정적 웹 페이지를 반환하는 웹 서버 혹은 스토리지 활용 : Nginx, Apache, S3
웹 프론트엔드 프레임워크(Vue.js, React.js, Angular.js 등)를 사용하여 CSR 개발하듯이 정적 웹 페이지를 개발한다. Static Websites와는 달리 HTML, JS, CSS를 직접 생성하는 것이 아니라 JSX, CSS를 개발하고 빌드한다.
또한 Pre-Rendering(빌드 타임에 렌더링이 수행)된다.
SSG with Hydration을 사용하는 이유는 역시 SEO 개선을 위해서이다. SSG로 페이지 로딩 속도를 극단적으로 줄일 수 있다.
성능 ↑ => SEO ↑
특징
기본적으로 Static Websites와 같은 특징들을 가지지만, 다음과 같은 차이점이 있다.
- 웹 페이지 버전 변경 시, 개발 -> 빌드 과정을 거치면 되어 Static Websites보다 간편하다.
- Hydration을 활용하여, 여전히 모바일 앱과 유사한 사용성을 제공할 수 있다.
=> 웹 브라우저가 동적 웹 페이지를 생성하여 반환하기 때문에, 웹 페이지 전환 시 깜빡임이 존재하지 않는다. - 서버가 불필요하다. Static Websites와 같이 S3와 같은 스토리지에 올려서 배포할 수 있다.
- 빌드 타임에 Pre-Rendering
- 1000개 유저 페이지가 필요한 경우 각각의 요청에 대해서 1000개 페이지를 빌드하여 스토리지에 저장하거나 서버에 올려야한다.
- REST API를 자체적으로 만들 수 있다.
- SSR에서 웹 페이지 반환 or 웹 데이터 반환
- Route Handler
=> REST API를 만들 수 있는 라우팅 기능. PUT, PATCH, GET, DELETE와 같은 메서드 사용 가능
- 실시간성이 떨어질 수 있다.
ISR
ISR(Incremental Static Regeneration)은 리액트같은 프레임워크로 개발 및 빌드한 정적 페이지를 주기적 갱신 / 반환하는 웹 애플리케이션 서버를 활용하는 것이다.
SEO 개선을 위해서 SSG를 사용했으나 실시간 페이지 업데이트를 위해 너무 잦은 빌드가 필요하다. -> ISR을 사용하자.
SSG with Hydration의 특징과 거의 비슷하지만, 준실시간성을 보장한다는점에서 차이가 존재한다.
특징
- SSG를 위해서는 웹 서버면 충분하지만, ISR은 주기적으로 근원 정보 호출을 통해 정적 페이지 리빌드를 하기 위해서 WAS가 필요하다.
=> Revalidate 옵션으로 SSG로 빌드했던 웹 페이지가 일정 시간이 지나면 리빌드된다. - SSG임에도 불구하고 서버가 필요하다. => 기술 블로그 같은 호스팅이 매우 어렵다.
- SSG와 마찬가지로 빌드타임에 렌더링이 된다.
- 1000명의 유저 정보 페이지가 필요하다면, 각각을 미리 만들어두어야 한다. 즉, 1000개의 유저 정보 페이지가 정적으로 존재해야 한다.
- WAS를 사용하여 주기적으로 리빌드를 하기 때문에 준실시간성이 보장된다.
- Hydration을 활용하여 웹 페이지 전환 시 깜빡임이 존재하지 않는다.
'ASAC' 카테고리의 다른 글
[ASAC 06] Javascript Runtime (브라우저와 서버에서의 비동기 처리) (1) | 2024.08.27 |
---|---|
[ASAC 06] 프론트엔드 변천사(Vanilla JS, jQuery, React.js) (1) | 2024.08.27 |
[ASAC 06] WS와 WAS (0) | 2024.08.27 |
[ASAC 06] SEO, 웹 페이지 성능 Core Web Vital (1) | 2024.08.27 |
[ASAC 06] 네트워크, DNS (0) | 2024.08.27 |