0. 왜 작성하는가?
- 왜 SSR인가? 왜 Nextjs인가?
- 문제의 블로그 ... Hydration Style Issue
1. Nextjs
1. JSX 문법으로 SSR 할 수 있는 프레임워크
- 왜? 사용하나? 무엇이 이점일까?
- 사용자 친화적?
Code Split, Skeleton UI 활용하면 CRA로 충분히 가능하지 않을까?
- 더 빠른 화면을 보여준다 - 깜박이는 현상을 제거할 수 있다 (js 다운받아 html 생성하기 때문)
- SEO?
최근에는 CSR도 가능하게 만든다고 한다.
- SEO 점수를 높여야지 검색시, 먼저 노출되기 떄문 - HTML 페이지의 정보가 필요하는데...없다(CSR)
- 사용자 친화적?
- Nextjs 동작원리
- 기본적으로 모든 페이지를 pre-Render를 제공하고 있고,(서버에서 HTML 생성)
- 이러한 페이지는 먼저 client에게 노출 후,(깜빡임 현상 제거 및 SEO 적용)
- page에 필요한 js를 다운로드하여 적용한다.(Hydration)
2. CRA와 SSR의 Loading 속도 차이
인터넷 속도가 느린곳은 SSR을 지원할 충분한 증거 0. Loading 시점
- DomContentLoaded : Render Tree 완성 시점
- Loaded: 리소스(JS, CSS, 이미지) 다운로드 후 적용 시점
-
Static 파일(단순 문자열)
- Throttle이 심해질수록(fast 3G -> slow 3G) Loading 시점의 차이가 큼 - reactjs - nextjs
Code
const Lorem = () => { return ( <div> {Array(1000) .fill(1) .map((v, index) => { return ( <div key={index}> <p> Lorem ipsum dolor sit amet, consectetur adipisicing elit. A corporis dolorum enim fugiat, harum illo impedit in inventore nostrum placeat, porro possimus quam quibusdam quisquam reprehenderit similique sint totam velit? </p> </div> ) })} ) </div> ) }
-
API 요청시
- Throttle이 심해질수록(fast 3G -> slow 3G) Loading 시점의 차이가 큼
- reactjs
- nextjs
Code
// next export const getStaticProps: GetStaticProps = async () => { const response = await fetch( 'https://jsonplaceholder.typicode.com/todos' ) const todos: Todo[] = await response.json() return { props: { todos, }, } } function About({ todos }: Props) { return ( <div> {todos.map((todo, index) => { return ( <div key={index}> <p>{todo.title}</p> </div> ) })} ) </div> ) } // react const App = () => { const [todos, setTodos] = useState([]) useEffect(() => { fetch('https://jsonplaceholder.typicode.com/todos') .then((response) => response.json()) .then((json) => setTodos(json)) }, []) return ( <div> {todos.map((todo, index) => { return ( <div key={index}> <p>{todo.title}</p> </div> ) })} ) </div> ) }
- Throttle이 심해질수록(fast 3G -> slow 3G) Loading 시점의 차이가 큼
2. Hydration
Server에서 받은 HTML에 Event를 달아주자
1. Server의 ReactDOMServer
JSX는 HTML이 아니다... HTML로 바꿔주는 Method
ReactDOMServer.renderToString(element)
renderToString()
- Next(Node)에서 만든 Component(JSX)를 HTML로 만듬
2. Client의 ReactDOM
ReactDOM은 아직 unMount상태이다!!
ReactDOM.hydrate(element, container[, callback])
hydrate()
- HTML(SSR) 이후 다운로드 받은 JS로
ReactDom
(unMounted 상태)을 생성 - HTML과
ReactDOM
을 비교하여, Event를 등록한다.- Hydrate의 역할은 이벤트를 달아주는거다.
- React Dom과 text, attribute(style, dataset)가 달라도 변경되지 않는다.
- 이러한 이슈는 dev환경에서 Error로 노출된다.
- 해결방법은 간단하다 → 한번 더 렌더링 하면된다.
- 장점: 문제해결
- 단점: 두번 렌더링 → 리소스, 시간 듬 → SSR할 필요가ㅏ..
- HTML(SSR) 이후 다운로드 받은 JS로
3. React Hydration Error
Server(HTML)랑 Client(ReactDom) 달라? 에러(dev환경)
- React.hydration 함수는 Server와 Client가 동일하다고 생각하고 이벤트를 등록
function MyComponent() {
const color = typeof window !== 'undefined' ? 'red' : 'blue'
return <h1 style={{ color }}>Hello World!</h1>
}
export default MyComponent
//console.error
// Warning: Prop `style` did not match. Server: "color:blue" Client: "color:red"
- 문제의 코드
const Home = () => {
return (
<>
<SSR />
<CSR />
<Always />
</>
)
}
export default function SSR() {
if (process.browser) return null
return <div style={{ color: 'red' }}>SSR</div>
}
export default function CSR() {
const [visible, setVisible] = useState(false)
useEffect(() => setVisible(true), [])
if (!visible) return null
return <div style={{ color: 'blue' }}>CSR</div>
}
export default function Always() {
return <div style={{ color: 'green' }}>Always green component</div>
}
-
작동원리
설명은 할 수 있으나, 100% 이해못함..1. Server에서 만든 HTML은 `SSR`,`ALWAYS` 컴포넌트 노출 2. Client JS로 만든 ReactDOM은 `ALWAYS`만 노출 -- hydration — 3. React는 당연히 같은 태그니까 1 === 3 태그는 같다고 판단하여 tag를 그대로 두고 textContent를 교체 - 속성은 변경되지 않는다. 4. CSR의 useEffectf로 인해 추가!
<>
<div></div> //ssr - 1
<div></div> //always - 2
</>
<>
<div></div> always - 3
</>