렌더링 전략이란 웹 페이지 또는 웹 애플리케이션을 웹 브라우저에 제공하는 방법을 의미합니다. 리액트의 경우 CSR, PHP의 경우 SSR을 지원을 하고있습니다. 하지만 Next의 경우 다양한 렌더링 전략을 제공합니다.
웹 페이지 방문 -> 해당 사이트의 html 코드와 js 파일을 전부 받아옵니다 -> 받아온 html 파일에 js파일과 css파일로 마크업을 진행하여줍니다.
표준 리액트 앱은 서버에서 자바스크립트 번들을 클라이언트로 전송한 다음 렌더링을 시작합니다. create-react-app(CRA)
를 사용해봤다면 화면이 텅텅 비어있는 것을 본 적이 있을 것입니다. 서버가 웹 애플리케이션이 필요로하는 스크립트와 스타일만 포함된 기본 HTML 마크업만 전송하기 때문입니다.
배포된 URL로 접근하면 서버로부터 해당 url로 배포된 html
마크업을 받아 화면에 렌더링을 시작합니다.
CRA
가 빌드 과정 동안 주입한 script
, link
태그의 자바스크립트 번들과 css
파일을 다운로드합니다.
해당 브라우저가 이를 통해 전체 애플리케이션을 렌더링합니다.
쉬운 페이지 전환
지연된 로딩과 성능
html
마크업만 렌더링을 합니다. 사용자가 버튼을 클릭하면 보이는 모달의 경우 동적으로 생성되기 때문에 실제로는 존재하지 않습니다.서버 부하 감소
html
파일을 전송하는 정도입니다.자연 스러운럽게 느껴지는 웹 애플리케이션
이러한 장점들은 단점이 될 수도 있습니다. 간단한 html
파일만을 보내기 때문에 네트워크 속도가 느린 환경에서는 페이지를 구성하는 script
나 css
파일을 받는데에 오랜 시간이 소요도리 수 있습니다. 이러한 경우 오랫동안 빈페이지를 바라보고 있어야 합니다.
서버 사이드 렌더링은 PHP, 파이썬과 같이 html
페이지를 웹 브라우저로 전송하기 전에 서버에서 전부 렌더링하는 방법으로 렌더링하여 도착하고 모든 자바스크립트 코드가 적재되면 동적으로 페이지 내용을 렌더링합니다.
Next.js에서도 마찬가지로 각 요청에 따라 서버에서 html
페이지를 동적으로 렌더링하여 웹 브라우저로 전송할 수 있습니다. 또한 서버에서 렌더링한 페이지에 스크립트 코드를 집어넣어서 나중에 웹 페이지로 동적으로 처리할 수도 있는데 이를 hydration
이라고 합니다.
웹 페이지 방문 -> 프론트 엔드 서버 (HTML 파일 요청) -> 백엔드 서버(렌더링 준비가 끝난 HTML 반환) -> 브라우저(받아온 HTML에 JS를 입혀 동적인 웹 페이지로 만들어줍니다.)
배포된 URL로 접근하면 서버로부터 해당 url로 배포된 html
과 script
파일을 받아 화면에 렌더링을 시작합니다.
받아온 html
페이지에 모든 script
코드가 적재가 된다면 해당 DOM 위에 각 스크립트 코드를 하이드레이션 합니다.
해당 브라우저가 이를 통해 전체 애플리케이션을 렌더링합니다.
위같은 이유때문에 페이지를 새로 고침하지 않고도 아무 문제 없이 사용자와 웹 페이지를 하이드레이션합니다. 그래서 페이지를 새로 고치지 않고도 아무 문제없이 상호작용할 수 있습니다.
리액트 하이드레이션의 관한 내용은 다음 사이트를 통해서 확인할 수 있습니다.
리액트 하이드레이션 덕분에 웹 앱은 싱글 페이지 애플리케이션(SPA)처럼 작동할 수 있습니다. 클라이언트 사이드 렌더링(CSR)과 서버 사이드 렌더링(SSR)의 장점을 모두 가지는 것입니다.
보안성
호환성
검색엔진 최적화
// getServerSideProps는 매 요청 시 호출이 됩니다.
export async function getServerSideProps(){
// 외부 API로부터 데이터를 가져옵니다.
const userRequest = await fetch('https://topdragon.co.kr/api/user/info');
const userData = await userRequest.json();
// 가져온 데이터를 props로 전달합니다.
return {
props: {
user: userData,
}
}
}
// 렌더링이 되는 페이지
function IndexPage(props) {
// props로 받아와서 사용을 할 수 있습니다.
return <div>Welcome, {props.user.name}</div>
}
// 매번 요청시마다 다시 요청을 합니다.
fetch(URL, { cache: 'no-store' });
export default funciton Home(){
const userReq = fetch('https://topdragon.co.kr/api/user/info', { cache: 'no-store' })
const user = userReq.json();
return <div>Welcome, {user.name}</div>
}
정적 사이트 생성(SSG)란 일부 또는 전체 페이지를 빌드 시점에 미리 렌더링 하는 것을 의미합니다. 웹 애플리케이션을 빌드할 때 내용이 거의 변하지 않는 경우 정적 페이지 형태로 제공하는 것이 좋습니다. Next.js는 이런 페이지를 빌드 과정에서 정적 페이지로 미리 렌더링해서 html
마크업 형태로 제공합니다. SSR과 비슷합니다. 또한 리액트 하이드레이션 덕분에 정적페이지에서도 여전히 사용자와 웹 페이지 간의 상호 작용이 가능합니다.
쉬운 확장
html
파일임로 CDN을 통해 파일을 제공하거나 캐시에 저장하기 쉽습니다. 정적 페이지의 경우 별도의 연산 없이 정적 자원의 형태로 제공되기 때문에 서버에 부하를 거의 주지 않습니다.성능 최적화
html
을 렌더링하기 때문에 페이지를 요청해도 클라이언트나 서버가 처리할 필요가 없습니다. 서버가 쪽에 요구하는 데이터라고는 정적 html
을 마크업 내에 미리 렌더링한 내용 정도만 받아오면 됩니다. 따라서 각 요청별로 발생할 수 있는 지연시간을 최소화 할 수 있습니다.안전한 API 요청
export async function getStaticProps(){
const userRequest = await fetch('https://topdragon.co.kr/api/user/info');
const userData = await userRequest.json();
const productReq = await fetch('https://topdragon.co.kr/api/product/list');
const productData = await productReq.json();
return {
props : {
user: userData,
product : productData,
},
};
}
function IndexPage(props) {
return (
<div>
<Paradise
user={props.user}
product={props.product}
/>
</div>
)
}
export default IndexPage;
function IndexPage(props) {
const userRequest = await fetch('https://topdragon.co.kr/api/user/info', { cache: 'force-cache' });
const productReq = await fetch('https://topdragon.co.kr/api/user/info', { cache: 'force-cache' });
const userData = await userRequest.json();
const productData = await productReq.json();
return (
<div>
<Paradise
user={userData}
product={productData}
/>
</div>
)
}
웹 페이지를 만들어 배포를 하고나면 다음 배포 전까지는 내용이 변하지 않는다는 것입니다.
Next.js는 이문제를 해결하기 위해서 ISR과 같은 해결책을 냈습니다.
ISR을 사용하면 Next.js가 어느 정도의 주기로 정적 페이지를 다시 렌더링하고 해당 내용을 업데이트할지 정할 수 있습니다.
export async function getStaticProps(){
const userRequest = await fetch('https://topdragon.co.kr/api/user/info');
const userData = await userRequest.json();
const productReq = await fetch('https://topdragon.co.kr/api/product/list');
const productData = await productReq.json();
return {
props : {
user: userData,
product : productData,
},
revalidate: 600 // 시간을 초 단위로 나타낸 값 (10분)
};
}
function IndexPage(props) {
return (
<div>
<Paradise
user={props.user}
product={props.product}
/>
</div>
)
}
export default IndexPage;
function IndexPage(props) {
const userRequest = await fetch('https://topdragon.co.kr/api/user/info', { next: { revalidate: 10 } });
const productReq = await fetch('https://topdragon.co.kr/api/user/info', { next: { revalidate: 10 } });
const userData = await userRequest.json();
const productData = await productReq.json();
return (
<div>
<Paradise
user={userData}
product={productData}
/>
</div>
)
}