React Component 라이브러리는 SSR 대응을 어떻게 하고 있을까?

okkkkkky·2023년 7월 10일
1

이 글은 제가 사내 블로그에 게시하였던 글을 옮겨왔습니다
2022년 10월 기준으로 작성된 글로, 언급된 라이브러리들의 내용이 업데이트 되었을 수 있으니 참고하여 읽어주시길 바랍니다.

사내 디자인 시스템을 개발하며, Storybook을 통해 컴포넌트 테스트를 진행하는 업무가 있었습니다. 작업을 하면서, 컴포넌트들의 단순 반응형 테스트는 CSR로 진행된다는 한계가 있었기에, Next.js 기반의 자사 웹사이트에 대응하고자 “Storybook에서 어떻게 SSR 테스트를 할 수 있을까?”라는 생각을 하게 되었습니다.

useLayoutEffect와 SSR의 연관성에 대해

SSR 테스트에 대해 고민하기 이전에, 왜 테스트가 필요한지, 현재 디자인 시스템에서 사용되고 있는 컴포넌트 내부에 존재하는 useLayoutEffect와 SSR에 대해 생각해볼 필요가 있습니다.
우선 useLayoutEffect에 대해서 간단하게 짚고 넘어가도록 하겠습니다.

useEffect

useLayoutEffect는 사용자들의 경험을 향상시키기 위해 DOM이 paint되기 전 실행되는 React hook입니다. 클래스형 컴포넌트의 리액트 생애주기였다면, componentDidMountcomponentDidUpdate의 단계에 실행되게 됩니다. 따라서 SSR 환경에서 무분별하게(?) useLayoutEffect를 사용하는 경우 아래와 같은 경고문구를 마주치게 됩니다.

“⚠️ Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer’s output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://fb.me/react-uselayouteffect-ssr for common fixes.”

위의 내용에 따라, useLayoutEffect는 서버가 렌더링하는 형식에 맞게 변환(encode)되지 못하고, 이로 인해 SSR 환경에서 생성된 초기 HTML과 hydration된 이후의 최종 HTML이 서로 맞지 않아 경고가 발생한 것임을 알 수 있습니다.

즉, script 태그를 통해 자바스크립트가 모두 다운로드 되어, client 사이드에서 렌더링되는 시점에 useLayoutEffect가 실행되기 때문에, hydration이 진행되지 않은 SSR에서는 useLayoutEffect가 제대로 사용할 수 없다는 의미가 됩니다.

일단 SSR 환경에서 컴포넌트가 렌더링 되어야 하는데,

당시 개발단계에서 위와 같은 이유로, useLayoutEffect를 사용하는 컴포넌트가 SSR에서도 문제없이 렌더링되어야 하는 이슈가 발생하게 되었습니다. useLayoutEffect를 포함하여 useEffect가 시행되는 시점이 SSR이 시행되고 난 이후임을 이용하여, 컴포넌트의 리렌더링을 일으키는 side effect를 만들어 SSR과 CSR이 다르게 렌더링 되도록 하였습니다.

// 컴포넌트 내부
const FooComponent = () => {
  // SSR에서는 false로 유지
  // -> SSR이후 hydration 과정에서 useEffect를 통해 isShown이 true로 변경된 후, CSR에 맞는 리렌더링 발생
  const [isShown, setIsShown] = useState(false);
  
  // 컴포넌트의 리렌더링을 일으키는 side effect
  useEffect(() => {
    setIsShown(true);
  }, []);
  
  // only-server JSX
  if (!isShown) return null;
  
  // only-client JSX
  return (...)
}

그렇다면 어떻게 SSR 환경일 때 컴포넌트를 테스트해 볼 수 있을까?

컴포넌트 내부에서 간단한 SSR 대응은 이렇게 해주었지만, 실제로 SSR 환경일때 컴포넌트를 어떻게 테스트할 수 있을지에 대해 여전히 고민이 있었습니다. 다행히(?) Storybook에서는 SSR 환경에서의 테스트 지원을 위해 @storybook/server라는 패키지를 제공하고 있었습니다.

사내에서 개발된 디자인 시스템과 관련된 내용은 아니지만, 임시로 만들어진 button 컴포넌트를 SSR 환경 내 렌더링 테스트하기 위해 @storybook/server와 Next.js, 그리고 Emotion을 사용하여 아래와 같이 SSR인 경우, CSR인 경우를 분리하여 테스트를 진행하였습니다. SSR, CSR 환경을 구분짓기 위해, 위에서 언급한 useEffect를 통한 분기처리 방식을 이용하여, SSR 환경에서의 컴포넌트 렌더링 및 컴포넌트 내 prop 변화에 따른 렌더링까지 체크할 수 있다는 것을 확인하였습니다.

Storybook

(같은 ButtonView 컴포넌트이지만, 왼쪽의 경우, SSR 환경에서의 storybook, 오른쪽은 CSR 환경으로 다르게 렌더링되도록 설정하였습니다)

export default function ButtonView({
  label = "default",
  size,
  backgroundColor,
}) {
  const [isServer, setIsServer] = useState(true);

  useEffect(() => {
    setIsServer(false);
  }, []);

  // only-server JSX
  if (isServer) {
    return (
      <div>
        <h1>ButtonView</h1>
        <Button size={size} backgroundColor={backgroundColor}>
          {label}
        </Button>
      </div>
    );
  }

  // only-client JSX
  return <div>This is Browser!!!!!</div>;
}

이렇게 @storybook/server와 함께 SSR 환경에서의 컴포넌트 테스트를 진행해본 후, 테스트는 일단락 되었지만, 다른 React component 라이브러리는 어떻게 SSR 테스트를 진행하고 있는지, 어떻게 SSR에 대응하고 있는지, 추가적인 리서치의 필요성이 생기게 되어 아래의 내용에 대해 확인할 수 있었습니다.

React Component 라이브러리들의 SSR 지원 및 테스트 현황

Material UI

Materia UI는 CSS-in-JS인 Emotion를 기반으로 구성된 라이브러리입니다. Materia UI는 SSR 지원을 위해 @emotion/react의 CacheProvider를 사용하고, Provider 내부의 value값, 즉 전역 Context에는 @emotion/cache의 createCache를 통해 만들어진 인스턴스가 할당되게 됩니다.
createCache는 말그대로 style에 대한 내용을 memoize하여 캐싱하는 역할을 합니다. SSR 환경 감지를 위해 아래와 같은 부가적인 분기처리를 진행하여 주고, 이 값이 전역 Context로 관리되어 컴포넌트 별로 CacheProvider를 통해 해당 값을 구독하게 됩니다.

let isBrowser = typeof document !== ‘undefined’;let getServerStylisCache = isBrowser ? undefined :;

Emotion의 createCache 테스트코드는 jest와 mocha를 통한 node 테스트로 이루어집니다.

Radix

Radix는 SSR을 지원하지만, 별도의 test를 지원하지는 않고, playground를 별도로 만들어 직접 SSR을 경험할 수 있는 사이트를 운영하고 있었습니다.

Radix는 @radix-ui/react-use-layout-effect라는 별도 라이브러리를 출시하였는데요, 해당 라이브러리 내부의 기능중 하나인 useLayoutEffect의 경우, SSR에 대응하기 위해 useLayoutEffect를 아래와 같이 globalThis.document에 따라 다르게 동작하는 별도의 hook으로 사용하고 있었습니다.

const useLayoutEffect = Boolean(globalThis?.document)
  ? React.useLayoutEffect
  : () => {};

export { useLayoutEffect };

Bootstrap

Bootstrap에서는 Material UI와 비슷한 방식으로 SSRProvider라는 Provider로 SSR 지원을 위한 전역 Context를 관리 하고 있었습니다.

이외에도 useIsSSR이라는 별도의 커스텀 hook등을 사용하여, SSR 환경과 관련된 Context와 함께 window의 타입을 통해 undefined가 아닌 경우 useLayoutEffect를 통해 SSR 환경 여부에 대한 감지를 하는 등의 방식으로 SSR 지원을 하고 있었습니다.

export function useIsSSR(): boolean {
  let cur = useContext(SSRContext);
  let isInSSRContext = cur !== defaultContext;
  let [isSSR, setIsSSR] = useState(isInSSRContext);

  // If on the client, and the component was initially server rendered,
  // then schedule a layout effect to update the component after hydration.

  if (typeof window !== "undefined" && isInSSRContext) {
    // eslint-disable-next-line react-hooks/rules-of-hooks
    useLayoutEffect(() => {
      setIsSSR(false);
    }, []);
  }

  return isSSR;
}

Ant design

Ant design공식문서에는 express engine과 함께 server 파일 관점에서의 설명이 적혀있었고, SSR(대표적으로 Next.js)에 대응하기 위해 config 파일을 수정하거나 최상단 컴포넌트에서 antd.less 파일을 import하여 사용할 수 있도록 하였습니다. 이외에 useLayoutEffect와 관련된 이슈들이 제기되었고, 이를 document의 타입에 따라 useEffect, useLayoutEffect로 분기처리하여 export하는 별도 커스텀 hook을 추가하여 SSR 대응에 지원했다는 것을 확인할 수 있었습니다.

import { useEffect, useLayoutEffect } from "react";

export default typeof document !== "undefined" ? useLayoutEffect : useEffect;

Chakra

SSR 지원을 위해, ChakraProvider는 전역 Context인 EnvironmentContext와 해당 Context를 컴포넌트에서 구독할 수 있도록 돕는 EnvironmentProvider로 구성되어 있습니다. Provider 내부에는 아래와 같이 window의 타입에 따라 defaultEnv 변수값을 설정해주고, 이 값을 기준으로 context API의 기본값을 만들어주는 역할을 합니다. EnvironmentProvider는 앞서 설명한 Lazy 렌더링을 기반으로 hidden 처리된 span 태그의 렌더링 분기처리를 하며 SSR에 대응하고 있었습니다.

const defaultEnv: Environment =
  typeof window !== "undefined" ? { window, document } : mockEnv
...

export function EnvironmentProvider(props: EnvironmentProviderProps) {
	...

	const [mounted, setMounted] = useState(false)
  useEffect(() => setMounted(true), [])

	...

  return (
    <EnvironmentContext.Provider value={context}>
      {children}
      {!environmentProp && mounted && <span hidden ... />}
    </EnvironmentContext.Provider>
  )
}

위의 Provider 대한 테스트는 storybook으로 진행되었고, EnvironmentProvider가 잘 감싸주고 있는지에 대한 테스트였기에 리서치의 의도와는 다소 다른 관점에 맞춰진 내용이있었습니다.

참고로 use-is-safe-layout-effect라는, globalThis.document에 따라 SSR에 추가 대응할 수 있는 커스텀 hook도 있었습니다.

import { useEffect, useLayoutEffect } from "react";

export const useSafeLayoutEffect = Boolean(globalThis?.document)
  ? useLayoutEffect
  : useEffect;

Fluent

Fluent는 CSS-in-JS인 griffel기반으로 작업된, Microsoft에서 만든 디자인 시스템입니다. @fluentui/react에서는 SSR 지원을 위해, SSRProvider라는, SSR 환경과 관련된 전역 Context를 사용하기 위한 Provider를 제공합니다. 이 Provider는 useSSRContext, useIsSSR을 중심으로 구성되어 있는데, useSSRContext의 경우 SSR 환경에 대한 Context 값을 return해주며, useIsSSRuseSSRContext의 return값과 default value값이 다른지에 대한 여부의 boolean flag를 기반으로 SSR인지 아닌지를 판별해줍니다.

import * as React from 'react';
import { canUseDOM } from './canUseDOM';

export const defaultSSRContextValue: SSRContextValue = {
  current: 0,
};

export const SSRContext = React.createContext<SSRContextValue | undefined>(undefined) as React.Context<SSRContextValue>;

export function useSSRContext(): SSRContextValue {
  return React.useContext(SSRContext) ?? defaultSSRContextValue;
}

export const SSRProvider: React.FC = props => {
  const [value] = React.useState<SSRContextValue>(() => ({ current: 0 }));

  return <SSRContext.Provider value={value}>{props.children}</SSRContext.Provider>;
};

export function useIsSSR(): boolean {
  const isInSSRContext = useSSRContext() !== defaultSSRContextValue;
  const [isSSR, setIsSSR] = React.useState(isInSSRContext);

	...

  if (canUseDOM() && isInSSRContext) {
    // eslint-disable-next-line
    React.useLayoutEffect(() => {
      setIsSSR(false);
    }, []);
  }

  return isSSR;
}

이와는 별개로, 아래와 같이 SSR 대응을 위해 window의 타입에 따라 DOM을 사용할 수 있는지 없는지에 대한 boolean값을 return해주는 커스텀 hook도 있었습니다.

export function canUseDOM(): boolean {
  return (
    typeof window !== "undefined" &&
    !!(
      window.document &&
      // eslint-disable-next-line deprecation/deprecation
      window.document.createElement
    )
  );
}

위의 내용에 대한 테스트는 testing-library를 통한 node 테스트를 진행하고 있었으며, SSRProvider가 없는 상태에서 useIsSSR을 실행시키는 경우에 대한, 즉, SSRProvider가 컴포넌트를 감싸지 않는 경우에 대한 테스트였습니다.

NextUI

NextUI는 Stitches, React Aria를 기반으로 만들어진 React component 라이브러리입니다. Next.js에서 사용하기 위해, NextUIProvider를 사용하여야 하는데요, 해당 Provider는 다른 라이브러리들과 비슷하게, @react-aria/ssr의 SSRProvider, Context API를 사용하여, useSSR이라는 커스텀 훅을 사용하여 return값을 기준으로 SSR 환경에 따른 렌더링 분기처리를 하며 대응하고 있었습니다.

import { SSRProvider } from "@react-aria/ssr";
import useSSR from "../use-ssr";
...

const ThemeProvider: React.FC<PropsWithChildren<ThemeProviderProps>> = ({
...
}) => {
  const {isBrowser} = useSSR();

	...

  const providerValue = useMemo<NextUIThemeContext>(() => {
    const themeTokens = isBrowser ? getDocumentCSSTokens() : {};
    ...
  }, [currentTheme, isBrowser]);

  ...

  useEffect(() => {
    if (!isBrowser || !userTheme) {
      return;
    }
		...
  }, [isBrowser, userTheme]);

  return (
    <SSRProvider>
      <OverlayProvider>
        <ThemeContext.Provider value={providerValue}>
          {!disableBaseline && <CssBaseline />}
          {children}
        </ThemeContext.Provider>
      </OverlayProvider>
    </SSRProvider>
  );
};

NextUI의 useSSR은 아래와 같이 window의 타입을 기준으로 browser가 맞는지 아닌지를 판별하며 SSR 환경을 감지하고 있습니다.

import {useEffect, useState} from "react";

const isBrowser = (): boolean => {
  return Boolean(
		typeof window !== "undefined" && window.document && window.document.createElement
	);
};

...

const useSSR = (): SSRState => {
  const [browser, setBrowser] = useState<boolean>(false);

  useEffect(() => {
    setBrowser(isBrowser());
  }, []);

  return {
    isBrowser: browser,
    isServer: !browser,
  };
};

export default useSSR;

NextUIProvider와 관련된 스토리북에서는 다크모드의 적용여부에 대해서만 테스트를 진행할 수 있었고, SSR에 대한 테스트 내용은 확인하기 어려웠습니다.

Mantine

Mantine은 Emotion을 기반으로 한 라이브러리로, Material UI와 마찬가지로 emotionCache를 활용한 MantineProvider를 사용하여 SSR 환경에 대해 지원하고 있었습니다.

이와는 별개로 SSRWrapper라는 컴포넌트가 있었는데요, RichTextEditor라는 컴포넌트를 window의 타입에 따라 분기처리하여 return 해주는 역할로, 해당 컴포넌트역시 별도의 테스트는 진행되고 있지 않았습니다.

import React from "react";
import type { RichTextEditorProps } from "@mantine/rte";

export function SSRWrapper(props: RichTextEditorProps) {
  if (typeof window !== "undefined") {
    // eslint-disable-next-line import/extensions, global-require
    const { RichTextEditor } = require("@mantine/rte");
    return <RichTextEditor {...props} />;
  }

  return null;
}

Rebass

역시 Emotion을 기반으로 만들어진 라이브러리입니다. SSR과 관련된 내용은 공식문서에 Emotion을 기반으로 만들어졌기 때문에, 관련 라이브러리를 참고하라는 내용이 기재되어있었습니다. 실제로, Rebass에서 사용하는 ThemeProvider는 emotion-theming이라는 라이브러리에서 제공되는 Provider였습니다.

Headless UI

Tailwind 팀에서 관리하는 Headless UI는 별도의 isServer라는 SSR 대응용 변수가 있었는데 역시 아래와 같이 window, document 타입으로 결정되는 flag였고, 이를 기반으로 useEffectuseLayoutEffect를 분기처리하며 SSR 환경에 지원하고 있었습니다. useIsoMorphicEffect는 각 컴포넌트 내부에서 사용되고 있었으며, 별도로 SSR에 대한 컴포넌트들의 Storybook 테스트는 확인되지 않았습니다.

// ssr.ts
export const isServer =
  typeof window === "undefined" || typeof document === "undefined";

//use-iso-morphic-effect.ts
import { useLayoutEffect, useEffect } from "react";
import { isServer } from "../utils/ssr";

export let useIsoMorphicEffect = isServer ? useEffect : useLayoutEffect;

이외에도 Next.js기반의 playground가 있었는데, _app.tsx 파일 내부에 OS별 key display를 구분해주는 useKeyDisplay라는 유틸함수에서 mounted라는 boolean flag를 기준으로 분기처리를 하여 SSR에 대응하는 것을 확인할 수 있었습니다.

function useKeyDisplay() {
  let [mounted, setMounted] = useState(false)

  useEffect(() => {
    setMounted(true)
  }, [])

  if (!mounted) return {}
  let isMac = ...
  return isMac ? KeyDisplayMac : KeyDisplayWindows
}

Evergreen

glamor와 ui-box기반으로 만들어진 Evergreen은 Rebass와 마찬가지로, 해당 라이브러리들의 공식문서에서 확인할 수 있다고 적혀있었습니다. SSR을 지원하는 함수로는 1) ui-box의 extractStyles, glamor의 renderStatic을 기반으로 구성된 Evergreen만의 extractStyles 유틸함수, 2) glamor의 rehydrate과 ui-box의 hydrate을 기반으로 만들어진 hydrate, autoHydrate 유틸함수가 있었습니다.

특히, autoHydrate의 경우 canUseDom이라는 변수 기준으로 hydrate의 여부를 결정하여 SSR 환경에 대응하고 있었습니다.

// autoHydrate.js
export default function autoHydrate() {
  if (canUseDom) {
		// extractStyles를 통해 id값이 evergreen-hydrate로 지정됩니다.
    const hydration = document.querySelector('#evergreen-hydrate')

    if (hydration) {
      ...
    }
  }
}

// canUseDom.js
export default Boolean(typeof window !== 'undefined' && window.document)

위의 extractStyles, autoHydrate은 testing-library를 통해 테스트가 진행되었습니다.

// autoHydrate.test.js
describe("autoHydrate", () => {
  it("should hydrate", () => {
    render(<Box height={16} />);
    const result = extractStyles();
    expect(() => {
      hydrate(result.cache);
    }).not.toThrow();
  });
});

// extractStyles.test.js
describe("extractStyles", () => {
  it("returns styles for a Box", () => {
    render(<Box height={11} />);
    const result = extractStyles({ nonce: "abcd1234" });
    expect(result).toMatchSnapshot();
    expect(result.css).toBeTruthy();
    expect(result.cache.uiBoxCache).toBeTruthy();
    expect(result.cache.glamorIds).toBeTruthy();
    expect(result.hydrationScript).toBeTruthy();
  });

  it("returns styles for a Button", () => {
    render(<Button />);
    const result = extractStyles();
    expect(result).toMatchSnapshot();
  });
});

grommet

styled-components를 기반으로 만들어진 grommet은 다른 라이브러리들과 마찬가지로 별도의 커스텀화된 useLayoutEffect를 window의 타입에 따라 처리하며 SSR에 대응하는 기능을 제공하고 있었습니다. 해당 커스텀 훅을 사용하는 컴포넌트들에서는 스냅샷 및 Storybook 테스트를 진행하고 있었으나 별도의 SSR 환경에서의 테스트는 확인할 수 없었습니다.

import { useLayoutEffect as vanillaUseLayoutEffect, useEffect } from "react";

export const useLayoutEffect =
  typeof window !== "undefined" ? vanillaUseLayoutEffect : useEffect;

export default useLayoutEffect;

요약

너무 길어서 다 읽지 못하신 분들을 위해 위의 내용을 요약하자면 아래와 같습니다.

라이브러리SSR 대응방식참고
Material UI
Emotion의 CacheProvider 사용
⭐️ document의 타입에 따른 isBrowser 변수를 통해 SSR 분기처리
Radix
@radix-ui/react-use-layout-effect 라이브러리 별도 출시
⭐️ globalThis.document의 타입에 따른 useLayoutEffect 커스텀 훅 사용
SSR로 구성된 playground repo가 있음
Bootstrap
@react-spectrum/ssr의 SSRProvider 사용
⭐️ window 타입에 따라 useIsSSR, canUseDom 변수를 통해 SSR 분기처리
Ant Design
⭐️ document 타입에 따라 useLayoutEffect, useEffect 분기 처리
Next.js 내부 config 파일 수정 및 antd.less 파일 직접 import
    Chakra
    EnvironmentProvider를 기반으로 구성된 ChakraProvider를 사용
    ⭐️ Lazy Rendering과 함께 window 타입에 기반한 context API의 기본값 설정
    use-is-safe-layout-effect이라는 커스텀 훅도 있음
    (globalThis.document 타입으로 useLayoutEffect, useEffect 분기 처리)
    storybook테스트가 있으나, 컴포넌트 렌더링 여부에 대한 테스트가 아님
    Fluent
    useSSRContext, useIsSSR 기반의 SSRProvider를 사용
    ⭐️ useSSRContext의 return값과, default value값의 비교를 기준으로 SSR 여부 확인
    testing-library 기반으로 SSRProvider 유무에 따른 useIsSSR 실행 결과 테스트
    NextUI
    NextUIProvider를 사용
    ⭐️ @react-aria/ssr의 SSRProvider, context API로, useSSR이라는 커스텀 훅을 사용하여 분기처리
    useSSR은 window 타입을 통한 브라우저 여부 판단
    NextUIProvider는 Storybook 내부에서 다크모드 적용에 대한 테스트였고, SSR 지원은 아니었음.
    Mantine
    Emotion의 emotionCache를 기반으로 MantineProvider를 사용.
    이와는 별개로 RichTextEditor라는 컴포넌트를 window의 타입에 따라 분기처리 해주는 SSRWrapper가 있음
    별도 테스트 없음
    Rebass
    Emotion의 ThemeProvider를 사용
    Headless UI
    ⭐️ window, document 타입 모두 체크하는 isServer 변수를 통해 useIsoMorphicEffect 커스텀 훅 사용
    이외에도 _app.tsx 파일 내부 key display를 결정하기 위해 Lazy Rendering 활용
    SSR로 구성된 playground repo가 있음
    Evergreen
    glamor, ui-box를 활용한 extractStyles, autoHydrate 유틸함수를 사용
    ⭐️ window 타입과 document 유무를 기준으로 canUseDom 변수를 사용
    testing-library 기반으로 테스트
    Grommet
    ⭐️ window 타입 기반으로 useLayoutEffect, useEffect 분기처리를 해줌
    커스텀 훅을 사용하는 컴포넌트들의 스냅샷, Storybook 테스트는 진행되었으나, SSR 환경에서의 테스트는 없었음.

    그래서 SSR 대응이나 테스트는 앞으로 어떻게 하나요?

    전체적으로 React component 라이브러리들은 Storybook을 통한 SSR 환경에서의 테스트보다는 SSR에 대응해주는, 전역 Context값을 가지고 있는 Provider에 대한 jest, mocha 테스트를 진행하고 있었습니다. 또한, 이러한 테스트를 진행하지 않는 경우, 컴포넌트 내부에서 document나 window에 대한 분기처리를 통해 커스텀화된 useLayoutEffect를 사용하거나 boolean flag를 따로 만들어 SSR에 대응하고 있었습니다.

    만약 SSR 대응에 대한 테스트를 진행하고자 하는 필요가 발생하는 경우, 아래의 3가지 방법으로 시행할 수 있을 것입니다.

    1. 서버 환경에서의 테스트를 위해 SSR 전용 Provider를 만들어서 node 자체에 대한 테스트를 하는 방법
    2. 별도의 테스트를 진행하지 않고, 컴포넌트 내부에서 useLayoutEffect를 사용하는 경우에 대비하여 별도의 커스텀화된 useLayoutEffect를 만들어 사용하는 방법
    3. @storybook/server와 Next.js를 사용하는 방법

    처음 시도했던 @storybook/server 라이브러리를 사용하는 방식이 제일 우선순위의 SSR 환경에서의 컴포넌트 테스트일 것이지만, 우선 현재 작업완료된 모든 컴포넌트들에 대해 간단하게 SSR 대응을 보완하고자, 이 글의 초반부에 언급했던 useEffect를 통한 리렌더링 방식으로 진행하였습니다. 추후 관련 컴포넌트 뿐만 아니라 실제 프로덕트에 사용되는 컴포넌트에 커스텀화된 useLayoutEffect를 사용하거나 @storybook/server를 사용한 테스트를 조금 더 확장하여 진행하여야 할 것 같습니다.

    관련하여 보완이 필요한 내용이 있거나 다른 점을 발견하셨다면 언제든 의견 부탁 드립니다 🙇‍♀️

    참고
    https://beta.reactjs.org/apis/react/useEffect#displaying-different-content-on-the-server-and-the-client
    https://reactjs.org/docs/hooks-reference.html#uselayouteffect
    https://github.com/donavon/hook-flow/blob/master/hook-flow.png
    https://gist.github.com/gaearon/e7d97cdf38a2907924ea12e4ebdf3c85

    0개의 댓글