nextjs 이전 버전에서는 _app과 _document 파일에 GlobalStyle을 적용해주면 됐는데, nextjs13 버전에서는 _app과 _document파일을 사용하지 않아서 새롭게 GlobalStyle을 적용해주는 방법을 찾아보았다.
createGlobalStyle을 이용해서 GlobalStyle 객체를 생성한다. 이곳에 적용할 global style을 다 넣어주면 된다. 나는 styled-component가 지원하는 styled-reset을 불러와서 내가 정의하고자 하는 global style과 함께 넣어주었다.
그리고 폴더 구조는 개개인에 따라 다른데, 내 경우에는 style 폴더를 앱 폴더와 분리하는 것을 선호해서 app폴더와 따로 styles 폴더를 만들었다. 내 스케폴딩은 다음과 같다.
├── node_modules
├── public
├── src
│ ├──app
│ │ ├──layout.tsx
│ │ ├──page.tsx
│ ├──components
│ ├──styles
│ │ ├──global-style.ts
│ │ ├──registry.tsx
├── eslint.json
├── .gitignore
├── next.config.js
├── ...
// src/styles/global-styles.ts
import { createGlobalStyle } from "styled-components";
import { reset } from "styled-reset";
export const GlobalStyle = createGlobalStyle`
${reset}
* {
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
min-width: 1200px;
background-color: #ffffff;
font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI;
font-size : 16px;
color: rgb(58, 58, 58);
}
ul, ol {
list-style: none;
}
button {
all: unset;
cursor: pointer;
}
`;
Next.js와 같은 서버 사이드 렌더링 애플리케이션에서 styled-components를 사용할 때 스타일이 클라이언트 측에서 React 애플리케이션이 다시 로드될 때 일관되게 유지되어야 하는 문제가 있다. 이를 위해 styled-components에서 제공하는 StyleSheetManager 컴포넌트를 사용할 수 있다.
기존 pages 디렉터리 방식에서는 server side rendering을 위해 아래 코드를 추가하여 사용했다.
// _document.tsx
export default class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
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()
}
}
}
하지만 app 디렉토리에서는 _app과 _document는 사용하지 않기 때문에 위 방식으로는 불가능하다.
따라서 Next.js 공식문서에서는 다음과 같은 방법을 제안한다. StyleSheetManager와 같은 styled-components API를 사용하여 렌더링 중 사용되는 모든 CSS rule을 모아놓을 global registry 컴포넌트를 만들고, useServerInsertedHTML함수를 사용해서 해당 style을 root의 layout 파일의 head 태그 안에 주입하도록 한다.
First, use the styled-components API to create a global registry component to collect all CSS style rules generated during a render, and a function to return those rules. Then use the useServerInsertedHTML hook to inject the styles collected in the registry into the head HTML tag in the root layout.
// lib/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>
);
}
다음과 같이 위 코드에서 정의한 style registry파일의 StyledComponentsRegistry 컴포넌트를 불러와 body 태그 아래에 넣어주면 글로벌 스타일 적용이 완성된다.
// app/layout.tsx
import StyledComponentsRegistry from './lib/registry';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>
</body>
</html>
);
}
위와 동일한 로직으로 내 파일구조에 맞게 global style을 적용시켰다.
// src/styles/registry.tsx
"use client"
import { useServerInsertedHTML } from "next/navigation"
import React, { useState } from "react"
import { ServerStyleSheet, StyleSheetManager } from "styled-components"
import { GlobalStyle } from '@/styles/global-style';
export const Registry = ({ children }: { children: React.ReactNode }) => {
const [sheet] = useState(() => new ServerStyleSheet())
useServerInsertedHTML(() => {
const styles = sheet.getStyleElement() // style 태그를 돔에 생성하기 위한 리엑트 엘리먼트들을 배열형태로 반환합니다.
sheet.instance.clearTag()
return <>{styles}</>
})
if (typeof document !== "undefined") {
return <>{children}</>
}
return (
<StyleSheetManager sheet={sheet.instance}>
<GlobalStyle />
{children}
</StyleSheetManager>
)
}
// src/app/layout.tsx
'use client';
import { Registry as StyledComponentRegistry } from "@/styles/registry"
import { QueryClient, QueryClientProvider } from 'react-query';
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'] });
const queryClient = new QueryClient();
export const metadata: Metadata = {
title: '서랍',
description: 'Generated by create next app',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang='ko'>
<body className={inter.className}>
<QueryClientProvider client={queryClient}>
<StyledComponentRegistry>{children}</StyledComponentRegistry>
</QueryClientProvider>
</body>
</html>
);
}
https://wikidocs.net/197760
https://velog.io/@jay/Next.js-13-master-course-Styling
https://nextjs.org/docs/app/building-your-application/styling/css-in-js