리액트 하이드레이션
참고 링크: https://react.dev/reference/react-dom/client/hydrateRoot
- React 18 버전에서 hydrate()는 더 이상 쓰지 않음
=> hydrateRoot()로 바뀜- CSR(CRA)에서의 createRoot()
- 형태:
hydrateRoot(domNode, reactNode)
- 실제 사용:
hydrateRoot(document.getElementById('root'), <App />
)- 반환값: render()와 unmount()를 포함한 객체
- render() 사용하기:
root.render(<App />);
- 하이드레이션된 리액트 root 내부에서 리액트 컴포넌트 갱신
- unmount() 사용하기:
root.unmount();
- 트리에 있는 모든 컴포넌트를 언마운트하고, root DOM 노드로부터 리액트 '분리(detach)'
블로그에서 어떤 사람이 작성한 모든 글을 한 페이지에 렌더링하는 경우
function IndexPage() {
return <div>여기는 index 페이지입니다.</div>;
}
export default IndexPage;
export async function getServerSideProps() {
const userRequest = await fetch('https://example.com/api/user');
const userData = await userRequest.json();
return {
props: {
user: userData,
},
};
}
function IndexPage(props) {
return <div>안녕하세요, {props.user.name}님!</div>;
}
export default IndexPage;
getServerSideProps 예약 함수 사용
서버 API를 미리 호출하여 동적 렌더링에 필요한 값(userData) 호출
자바스크립트 번들을 서버에서 전송 받은 후 클라이언트에서 렌더링
<script>
)와 스타일(<link>
)을 포함된 기본 HTML 마크업 <div id="root"></div>
뿐빌드 과정 동안 컴파일한 JS 파일과 CSS 파일을 HTML 페이지에서 불러오고 <div id="root"></div>
안에 불러온 내용을 렌더링
=> 특정 컴포넌트를 브라우저(클라이언트)에서만 렌더링하도록 하는 방식으로 해결 가능
Highlight.js 라이브러리 적용
import Head from 'next/head';
import hljs from 'highlight.js';
import javascript from 'highlight.js/lib/languages/javascript';
function Highlight({ code }) {
hljs.registerLanguage('javascript', javascript);
hljs.initHighlighting();
return (
<>
<Head>
<link rel='stylesheet' href='/highlight.css' />
</Head>
<pre>
<code className='js'>{code}</code>
</pre>
</>
);
}
export default Highlight;
hljs 호출 부분을 useEffect로 감싸준다.
import { useEffect } from 'react'; // 추가
import Head from 'next/head';
import hljs from 'highlight.js';
import javascript from 'highlight.js/lib/languages/javascript';
function Highlight({ code }) {
// useEffect로 hljs 로직을 감싸주기
useEffect(() => {
hljs.registerLanguage('javascript', javascript);
hljs.initHighlighting();
}, []);
return (
<>
<Head>
<link rel='stylesheet' href='/highlight.css' />
</Head>
<pre>
<code className='js'>{code}</code>
</pre>
</>
);
}
export default Highlight;
import { useState, useEffect } from 'react';
import Highlight from '../components/Highlight'; // // 위 Highlight 컴포넌트를 import
function UseEffectPage() {
const [isClient, setIsClient] = useState(false); // useState 추가
// 렌더링 잉후 useState 상태 변경
useEffect(() => {
setIsClient(true);
}, []);
return (
<div>
// useState 상태에 따라 Highlight 컴포넌트 함수 실행
{isClient && (
<Highlight code={"console.log('하이')"} language='js' />
)}
</div>
);
}
export default UseEffectPage;
이제 Highlight 컴포넌트는 브라우저(클라이언트)에서만 렌더링된다.
// 예전 코드
function IndexPage() {
const side = process.browser ? 'client' : 'server';
return <div>현재 {side} 사이드 렌더링 중입니다</div>
}
export default IndexPage;
처음에는 '현재 server 사이드 렌더링 중입니다'를 표시하다가
리액트 하이드레이션이 끝나면 바로
'현재 client 사이드 렌더링 중입니다'로 바뀜
좀 더 정확한 의미를 갖는 typeof window 사용
// 지금 코드
function IndexPage() {
const side = typeof window === 'undefined' ? 'server' : 'client';
return <div>현재 {side} 사이드 렌더링 중입니다</div>
}
export default IndexPage;
window 객체가 정의되지 않으면(undefined이면) 서버 사이드,
window 객체가 존재하면(undefined가 아니면) 클라이언트 사이드
import dynamic from 'next/dynamic'; // next/dynamic에서 import
// Highlight 컴포넌트를 dynamic 모듈을 활용하여 가져옴
// 형태: dynamic(callback, options)
const Highlight = dynamic(
() => import('../components/Highlight'),
{ ssr: false },
);
import styles from '../styles/Home.module.css';
function DynamicPage() {
return (
<div className={styles.main}>
// 동적으로 가져온 Highlight 컴포넌트
<Highlight code={"console.log('바이')"} language='js' />
</div>
);
}
ssr: false
옵션검색 엔진에 노출될 필요가 없는 웹 페이지를 만들 때
Incremental Static Regeneration의 약자
동적 컨텐츠를 제공하지만 해당 데이터를 가져오는 데 아주 오랜 시간이 걸리는 웹 페이지
=> SSR 또는 CSR을 사용하면 끔찍한 사용자 경험 초래
import fetch from 'isomorphic-unfetch';
import Dashboard from './components/Dashboard';
export async function getStaticProps() {
const userReq = await fetch('/api/user');
const userData = await userReq.json();
const dashboardReq = await fetch('/api/dashboard');
const dashboardData = await dashboardReq.json();
return {
props: {
user: userData,
dashboard: dashboardData,
},
revalidate: 600 // 시간 지정 (주의: second 단위 / 1분은 60 => 600은 10분)
};
}
function IndexPage(props) {
return (
<div>
<Dashboard user={props.user} dashboard={props.dashboard} />
</div>
);
}
export default IndexPage;
다양한 렌더링 방법을 선택할 수 있다는 점은 Next.js를 사용하는 중요한 이유이기도 하다.