개요
useWindow
라는 커스텀 훅을 만들어 isMobile
, isTablet
, isDesktop
으로 컴포넌트나 props를 분기시키며 개발하고 있었다.
// useWindow.ts
const useWindow = () => {
const isMobile = useRef<boolean>(false);
const isTablet = useRef<boolean>(false);
const isDesktop = useRef<boolean>(false);
const isClient = useRef<boolean>(false);
const [windowSize, setWindowSize] = useState<{ width: number; height: number }>({
width: 0,
height: 0,
});
useEffect(() => {
if (typeof window !== 'undefined') {
isClient.current = true;
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
isMobile.current = window.innerWidth < deviceWidthNumber.tablet;
isTablet.current =
window.innerWidth > deviceWidthNumber.tablet &&
window.innerWidth < deviceWidthNumber.desktop;
isDesktop.current = window.innerWidth > deviceWidthNumber.desktop;
};
window.addEventListener('resize', handleResize);
handleResize();
return () => window.removeEventListener('resize', handleResize);
}
}, []);
return {
...
};
};
export default useWindow;
단순하게 위 사진처럼, PC 화면에서는 aside 화면이 노출되는 상태이고, 모바일 화면에서는 햄버거 메뉴가 추가되어 이를 클릭하였을 때 aside 화면을 확인할 수 있는 구조이다.
useWindow
를 사용하였을 경우 브라우저 화면을 늘리고 줄일 때마다 UI가 즉시 반영된다. 하지만 이는 모든 초기 렌더링이 끝났을 경우의 이야기이고, 아래의 각 서비스 환경에 따라 초기 렌더링 시의 보이는 화면에 대한 차이를 이야기해보려고 한다.
각 초기 렌더링 CSR
CSR과 useEffect();
기본적인 CSR의 경우 첫 화면을 불러올시, 깡통 index.html
파일과 함께 해당 페이지에대한 번들링된 bundle.js
파일을 클라이언트에 던져주게 된다. 이는 JavaScript
파일이 동작하기 이전까지 사용자는 빈 화면을 볼 것이고, 크롤링 봇의 경우는 유용한 페이지가 아니라고 인식하고 지나갈 것이다. 물론 JavaScript
가 동작하고나면, 사용자가 어느 액션을 취한다거나 같은 서비스 내의 페이지를 이동시 화면이 빠르게 변경되는 것을 확인할 수 있을 것이다.
CSR useLayoutEffect();
여러 초급 React
개발자들이 자주 하는 실수가 적절한 상황에 적절한 hook
을 사용하지 않는 것이다. hook
을 많이 알고 있는 것에 따라 서비스의 리소스 최적화에 어느 정도 영향을 끼치는 것 같다. 지금과 같은 상황에서도 useWindow
라는 커스텀 훅을 useEffect
로 사용 중이다. 이를 useLayoutEffect
로만 바꾸어주어도, 초기 렌더링 속도를 꽤 줄일 수 있다.
SSR과 useEffect();
SSR
(server side rendering) 말 그대로 client
에서 요청을 하면, CSR
과는 다르게 server
에서 JavaScript
를 동작시켜, 1차적으로 렌더링을 끝낸 화면과 bundle.js
파일을 client
로 보내주게된다. 하지만, 이 과정에서 client server
에서는 요청한 client
의 window size
를 알 수 없기 때문에 SSR
이 원하는 대로 이루어지지 않고, client
에 렌더링 된 파일을 보내준다. 그 이후 동작은 기존 CSR
과 같다.
가끔 SSR이 왜 필요한지, 예전의 SSR과 다른게 뭔지 질문을 받아보았는데 이참에 필요한 이유를 정리해보려고 한다.
SSR의 변화
예전의 SSR
server
에서 html
template
파일을 갖고 있다가, client
에서 요청시 필요한 template
을 response
로 보내준다.
이는 페이지를 이동할 때 마다 비슷한 UI가 있어도 다시 모든 파일을 보내준다. 상태관리나 api call
의 경우 주로 jQuery나 Ajax로 하게되는데, 객체지향 프로그래밍이 어렵고, 코드가 전혀 암호화되지 않기 때문에 보안적으로 매우 취약한 상태가된다.
CSR
client
에서 요청 시 해당 프로젝트를 갖는 저장소에서 index.html
과 빌드 및 번들링 된 파일을 보내준다. 초기 렌더링이 조금 느리지만, 페이지 이동 시 같은 화면은 놔두고, 필요한 파일만 받아와서 화면을 업데이트할 수 있게 되었으며, 예전의 SSR
과 비교해서 코드를 객체 지향적으로 작성할 수 있게 되었고, 성능 최적화에 많은 이점을 얻게 되었다. 하지만, 기본적으로 index.html
에 넣어둔 opengraph
정보 말고는 크롤링 봇이 갖고갈 수 있는 정보가 전혀 없기 때문에, 다른 사이트들보다 좋은 콘텐츠를 갖고 있다고 해도 포털사이트들의 검색 결과에 상위노출 시키기가 어렵고, 다양한 소셜서비스에서 바이럴 효과를 얻고 싶어도, 미리보기 화면이 원하는 대로 만들어지지 않기 때문에, 클릭률이 낮아질 수밖에 없다.
CSR Pre-Rendering
위 CSR
의 단점을 보완하기 위해서 만들어진 기술로, 프로젝트 빌드 시점에 어느 정도의 원하는 페이지들을 미리 만들어두고, client
요청 시 미리 만들어둔 파일을 전달해준다. 당연하게도 초기 렌더링 시간이 없어지고, 크롤링 봇또한 의도한 콘텐츠를 읽어 들여 포털사이트의 검색 결과의 순위가 올라갈 수도 있고, 소셜서비스의 바이럴 효과 클릭률도 올라가게 된다.
“그럼 Pre-Rendering
하면 되지, 왜 신규 SSR
이 나와서 저장소가 아니라 서버를 써야 하냐?””라고 묻는다면, 만약 우리 서비스에서 잘 보여주고 싶은 페이지가 1억 개를 가지고 있고, 그것을 모두 Pre-Rendering
하게 된다고 한다면, 빌드 시간이 엄청나게 불어나게 되고, 어느 페이지의 콘텐츠가 업데이트되었다고 한다면, 이미 Pre-Rendering
된 파일은 업데이트되지 않는다.
신규 SSR
위 모든 단점이 보완되어 즉, 객체 지향적으로 프로그래밍할 수 있고, 코드를 쉽게 암호화할 수 있으며, 기본적인 콘텐츠를 client server
가 서비스의 server
에서 불러온 다음에 렌더링하여 크롤링 봇의 인식 및 소셜 바이럴에 문제가 없고, 페이지 이동 시 필요한 파일만 줄 수 있고, Pre-Rendering
기술도 사용할 수 있어서 저장된 페이지를 바로 client
에 건네줄 수 있으며, 한 번 요청된 페이지를 캐싱시키고 캐싱 된 페이지를 다음부턴 바로 보내줄 수도 있다.
추가로 더 알아본다면, react
의 최신 기술인 hydration
이라는 기술이 있는데, 사용자가 보고 있는 컴포넌트만 client
에 보내주고, 스크롤 등 화면을 이동 시에 추가로 필요한 컴포넌트를 받아와서 렌더링시키는 방법이다. 이를 SSR에 적용하면, 크롤링 봇이 hydration
된 컴포넌트를 인식하지 못한다고 생각할 수 있는데, 화면 렌더링은 시켜두되 해당 컴포넌트의 번들링 된 JavaScript
파일은 화면상에 보였을 때 받아오는 신기술이 있다.
https://nextjs.org/docs/advanced-features/dynamic-import
즉, nextjs는 무적이고 최강이다.
각 초기 렌더링 SSR
SSR과 useLayoutEffect();
SSR과 css @media screen
결국 SSR
에서 반응형 웹 개발을 할 때 hook
의 의존성을 줄이고, css
의 @media screen
으로 개발하면, 더욱 나은 SSR 개발을 할 수 있다.