모던 리액트 deep dive 스터디 - 4강 발표 정리
주제: 4장 서버 사이드 렌더링 (252p ~ 335p)
JAM 스택을 사용하여 프런트엔드는 자바스크립트와 마크업을 미리 빌드해 두고 정적으로 사용자에게 제공하여 서버 확정성 문제에서 자유로워질 수 있게 됨.
시대가 지나면서 인터넷 속도, 하드웨어 성능이 좋아졌지만 자바스크립트 리소스 크기도 덩달아 증가하였기 때문에
여전히 모바일에서의 웹페이지 로딩은 오래걸린다. 현재의 웹 애플리케이션은 그만큼 정말 다양한 작업들을 처리하고 있다.
서버 사이드 렌더링
최초에 사용자에게 보여줄 페이지를 서버에서 렌더링해 빠르게 사용자에게 화면을 제공하는 방식
싱글 페이지 애플리케이션과의 차이는 웹페이지 렌더링의 책임을 어디에 두느냐다.
서버 사이드 렌더링은 렌더링에 필요한 작업을 모두 서버에서 수행
장점
1. 최초 페이지 진입이 비교적 빠르다
FCP가 빨라질 수 있다.
일반적으로 서버에서 HTTP 요청을 수행하는 것이 더 빠르며, HTML을 그리는 작업도 서버에서 HTML을 문자열로 미리 그려서 내려주는 것이 더 빠른 속도를 가진다.
2. 검색 엔진과 SNS 공유 등 메타데이터 제공이 쉽다
검색 엔진 최적화에 유용하다
검색 엔진이 사이트에서 필요한 정보를 가져가는 과정
1. 검색 엔진 로봇이 페이지에 진입
2. 페이지가 HTMl 정보를 제공해 로봇이 HTML 다운로드, 자바스크립트 코드는 실행하지 않음
3. HTML 페이지 내부의 Open Graph나 meta 태그 정보를 기반으로 페이지의 검색 정보를 가져와 검색 엔진에 저장
검색 엔진 로봇은 자바스크립트를 다운로드하거나 실행하지 않는다.
SPA는 대부분의 작동이 JS에 의존하기 때문에 검색 엔진에 불리하다.
반면, 서버 사이드 렌더링은 최초의 렌더링 작업이 서버에서 일어나, 검색 엔진에 제공할 정보를 서버에서 가공하여 HTML 응답으로 제공할 수 있어 검색 엔진 최적화에 대응하기 용이하다.
3. 누적 레이아웃 이동이 적다
누적 레이아웃 이동이란?
사용자에게 페이지를 보여준 이후에 뒤늦게 어떤 HTML 정보가 추가되거나 삭제되어 마치 화면이 덜컥거리는 것과 같은 부정적인 사용자 경험. 즉, 사용자가 예상치 못한 시점에서 페이지가 변경되어 불편을 초래
서버 사이드 렌더링의 경우 요청이 완전히 완료된 이후에 완성된 페이지를 제공하므로 이러한 문제에서 비교적 자유롭다.
4. 사용자의 디바이스 성능에 비교적 자유롭다
서버 사이드 렌더링은 렌더링에 대한 부담을 서버와 나눌 수 있어 사용자의 디바이스 성능으로부터 조금 더 자유로워질 수 있다.
5. 보안에 좀 더 안전하다
브라우저의 개발자 도구와 같은 작업을 수행할 수 없어 보안적으로 안전하다.
단점
1. 소스코드를 작성할 때 항상 서버를 고려해야 한다.
window, sessionStorage와 같이 브라우저에만 있는 전역 객체를 사용할 수 없다.
클라이언트에서만 실행되는 코드가 많을수록 불리하다.
2. 적절한 서버가 구축돼 있어야 한다.
사용자의 요청에 대응할 수 있는 물리적인 가용량 확보, 복구 전략, 요청 분산, 프로세스 다운 대비 등등과 같은 여러 방안이 준비되어 있어야 한다.
3. 서비스 지연에 따른 문제
서버에서 사용자에게 보여줄 페이지에 대한 렌더링 작업이 끝나기까지는 사용자에게 어떠한 정보도 제공할 수 없다.
대안 => Streaming SSR
API 호출과 관련없는 부분은 빠르게 그림
API 호출과 같이 외부 요소에 의해 렌더링이 지연될 수 있는 곳은 나중에 그린다.
*서버 사이드 렌더링 역시 만능이 아니다
잘못된 웹페이지 설계는 성능 개선은 커녕 서버와 클라이언트 두 군데로 관리 포인트만 늘어나는 역효과를 낳을 수 있다.
멀티 페이지 애플리케이션에서 발생하는 라우팅 문제를 해결하기 위한 다양한 API
1. 페인트 홀딩
같은 출처에서 라우팅이 일어날 경우 화면을 잠깐 하얗게 띄우는 대신 이전 페이지의 모습을 잠깐 보여주는 기법
2. back forward cache(bfchache)
브라우저 앞으로 가기, 뒤로가기 실행 시 캐시된 페이지를 보여주는 기법
3. Shared Elemnet Transitions
페이지 라우팅이 일어났을 때 두 페이지에 동일 요소가 있다면 해당 콘텍스트를 유지해 부드럽게 전환되게 하는 기법
평균적인 노력을 들인다면, 별도의 최적화를 거쳐야하는 싱글 페이지 애플리케이션보다 서버에서 렌더링되는 멀티 페이지 애플리케이션이 더 나은 경험을 제공할 수 있음.
react-dom이 서버에서 렌더링하기 위한 다양한 메서드들
1. renderToString
인수로 넘겨받은 리액트 컴포넌트를 렌더링해 HTML 문자열로 반환하는 함수
인수로 주어진 리액트 컴포넌트를 기준으로 빠르게 브라우저가 렌더링할 수 있는 HTML을 제공하는 데 목적을 두기 때문에 JS 코드를 포함시키거나 렌더링하지 않는다.
2. renderToStaticMarkup
renderToString과 동일하지만 차이점은 data-reactroot와 같은 리액트에서만 사용하는 추가적인 DOM 속성을 만들지 않는다.
이 덕분에 결과물인 HTML의 크기를 아주 약간 줄일 수 있다.
+) data-reactroot라는 속성은 리액트 컴포넌트의 루트 엘리먼트 식별하는 기준이 된다.
renderToStaticMarkup은 리액트의 이벤트 리스너가 필요 없는 완전히 순수한 HTML을 만들 때만 사용된다.
만약 리액트의 자바스크립트 이벤트 리스너를 등록하는 hydrate를 수행하면 서버와 클라이언트의 내용이 불일치 하다는 에러가 발생하는데, 이는 renderToStaticMarkup의 결과는 항상 순수한 HTML만을 반환하기 때문이다.
3. renderToNodeStream
renderToString과 결과물이 완전히 동일하다. 다만, 두 가지 차이점이 있다.
1. renderToNodeStream은 브라우저에서 사용하는 것이 완전히 불가능하다.
renderToNodeStream는 완전히 Node.js 환경에 의존하고 있다.
2. renderToNodeStream의 결과물은 Node.js의 ReadableStream이다.
ReadableStream은 utf-8로 인코딩된 바이트 스트림으로, Node.js 환경에서만 사용할 수 있다.
renderToNodeStream을 사용하는 이유는?
renderToString으로 생성해야 하는 HTML의 크기가 매우 크다면 서버에 큰 부담이 될 수 있다.
이럴때 renderToNodeStream으로 렌더링한다면 데이터를 청크 단위로 분리하여 순차적으로 처리할 수 있다는 장점이 있다.
따라서, 대부분의 널리 알려진 리액트 서버 사이드 렌더링 프레임워크는 renderToString 대신 renderToNodeStream을 사용한다.
4. renderToStaticNodeStream
renderToNodeStream과 제공하는 결과물은 동일하난 renderToStaticMarkup과 마찬가지로 리액트 자바스크립트에 필요한 리액트 속성이 제공되지 않는다.
5. hydrate
renderToString과 renderToNodeStream으로 생성된 HTML 콘텐츠에 자바스크립트 핸들러나 이벤트를 붙이는 역할을 한다.
render와의 차이점은 hydrate는 기본적으로 이미 렌더링된 HTML이 있다는 가정하에 작업이 수행되고 이벤트를 붙이는 작업을 수행한다.
hydrate는 서버에서 제공해 준 HTML이 클라이언트의 결과물과 같을 것이라는 가정하에 실행 된다.
hydrate는 렌더링을 한 번 수행하면서 수행한 렌더링 결과물 HTML과 인수로 넘겨받은 HTML을 비교하는 작업도 수행한다.
만약 불일치 시 hydrate가 렌더링한 기준으로 웹페이지를 그린다.
참고로 리액트로 서버 사이드 렌더링을 직접 구현하는 것은 추천되지 않는다.
리액트 팀에서조차 Next.js 같은 프레임워크를 사용하라고 권하고 있으니 참고하자.
Next.js
Vercel에서 만든 리액트 기반 서버 사이드 렌더링 프레임워크
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig ={
reactStrictMode: true, // 엄격모드
swcMinify: true, // 번들링과 컴파일을 더욱 빠르게 수행할 수 있는 SWC를 기반으로 코드 최소화 작업 여부
}
module.exports = nextConfig
주석은 자바스크립트 파일에 타입스크립트의 타입 도움을 받기 위해 추가된 코드
해당 주석이 있어야 next의 NextConfig를 기준으로 타입의 도움을 받을 수 있다.
pages/_app.tsx
- 에러 바운더리를 사용해 애플리케이션 전역에서 발생하는 에러 처리
- react.css 같은 전역 CSS 선언
- 모든 페이지에 공통으로 사용 또는 제공해야 하는 데이터 제공 등
- Next.js를 초기화하는 파일로, Next.js 설정과 관련된 코드를 모아두는 곳
- 경우에 따라 서버와 클라이언트 모두에서 렌더링될 수 있음
_document.tsx
애플리케이션의 HTML을 초기화하는 곳
- <html>이나 <body>에 DOM 속성을 추가하고 싶을때 사용
- 무조건 서버에서 실행 되어야 하므로 이벤트 핸들러 추가 불가능
- Next.js로 만드는 웹사이트의 뼈대가 되는 HTML 설정과 관련된 코드를 추가하는 곳
- 반드시 서버에서만 렌더링됨
- 필수적인 파일 X
pages/_error.tsx
- 프로젝트 전역에서 발생하는 에러를 처리
- 필수적인 파일 X
pages/404.tsx
- 원하는 스타일의 404 페이지 정의
pages/500.tsx
- 원하는 스타일의 500 페이지 정의
- pages/_error.tsx보다 우선순위가 더 높음
파일 이름으로 라우팅
- /pages/index.tsx : 웹사이트의 루트, localhost:3000과 같은 루트 주소
- /pages/hello.tsx : localhost:3000/hello로 접근 가능
- /pages/hello/index.tsx는 /pages/hello.tsx와 같은 주소를 바라봄 (둘 다 존재한다면 빌드 에러 발생할 수 있음)
- /pages/hello/[greeting] : []내에는 어떠한 문자도 올 수 있다.
localhost:3000/hello/1 or localhost:3000/hello/greeting 모두 유효
* Next.js에서 라우팅
Next.js는 서버 사이드 렌더링의 장점을 살려 사용자가 빠르게 볼 수 있는 최초 페이지를 제공하고
싱글 페이지 애플리케이션의 장점인 자연스러운 라우팅을 모두 사용한다.
따라서, Next.js의 장점을 적극 살리기 위해서는 내부 페이지 이동 시
- <a> 대신 <Link> 사용
- window.location.push 대신 router.push 사용
📌 getServerSideProps
getServerSideProps가 없다면 서버에서 실행하지 않아도 되는 페이지로 처리하여 클라이언트에서 수행한다.
Data Fetching
📌 getStaticPaths, getStaticProps
블로그 글이나 약관 같은 정적으로 결정된 페이지를 보여주고자 할 때 사용되는 함수
반드시 함께 사용해야 한다.
📌 getServerSideProps
서버에서 실행되는 함수
- 해당 함수가 정의되어 있다면, 무조건 페이지 진입 전에 이 함수를 실행한다.
- JSON으로 제공할 수 있는 값만 props로 전달할 수 있다.
(class나 Date같은 JSON으로 직렬화할 수 없는 값은 제공 불가능하다)
- window.document와 같은 브라우저에서만 접근할 수 있는 객체에 접근 불가
- 서버는 자신의 호스트를 유추할 수 없기때문에 fetch시 완전한 주소를 제공해야한다. (/api/some/fetch X)
- 에러 발생시 500.tsx같은 에러 정의 페이지로 리다이렉트 됨
- 클라이언트에서만 실행 가능한 변수, 함수, 라이브러리 등은 서버에서 실행되지 않게 해야함.
- getServerSideProps의 내부에는 최대한 간결한 코드 작성이 요구됨.
📌 getInitialProps
getStaticProps나 getServerSideProps가 나오기 전에 사용할 수 있었던 유일한 페이지 데이터 불러오기 수단
라우팅에 따라서 서버와 클라이언트 모두에서 실행 가능
제한적으로 사용되어야 함.
가급적이면 getStaticProps나 getServerSideProps를 사용
스타일 적용하기
전역 스타일
// 적용하고 싶은 글로벌 스타일
import ’../styles.css’
글로벌 스타일은 다른 페이지나 컴포넌트와 충돌할 수 있으므로 반드시 _app.tsx에서만 제한적으로 사용
CSS in JS
styled-components와 같은 스타일을 적용하려면
Next.js에서는 CSS-in-JS의 스타일을 서버에서 미리 모은 다음 서버 사이드 렌더링에서 한꺼번에 제공해야한다.
그렇지 않다면, 스타일이 브라우저에서 뒤늦게 추가되어 FOUC(flash of unstyled content)라는 날것의 HTML이 잠시 노출된다
따라서, CSS-in-JS를 서버 사이드 렌더링 프레임워크에서 사용할 때는 반드시 초기화 과정을 서버에서 거쳐야 한다.
Next.js와 SWC를 고려하고 있다면, styled-jsx, styled-components, emotion 중 하나를 사용하는 것이 좋다.
사용하는 개발자들이 비교적 많고, SWC를 이용한 빠른 빌드의 이점을 누릴 수 있다
next.config.js
📌 basePath
URL 접두사 역할, localhost:3000/으로 정의할 경우
<Link>나 router.push()시 basePath 추가할 필요 X
다만, <a>태그 혹은 window.loaction.push 등으로 라우팅을 수행할 경우에는 적용되지 않아 이 경우에는 반드시 bathPath가 붙어 있어야 한다.
📌 swcMinify
swc를 이용해 코드를 압축할지를 나타낸다.
📌 poweredByHeader
Next.js는 응답 헤더에 X-Power-by: Next.js 정보를 제공하는데, false를 선언하면 이 정보가 사라진다.
powered-by 헤더는 취약점으로 분류되므로 false로 설정하는 것이 좋다.
📌 redirects
특정 주소를 다른 주소로 보내고 싶을 때 사용
📌 reactStrictMode
리액트에서 제공하는 엄격 모드 설정 여부
📌 redirects
next에서 빌드된 결과물을 동일한 호스트가 아닌 다른 CDN 등에 업로드하고자 한다면 이 옵션에 해당 CDN 주소를 명시
'프로그래밍언어 > React' 카테고리의 다른 글
모던 리액트 deep dive 스터디 - 14장 발표 정리 (1) | 2024.12.29 |
---|---|
모던 리액트 deep dive 스터디 - 12장 발표 정리 (1) | 2024.12.21 |
모던 리액트 deep dive 스터디 - 8주차 발표 정리 (0) | 2024.04.27 |
모던 리액트 deep dive 스터디 - 7주차 발표 정리 (1) | 2024.04.20 |
모던 리액트 deep dive 스터디 - 6주차 발표 정리 (0) | 2024.04.13 |