끄적이는 개발노트
Next.js 15 - Styled-Components 본문
Next.js 에서 css를 다루는 방법에는 기본적인 built-in 방식과 Styled Component, TailwindCSS 방식이 대표적일 것이다.
물론, Next.js에서는 기본적으로 create app을 할 때부터 TailwindCSS 적용 옵션을 설정하면 기본 세팅을 해줄 정도로 가장 편리하고 공식적으로 추천하는 방법이다. (본인 역시 TailwindCSS를 선호한다.)
하지만, Styled-Components 역시 아직까지 꽤나 사용되는 방법이기에 적용 방법을 정리해둔다.
1. 작동 방식
Styled-Components는 css-in-js 방식으로 작동한다.
CSS-in-JS?
Javascript 코드 내에 css를 생성하는 로직이 담기는 방식이다.
그렇기 때문에 변수에 접근이 가능하여 props와 같은 형태로 dynamic하게 스타일을 적용할 수 있다.
하지만, 미리 모든 스타일을 적용하기 때문에 렌더링이 더 오래걸릴 수도 있다.
이렇게 보면 별 문제는 없어보이지만, Next.js는 기본적으로 SSR 형식이다. 또한, 15버전에 들어오면서 server component로 이루어져있는데 이와 호환성이 굉장히 떨어진다. Styled-Components는 클라이언트가 런타임일 때, styledsheet를 생성하고 DOM에 주입한다. 따라서 SSR 방식에서 동작하게 되면 서버에서 HTML이 미리 생성되는데 style은 런타임 때 생성되고 주입되기 때문에 깜빡임이 발생한다. 그렇기 때문에 공식문서에서도 CSS-in-JS 방식에 대해 경고를 하고 있다.
2. 해결을 위한 설정
next.config.js
// next.config.js
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
compiler: {
styledComponents: true,
},
};
export default nextConfig;
- SWC 를 통해 활성화
SWC (Speedy Web Compiler)
Rust로 제작된 매우 빠른 자바스크립트 컴파일러
JS/TS 트랜스파일/컴파일
src/lib/registry.tsx
src 폴더 아래에 lib/registry.tsx 파일을 생성하고 아래와 같이 작성해준다.
// registry.tsx
"use client";
import React, { useState } from "react";
import { useServerInsertedHTML } from "next/navigation";
import { ServerStyleSheet, StyleSheetManager } from "styled-components";
export default function StyledComponentsRegistry({
children,
}: {
children: React.ReactNode;
}) {
// Only create stylesheet once with lazy initial state
// x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet());
useServerInsertedHTML(() => {
const styles = styledComponentsStyleSheet.getStyleElement();
styledComponentsStyleSheet.instance.clearTag();
return <>{styles}</>;
});
if (typeof window !== "undefined") return <>{children}</>;
return (
<StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
{children}
</StyleSheetManager>
);
}
위는 공식문서 코드로 useServerInsertedHTML hook을 통해 렌더링 시 생성된 Styled-Components의 스타일을 수집하고 head에 삽입하는 역할을 한다.
layout.tsx
// layout.tsx
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import StyledComponentsRegistry from "@/lib/registry";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={`${geistSans.variable} ${geistMono.variable}`}>
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>
</body>
</html>
);
}
root layout 파일에 생성한 style registry로 wrapping 한다.
이에 대한 전체적인 과정을 공식문서에서 아래와 같이 설명하고 있다.
알아두면 좋은 정보
- 서버 렌더링 중에 스타일은 전역 레지스트리로 추출되어 <head>HTML에 반영됩니다. 이를 통해 스타일 규칙이 해당 규칙을 사용하는 콘텐츠보다 먼저 배치됩니다. 향후 React에서 추가될 기능을 사용하여 스타일을 어디에 삽입할지 결정할 수 있습니다.
- 스트리밍 중에 각 청크의 스타일이 수집되어 기존 스타일에 추가됩니다. 클라이언트 측 하이드레이션이 완료되면 styled-components평소처럼 작업을 이어받아 추가적인 동적 스타일을 주입합니다.
- 스타일 레지스트리 트리의 최상위 레벨에 클라이언트 컴포넌트를 사용하는 이유는 CSS 규칙을 추출하는 것이 더 효율적이기 때문입니다. 이렇게 하면 후속 서버 렌더링 시 스타일을 다시 생성할 필요가 없고, 서버 컴포넌트 페이로드에 스타일이 포함되는 것을 방지할 수 있습니다.
- 스타일이 적용된 구성 요소 컴파일의 개별 속성을 구성해야 하는 고급 사용 사례의 경우 Next.js 스타일이 적용된 구성 요소 API 참조를 읽어 자세히 알아보세요.
3. 코드
styles 폴더에 styledComponents.ts 파일을 생성한다.
// styledComponent.ts
import styled, { css } from "styled-components";
export const Wrapper = styled.div`
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #000000;
`;
export const Title = styled.h1`
font-size: 64px;
color: white;
`;
export const TestDiv = styled.div`
display: flex;
justify-content: center;
align-items: center;
width: 200px;
background-color: gray;
margin: 4px;
padding: 4px;
`;
export const Title2 = styled.p<{ $large?: boolean }>`
${(props) =>
props.$large
? css`
color: white;
font-size: 32px;
`
: css`
color: black;
font-size: 20px;
`}
${TestDiv}:hover & {
color: orange;
}
`;
export const TestDiv2 = styled(TestDiv)`
background-color: blue;
`;
가볍게 살펴보자면,
- 방식은 기본 styled 선언과 동일
- styled 객체와 함께 custom하고자 하는 tag를 수정(Tagged Templated Literal 방식)
- props를 통해 dynamic style 설정
- 각각의 css 값을 props(ex. width, height)로 변경
- 위와 같이 임의의 변수를 받고 그 값에 따라 여러 css를 한번에 변경 가능
- 백틱 안에서 : &와 같은 기호와 같은 가상선택자 사용 가능
- styled 객체에서 위에 선언한 component를 상속받아서도 사용 가능
// page.tsx
"use client";
import {
TestDiv,
TestDiv2,
Title,
Title2,
Wrapper,
} from "@/styles/styledComponent";
export default function Home() {
return (
<Wrapper>
<Title>hi</Title>
<TestDiv>
<Title2 $large>test</Title2>
</TestDiv>
<TestDiv2>
<Title2>test</Title2>
</TestDiv2>
</Wrapper>
);
}
"use client"를 추가해 클라이언트 컴포넌트임을 명시해야 한다.
4. 실행
아래와 같이 잘 실행되는 것을 확인할 수 있다.
다만, TailwindCSS가 따로 class명과 사용방식에 있어 러닝커브가 있고 코드가 다소 지저분해지더라도 TailwindCSS가 성능적으로나 사용면에서나 훨씬 편리하고 효율적인 것 같다...
'JavaScript > Next.js' 카테고리의 다른 글
Next.js 15 - 전역 상태 Context API (0) | 2025.04.24 |
---|---|
Next.js - Font 적용 (0) | 2025.04.23 |
Next.js - userAgent 정보 확인 (0) | 2023.06.23 |
Next.js 결제모듈 연동 - 포트원(구 아임포트) (2) (0) | 2023.06.19 |
Next.js 결제모듈 연동 - 포트원(구 아임포트) (1) (1) | 2023.06.19 |