โ† ๋ชจ๋“  ๊ธ€

next.jsssr

Styled components with SSR

React์—์„œ ์Šคํƒ€์ผ์„ ์ž…ํžˆ๋Š” ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ๊ฐ€์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ์ค‘์—์„œ CSS-in-JS๋Š” JavaScript๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ์˜ ์Šคํƒ€์ผ์„ ์ •์˜ํ•˜๋Š” ํŒจํ„ด์ž…๋‹ˆ๋‹ค. styled-components๋‚˜ emotion๊ณผ ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์ด ์ด ํŒจํ„ด์„ ๊ตฌํ˜„ํ•˜๊ณ โ€ฆ

2025.02.151 min read

React์—์„œ ์Šคํƒ€์ผ์„ ์ž…ํžˆ๋Š” ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ๊ฐ€์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ์ค‘์—์„œ CSS-in-JS๋Š” JavaScript๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ์˜ ์Šคํƒ€์ผ์„ ์ •์˜ํ•˜๋Š” ํŒจํ„ด์ž…๋‹ˆ๋‹ค. styled-components๋‚˜ emotion๊ณผ ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์ด ์ด ํŒจํ„ด์„ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์€ ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง์— ์ตœ์ ํ™”๋˜์–ด ์žˆ์–ด, Next.js์™€ ๊ฐ™์€ SSR ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ช‡ ๊ฐ€์ง€ ์ถ”๊ฐ€์ ์ธ ์„ค์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

์Šคํƒ€์ผ ์ˆ˜์ง‘

์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง ์‹œ, ์ดˆ๊ธฐ HTML์„ ์ƒ์„ฑํ•˜๋Š” ์‹œ์ ์— ๋ชจ๋“  styled-components์˜ ์Šคํƒ€์ผ์„ ์ˆ˜์ง‘ํ•˜์—ฌ ํฌํ•จ์‹œ์ผœ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ServerStyleSheet๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค:

์—ฌ๊ธฐ์„œ ์‚ฌ์šฉ๋œ useServerInsertedHTML ํ›…์€ Next.js์—์„œ ์ œ๊ณตํ•˜๋Š” ํŠน๋ณ„ํ•œ ํ›…์œผ๋กœ, SSR ๊ณผ์ •์—์„œ HTML์„ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์„œ๋ฒ„์—์„œ ์ƒ์„ฑ๋œ ์Šคํƒ€์ผ์ด ์ดˆ๊ธฐ HTML ์‘๋‹ต์— ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

tsx
export const StyledComponentRegistry = ({ children }: PropsWithChildren) => {
  const [styleSheet] = useState(() => new ServerStyleSheet());

  useServerInsertedHTML(() => {
    const styles = styleSheet.getStyleElement();
    styleSheet.instance.clearTag();
    return <>{styles}</>;
  });

  if (typeof window !== 'undefined') return <>{children}</>;

  return <StyleSheetManager sheet={styleSheet.instance}>{children}</StyleSheetManager>;
};

export default function RootLayout({
  children,
}: Readonly<{
  children: ReactNode;
}>) {
  return (
    <html lang="en">
      <body>
        <StyledComponentRegistry>
          {children}
        </StyledComponentRegistry>
      </body>
    </html>
  );
}

ํด๋ž˜์Šค ์ด๋ฆ„ ์ผ๊ด€์„ฑ ๋ณด์žฅ

CSS-in-JS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์Šคํƒ€์ผ๋งˆ๋‹ค ๊ณ ์œ ํ•œ ํ•ด์‹œ๊ฐ’์„ ํด๋ž˜์Šค ์ด๋ฆ„์œผ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง๊ณผ ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ๊ณผ์ •์—์„œ ์ด ํด๋ž˜์Šค ์ด๋ฆ„๋“ค์ด ์ผ์น˜ํ•ด์•ผ ํ•˜๋Š”๋ฐ, ์ด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ํ•ด๊ฒฐ๋ฉ๋‹ˆ๋‹ค:

  1. Next.js์˜ ์ปดํŒŒ์ผ๋Ÿฌ ์„ค์ •์„ ํ†ตํ•ด ๋นŒ๋“œ ์‹œ์ ์— ํด๋ž˜์Šค ์ด๋ฆ„์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
javascript
const nextConfig = {
  compiler: {
    styledComponents: {
      // ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ๋””๋ฒ„๊น…์„ ์œ„ํ•œ ํด๋ž˜์Šค๋ช… ์‚ฌ์šฉ
      displayName: process.env.NODE_ENV === 'development',
      // SSR ์ง€์› ํ™œ์„ฑํ™”
      ssr: true,
      // ๋” ์ž‘์€ ๋ฒˆ๋“ค ์‚ฌ์ด์ฆˆ๋ฅผ ์œ„ํ•œ ์„ค์ •
      minify: true
    }
  }
};
  1. ServerStyleSheet๊ฐ€ ์„œ๋ฒ„ ์‚ฌ์ด๋“œ์—์„œ ์Šคํƒ€์ผ์„ ์ˆ˜์ง‘ํ•˜๊ณ , ์ด๋ฅผ ์ดˆ๊ธฐ HTML์— ํฌํ•จ์‹œํ‚ต๋‹ˆ๋‹ค.
  2. ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ์—์„œ๋„ ๋™์ผํ•œ ํ•ด์‹œ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ™์€ ํด๋ž˜์Šค ์ด๋ฆ„์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ์„ค์ •๊ณผ ๋ฉ”์ปค๋‹ˆ์ฆ˜๋“ค์ด ํ•จ๊ป˜ ์ž‘๋™ํ•˜์—ฌ Styled-components ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ Next.js์˜ SSR ํ™˜๊ฒฝ์—์„œ ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.