React와 webpack으로 SSR이 구현된 코드베이스에서 React.Suspense 컴포넌트를 사용하려다가 위와 같은 에러를 마주하였다.
import React, { useState, Suspense } from 'react';
import styles from './style.scss';
import GetData from './components/GetData';
import ErrorBoundary from './asyncHandler/ErrorBoundary';
import ErrorComponent from './asyncHandler/ErrorComponent';
import LoadingComponent from './asyncHandler/LoadingComponent';
import SSRCompatibleSuspense from './asyncHandler/SSRCompatibleSuspense';
function AsyncTest() {
return (
<ErrorBoundary renderFallback={({ error }) => <ErrorComponent error={error} />} resetKey={resetKey}>
<Suspense fallback={<LoadingComponent />} >
<GetData />
</Suspense>
</ErrorBoundary>
);
}
export default AsyncTest;
문제의 원인은 React로 SSR을 구현하기 위해 사용한 ReactDOMServer.renderToString
에서 Suspense 컴포넌트를 지원하지 않았기 때문이었다.
참고로 ReactDOMServer.renderToString
는 React 엘리먼트의 초기 HTML을 렌더링하며 HTML 문자열을 반환시킨다. 이렇게 서버사이드에서 렌더링 된 마크업이 있는 노드에서는 ReactDOM.hydrate()
를 호출해 위의 마크업을 보존하고 이벤트 핸들러만 연결함으로써 첫 로드 성능을 향상시킬 수 있다.
그럼 이 문제를 어떻게 해결하면 좋을까 ?
Suspense컴포넌트를 커스텀해서 서버사이드환경에선 전달받은 fallback 컴포넌트를 렌더링 할 수 있도록, 클라이언트사이드 환경에선 Suspense 컴포넌트를 적용시키도록 Suspense컴포넌트 불러오는 시점이 페이지가 마운트 된 이후가 되도록 커스텀을 해보았다.
SSRCompatibleSuspense.jsx
import React, { Suspense } from 'react';
import useMounted from 'hooks/useMounted';
export default function SSRCompatibleSuspense(props) {
const isMounted = useMounted();
if (isMounted) {
return <Suspense {...props} />;
}
return <>{props.fallback}</>;
}
useMounted.js
import React from 'react';
function useMounted() {
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => {
setMounted(true);
}, []);
return mounted;
}
export default useMounted;
AsyncTest.jsx
import React, { useState } from 'react';
import styles from './style.scss';
import GetData from './components/GetData';
import ErrorBoundary from './asyncHandler/ErrorBoundary';
import ErrorComponent from './asyncHandler/ErrorComponent';
import LoadingComponent from './asyncHandler/LoadingComponent';
import SSRCompatibleSuspense from './asyncHandler/SSRCompatibleSuspense';
function AsyncTest() {
return (
<ErrorBoundary renderFallback={({ error }) => <ErrorComponent error={error} />} resetKey={resetKey}>
<SSRCompatibleSuspense fallback={<LoadingComponent />} >
<GetData />
</SSRCompatibleSuspense>
</ErrorBoundary>
);
}
export default AsyncTest;
이를 통해 위 에러를 해결할 수 있었다! 동일한 에러에 대해 라이브러리나 프레임워크의 config를 이용해 해결할 수도 있었을 것 같은데, 순수 React로 SSR을 구현한 해당 프로젝트에서는 위와 같은 방식으로 에러를 해결하였다.