자바스크립트의 발전에 힘입어 개발자들은 어떤 시도를 해보기로 합니다. 서버에서 최소한의 HTML을 한번만 받고, 나머지를 자바스크립트를 이용해 그려내기로요. 왜 했을까요? 기존 방식인 MPA(Multi Page Application)에서는 새로운 화면을 보려면 화면을 새하얗게 날리고, 서버에서 새로운 HTML을 받아와 문서의 Asset(image, video, ...)을 다운로드하곤 했습니다. 서버에 요청할 때마다 페이지 전체를 새로 렌더링하는 것은 비효율적이라 생각했습니다. 이에 필요한 데이터만 받아아고, 해당 부분만 변경하도록 한 것이죠.
CSR(Client-side Rendering)이란 JavaScript로 클라이언트에서 HTML 페이지를 생성하고 브라우저에서 페이지를 렌더링하는 것입니다. 클라이언트에서 logic, data fetching, templating, routing을 처리하지요. SSR(Server-side Rendering)과 비교하면 결과적으로 서버로부터 더 많은 데이터를 전달받습니다.
브라우저의 렌더링 방식
1. 최초 통신시 필요한 재원을 브라우저가 확보한다.
- body 태그가 비어있는 index.html을 서버로부터 받는다.
- head의 필요한 자원을 서버로부터 다시 받아온다.
2. 그려야하는 화면에 맞게 재료를 조립하며
- JavaScript로 DOM API를 활용해 태그를 그려넣는다.
3. 추가 자원은 필요할 때 서버에서 받아온다.
- fetch를 통해 JSON 객체로 받아온다.
- innerHTML을 변경하는 방식으로 해당 부분의 데이터만 갈아끼운다.
장점 및 단점
데이터의 양이나 처리할 logic이 많지 않은 경우, 서버로부터 최소한의 HTML을 받기 때문에 페이지를 더 빠르게 로드할 수 있습니다. 각 페이지마다 전체 HTML이 아닌 데이터나 일부분만 수정하면 되기 때문입니다. 반대로 애플리케이션이 커지면 JavaScript의 양이 증가하는 경향이 있습니다. 처리할 로직과 데이터 양이 많은 경우, 렌더링하는데 부정적인 영향을 미칠 수 있습니다. 이를 방지하기 위해 필요할 때 필요한 것만 제공할 수 있도록 코드를 분할할 수 있습니다. 그리고 반복적으로 작업될 수 있는 부분을 캐싱하면, 첫 렌더링 이후 성능을 향상시킬 수 있습니다.
js를 실행하지 않는 검색 엔진 크롤러는 빈 데이터 또는 로딩 상태만 보게 됩니다. js를 실행한다고 하더라도, 사용자의 기기 성능 혹은 네트워크 상태에 따라 js 실행을 방해 받을 수 있습니다. 이는 SEO를 위한 콘텐츠 크롤링과 인덱싱에 부정적인 영향을 줍니다.
React에서의 작동
브라우저가 서버로부터 최소한의 HTML 페이지와 JavaScript, CSS를 전달 받으면, JavaScript가 DOM을 업데이트하고 페이지를 렌더링합니다. 애플리케이션이 처음 로드될 때 딜레이가 발생하는데, 이는 아직 페이지가 완전히 렌더링 되지 않았기 때문입니다. 이후 애플리케이션 내 속도는 새로 패치될 데이터만 필요하고, 그 부분만 리렌더링 되면 되기 때문에 빠르게 렌더링할 수 있습니다.
Next.js에서 CSR 적용 방법
- useEffect hook
import React, { useState, useEffect } from 'react'
export function Page() {
const [data, setData] = useState(null)
useEffect(() => {
const fetchData = async () => {
const response = await fetch('https://api.example.com/data')
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const result = await response.json()
setData(result)
}
fetchData().catch((e) => {
// handle the error as needed
console.error('An error occurred while fetching the data: ', e)
})
}, [])
return <p>{data ? `Your data: ${data}` : 'Loading...'}</p>
}
- SWR library (recommended)
import useSWR from 'swr'
export function Page() {
const { data, error, isLoading } = useSWR(
'https://api.example.com/data',
fetcher
)
if (error) return <p>Failed to load.</p>
if (isLoading) return <p>Loading...</p>
return <p>Your Data: {data}</p>
}
next.js에서 swr library를 권장하는 이유가 궁금해서 swr의 github 코드를 까봤습니다.
우선, 다음과 같이 캐시 기능이 내장되어 있습니다. 코드를 살펴보면 요청 중복을 방지하고, 캐시된 데이터와 새로 받아온 데이터를 비교해서 불필요한 로직 실행을 줄여 빠르고 가볍게 데이터를 받아옵니다. useEffect를 통해 직접 구현을 하는걸 상상하니, 머리가 아득하네요..
// swr > core > src > use-swr.ts
// ... previous code
const [getCache, setCache, subscribeCache, getInitialCache] =
createCacheHelper<
Data,
State<Data, any> & {
// The original key arguments.
_k?: Key
}
>(cache, key)
// ... next code
또한, React 버전 업데이트에 따라 발생할 수 있는 이슈를 swr 팀이 관리해줍니다. 버전 업에 따른 코드 관리에서도 조금은 자유로워질 수 있겠네요!
// swr > core > src > use-swr.ts
// ... previous code
/*
For React 17
Do unmount check for calls:
If key has changed during the revalidation, or the component has been
unmounted, old dispatch and old event callbacks should not take any
effect
For React 18
only check if key has changed
https://github.com/reactwg/react-18/discussions/82
*/
const callbackSafeguard = () => {
if (IS_REACT_LEGACY) {
return (
!unmountedRef.current &&
key === keyRef.current &&
initialMountedRef.current
)
}
return key === keyRef.current
}
// ... next code
출처
- CSR: https://nextjs.org/docs/pages/building-your-application/rendering/client-side-rendering
- Rendering on the Web: https://web.dev/rendering-on-the-web/
- What is the Client-Side Rendering and how it works by Riccardo Andreatta: https://ferie.medium.com/what-is-the-client-side-rendering-and-how-it-works-c90210e2cd14
- SPA with CSR by 오종택님: https://lean-mahogany-686.notion.site/1-3-SPA-with-CSR-bbb351fd7f39452eb7e80da8201be963
- Rendering: https://nextjs.org/learn/foundations/how-nextjs-works/rendering
'Next.js' 카테고리의 다른 글
[Next.js] 라우팅과 네비게이션의 동작 (0) | 2024.06.14 |
---|---|
CSR부터 RSC까지 렌더링 방식의 변화 (0) | 2024.06.08 |
SSR(Server-side Rendering) (0) | 2023.06.30 |