icon안동민 개발노트

CSS-in-JS 솔루션 (styled-components)


 CSS-in-JS는 JavaScript를 사용하여 컴포넌트의 스타일을 직접 정의하는 기법입니다.

 이 절에서는 Next.js에서 대표적인 CSS-in-JS 라이브러리인 styled-components의 사용법, 장단점, 그리고 서버 컴포넌트와의 호환성에 대해 알아보겠습니다.

styled-components 설정

  1. 패키지 설치 :
npm install styled-components
  1. .babelrc 파일 생성 (SSR 지원을 위해) :
{
  "presets": ["next/babel"],
  "plugins": [["styled-components", { "ssr": true }]]
}
  1. _document.js 파일 생성 (SSR 스타일 주입) :
import Document, { Html, Head, Main, NextScript } from 'next/document'
import { ServerStyleSheet } from 'styled-components'
 
export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const sheet = new ServerStyleSheet()
    const originalRenderPage = ctx.renderPage
 
    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: (App) => (props) =>
            sheet.collectStyles(<App {...props} />),
        })
 
      const initialProps = await Document.getInitialProps(ctx)
      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>
        ),
      }
    } finally {
      sheet.seal()
    }
  }
}

기본 사용법

import styled from 'styled-components'
 
const Button = styled.button`
  background-color: #0070f3;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 5px;
  cursor: pointer;
 
  &:hover {
    background-color: #0051a2;
  }
`
 
export default function MyComponent() {
  return <Button>Click me</Button>
}

동적 스타일링

 props를 사용하여 동적으로 스타일을 변경할 수 있습니다.

const Button = styled.button`
  background-color: ${props => props.primary ? '#0070f3' : '#ffffff'};
  color: ${props => props.primary ? 'white' : '#0070f3'};
  padding: 10px 20px;
  border: 2px solid #0070f3;
  border-radius: 5px;
`
 
export default function MyComponent() {
  return (
    <>
      <Button primary>Primary Button</Button>
      <Button>Secondary Button</Button>
    </>
  )
}

테마 적용

 ThemeProvider를 사용하여 전역 테마를 적용할 수 있습니다.

import { ThemeProvider } from 'styled-components'
 
const theme = {
  colors: {
    primary: '#0070f3',
    secondary: '#ff4081',
  },
  fonts: {
    main: 'Arial, sans-serif',
  }
}
 
const Button = styled.button`
  background-color: ${props => props.theme.colors.primary};
  font-family: ${props => props.theme.fonts.main};
`
 
export default function App({ Component, pageProps }) {
  return (
    <ThemeProvider theme={theme}>
      <Component {...pageProps} />
    </ThemeProvider>
  )
}

장단점

 장점

  1. 컴포넌트와 스타일의 강력한 결합
  2. 동적 스타일링 용이
  3. 자동 벤더 프리픽싱
  4. 중복 스타일 제거

 단점

  1. 런타임 오버헤드
  2. 서버 사이드 렌더링 설정 복잡성
  3. 기존 CSS 도구와의 호환성 문제

서버 컴포넌트와의 호환성

 styled-components는 클라이언트 컴포넌트에서만 사용 가능합니다. 서버 컴포넌트에서는 직접 사용할 수 없으며, 클라이언트 컴포넌트로 래핑하여 사용해야 합니다.

// ServerComponent.js
import ClientComponent from './ClientComponent'
 
export default function ServerComponent() {
  return <ClientComponent>Hello from server</ClientComponent>
}
 
// ClientComponent.js
'use client'
import styled from 'styled-components'
 
const StyledDiv = styled.div`
  color: blue;
`
 
export default function ClientComponent({ children }) {
  return <StyledDiv>{children}</StyledDiv>
}

7장 서버 컴포넌트와 클라이언트 컴포넌트 연관성

 CSS-in-JS 솔루션은 7장에서 다룬 클라이언트 컴포넌트와 밀접하게 연관됩니다. styled-components와 같은 라이브러리는 클라이언트 컴포넌트에서만 사용 가능하므로, 서버 컴포넌트와 함께 사용할 때는 주의가 필요합니다. 스타일이 필요한 부분을 클라이언트 컴포넌트로 분리하고, 서버 컴포넌트에서는 이를 import하여 사용하는 패턴을 적용해야 합니다.

실습 : 다크 모드 전환 가능한 레이아웃 구현

 다음 요구사항을 만족하는 다크 모드 전환 레이아웃을 구현해보세요.

  1. 라이트 / 다크 테마 정의
  2. 테마 전환 버튼 구현
  3. 테마에 따른 스타일 적용

 구현 예시:

// theme.js
export const lightTheme = {
  body: '#ffffff',
  text: '#000000',
  toggleBorder: '#ffffff',
  background: '#f4f4f4',
}
 
export const darkTheme = {
  body: '#363537',
  text: '#fafafa',
  toggleBorder: '#6B8096',
  background: '#999',
}
 
// ThemeToggle.js
'use client'
import { useState } from 'react'
import { ThemeProvider } from 'styled-components'
import { lightTheme, darkTheme } from './theme'
import styled from 'styled-components'
 
const ToggleButton = styled.button`
  background: ${({ theme }) => theme.background};
  color: ${({ theme }) => theme.text};
  border: 2px solid ${({ theme }) => theme.toggleBorder};
  padding: 10px;
  border-radius: 5px;
`
 
const Layout = styled.div`
  background: ${({ theme }) => theme.body};
  color: ${({ theme }) => theme.text};
  min-height: 100vh;
  padding: 20px;
`
 
export default function ThemeToggle({ children }) {
  const [theme, setTheme] = useState('light')
 
  const toggleTheme = () => {
    setTheme(theme === 'light' ? 'dark' : 'light')
  }
 
  return (
    <ThemeProvider theme={theme === 'light' ? lightTheme : darkTheme}>
      <Layout>
        <ToggleButton onClick={toggleTheme}>
          Toggle Theme
        </ToggleButton>
        {children}
      </Layout>
    </ThemeProvider>
  )
}
 
// page.js
import ThemeToggle from '../components/ThemeToggle'
 
export default function Home() {
  return (
    <ThemeToggle>
      <h1>Welcome to my app</h1>
      <p>This is a paragraph with theme-based styling.</p>
    </ThemeToggle>
  )
}

 이 실습을 통해 styled-components를 사용하여 테마를 정의하고, 동적으로 테마를 전환하는 방법을 경험할 수 있습니다. 이는 다크 모드 구현이나 사용자 정의 테마 기능을 개발할 때 유용한 패턴입니다.

 styled-components를 비롯한 CSS-in-JS 솔루션은 동적 스타일링과 테마 적용에 강점을 가지고 있지만, 서버 컴포넌트와의 호환성 및 성능 최적화에 주의가 필요합니다. 프로젝트의 요구사항과 팀의 선호도를 고려하여 적절한 스타일링 방식을 선택하는 것이 중요합니다.