Next.js의 글로벌 대 로컬 스타일링
게시 됨: 2022-03-10저는 Next.js를 사용하여 복잡한 프런트 엔드 프로젝트를 관리하는 좋은 경험을 했습니다. Next.js는 JavaScript 코드를 구성하는 방법에 대한 의견이 있지만 CSS를 구성하는 방법에 대한 기본 제공 의견은 없습니다.
프레임워크 내에서 작업한 후 Next.js의 지도 철학을 따르고 CSS 모범 사례를 실행한다고 생각하는 일련의 조직 패턴을 찾았습니다. 이 기사에서는 이러한 패턴을 보여주기 위해 함께 웹사이트(찻집!)를 구축할 것입니다.
참고 : React에 대한 기본적인 이해가 있고 새로운 CSS 기술을 배우는 데 개방적이면 좋겠지만 아마도 이전 Next.js 경험이 필요하지 않을 것입니다.
"구식" CSS 작성
Next.js를 처음 살펴볼 때 일종의 CSS-in-JS 라이브러리 사용을 고려하고 싶을 수 있습니다. 프로젝트에 따라 이점이 있을 수 있지만 CSS-in-JS는 많은 기술적 고려 사항을 도입합니다. 번들 크기에 추가되는 새 외부 라이브러리를 사용해야 합니다. CSS-in-JS는 전역 상태에 대한 추가 렌더링 및 종속성을 유발하여 성능에 영향을 줄 수도 있습니다.
추천 자료 : Aggelos Arvanitakis의 "최신 CSS-in-JS 라이브러리의 보이지 않는 성능 비용(React Apps)"
게다가 Next.js와 같은 라이브러리를 사용하는 요점은 가능한 한 자산을 정적으로 렌더링하는 것이므로 CSS를 생성하기 위해 브라우저에서 실행해야 하는 JS를 작성하는 것은 그다지 의미가 없습니다.
Next.js 내에서 스타일을 구성할 때 고려해야 할 몇 가지 질문이 있습니다.
프레임워크의 규칙/모범 사례에 어떻게 맞출 수 있습니까?
"글로벌" 스타일 문제(글꼴, 색상, 기본 레이아웃 등)와 "로컬" 스타일(개별 구성 요소에 관한 스타일)의 균형을 어떻게 맞출 수 있습니까?
첫 번째 질문에 대해 내가 생각해낸 대답은 단순히 좋은 구식 CSS를 작성 하는 것입니다. Next.js는 추가 설정 없이 지원합니다. 또한 성능이 좋고 정적인 결과를 산출합니다.
두 번째 문제를 해결하기 위해 다음 네 부분으로 요약할 수 있는 접근 방식을 취합니다.
- 디자인 토큰
- 글로벌 스타일
- 유틸리티 클래스
- 구성요소 스타일
나는 여기에서 CUBE CSS ("구성, 유틸리티, 차단, 예외")에 대한 Andy Bell의 아이디어에 감사드립니다. 이 조직 원리에 대해 들어본 적이 없다면 공식 사이트나 Smashing Podcast의 기능을 확인하는 것이 좋습니다. 우리가 CUBE CSS에서 취할 원칙 중 하나는 CSS 캐스케이드를 두려워하기보다는 수용 해야 한다는 아이디어입니다. 이러한 기술을 웹사이트 프로젝트에 적용하여 배워봅시다.
시작하기
우리는 차 가게를 지을 것입니다. 왜냐하면, 차는 맛있기 때문입니다. 우리는 새로운 Next.js 프로젝트를 만들기 위해 yarn create next-app
을 실행하여 시작할 것입니다. 그런 다음, styles/ directory
에 있는 모든 것을 제거할 것입니다(모두 샘플 코드임).
참고 : 완성된 프로젝트를 따라하고 싶다면 여기에서 확인할 수 있습니다.
디자인 토큰
거의 모든 CSS 설정에서 모든 전역 공유 값을 변수에 저장 하면 분명한 이점이 있습니다. 클라이언트가 색상 변경을 요청하는 경우 변경을 구현하는 것은 대규모 찾기 및 바꾸기가 아니라 한 줄로 이루어집니다. 결과적으로 Next.js CSS 설정의 핵심 부분은 모든 사이트 전체 값을 디자인 토큰 으로 저장하는 것입니다.
내장된 CSS 사용자 정의 속성을 사용하여 이러한 토큰을 저장합니다. (이 구문에 익숙하지 않은 경우 "CSS 사용자 정의 속성에 대한 전략 안내서"를 참조하십시오.) 저는 (일부 프로젝트에서) 이 목적으로 SASS/SCSS 변수를 사용하기로 선택했음을 언급해야 합니다. 실질적인 이점을 찾지 못했기 때문에 일반적으로 다른 SASS 기능(믹스인, 반복, 파일 가져오기 등)이 필요한 경우에만 프로젝트에 SASS를 포함합니다. 대조적으로 CSS 사용자 정의 속성은 캐스케이드와도 작동하며 정적으로 컴파일하는 대신 시간이 지남에 따라 변경할 수 있습니다. 따라서 오늘 은 일반 CSS를 사용하겠습니다 .
styles/
디렉토리에서 새 design_tokens.css 파일을 만들어 보겠습니다.
:root { --green: #3FE79E; --dark: #0F0235; --off-white: #F5F5F3; --space-sm: 0.5rem; --space-md: 1rem; --space-lg: 1.5rem; --font-size-sm: 0.5rem; --font-size-md: 1rem; --font-size-lg: 2rem; }
물론 이 목록은 시간이 지남에 따라 증가할 수 있고 증가할 것입니다. 이 파일을 추가하고 나면 모든 페이지의 기본 레이아웃인 pages/_app.jsx 파일로 이동하고 다음을 추가해야 합니다.
import '../styles/design_tokens.css'
저는 디자인 토큰을 프로젝트 전체에서 일관성을 유지하는 접착제로 생각하고 싶습니다. 통합된 디자인 언어를 보장하기 위해 개별 구성 요소 내에서 뿐만 아니라 전역 규모에서 이러한 변수를 참조합니다.
전역 스타일
다음으로 웹 사이트에 페이지를 추가해 보겠습니다! 페이지/index.jsx 파일(이것이 당사 홈페이지)으로 이동합니다. 모든 상용구를 삭제하고 다음과 같이 추가합니다.
export default function Home() { return <main> <h1>Soothing Teas</h1> <p>Welcome to our wonderful tea shop.</p> <p>We have been open since 1987 and serve customers with hand-picked oolong teas.</p> </main> }
불행히도 그것은 아주 평범해 보일 것이므로 <h1>
태그와 같은 기본 요소에 대한 몇 가지 전역 스타일을 설정해 보겠습니다. (저는 이러한 스타일을 "합리적인 전역 기본값"이라고 생각하고 싶습니다.) 특정 경우에 재정의할 수 있지만 그렇지 않을 경우 원하는 것이 무엇인지에 대한 좋은 추측입니다.
이것을 styles/globals.css 파일(기본적으로 Next.js에서 가져옴)에 넣겠습니다.
*, *::before, *::after { box-sizing: border-box; } body { color: var(--off-white); background-color: var(--dark); } h1 { color: var(--green); font-size: var(--font-size-lg); } p { font-size: var(--font-size-md); } p, article, section { line-height: 1.5; } :focus { outline: 0.15rem dashed var(--off-white); outline-offset: 0.25rem; } main:focus { outline: none; } img { max-width: 100%; }
물론 이 버전은 상당히 기본적이지만 내 globals.css 파일은 일반적으로 실제로 너무 커질 필요가 없습니다. 여기서는 기본 HTML 요소(제목, 본문, 링크 등)의 스타일을 지정합니다. 기본 스타일을 제공하기 위해 이러한 요소를 React 구성 요소에 래핑하거나 지속적으로 클래스를 추가할 필요가 없습니다.
기본 브라우저 스타일의 재설정 도 포함합니다. 예를 들어 "고정 바닥글"을 제공하는 사이트 전체 레이아웃 스타일이 있을 수 있지만 모든 페이지가 동일한 레이아웃을 공유하는 경우에만 여기에 속합니다. 그렇지 않으면 개별 구성 요소 내에서 범위를 지정해야 합니다.
저는 항상 키보드 사용자가 포커스를 받을 때 대화형 요소를 명확하게 나타내기 위해 어떤 종류의 :focus
스타일을 포함합니다. 사이트 디자인 DNA의 필수적인 부분으로 만드는 것이 가장 좋습니다!
이제 웹 사이트가 형성되기 시작했습니다.
유틸리티 클래스
우리 홈페이지가 확실히 개선될 수 있는 한 가지 영역은 현재 텍스트가 항상 화면의 측면으로 확장되므로 너비를 제한합시다. 이 페이지에서 이 레이아웃이 필요하지만 다른 페이지에서도 필요할 수 있다고 생각합니다. 이것은 유틸리티 클래스의 훌륭한 사용 사례입니다!
CSS를 작성하는 대신 유틸리티 클래스를 아껴서 사용 하려고 합니다. 프로젝트에 하나를 추가하는 것이 합리적일 때의 개인적인 기준은 다음과 같습니다.
- 반복해서 필요합니다.
- 그것은 한 가지를 잘합니다.
- 다양한 구성 요소 또는 페이지에 적용됩니다.
이 경우가 세 가지 기준을 모두 충족한다고 생각하므로 새 CSS 파일 styles/utilities.css 를 만들고 다음을 추가해 보겠습니다.
.lockup { max-width: 90ch; margin: 0 auto; }
그런 다음 import '../styles/utilities.css'
를 페이지/_app.jsx 에 추가해 보겠습니다. 마지막으로 pages/index.jsx의 <main>
태그를 <main className="lockup">
으로 변경해 보겠습니다.
이제 우리의 페이지가 더욱 뭉쳐지고 있습니다. max-width
속성을 사용했기 때문에 레이아웃을 모바일 반응형으로 만들기 위해 미디어 쿼리가 필요하지 않습니다. 그리고 한 글자의 너비에 해당하는 ch
측정 단위를 사용했기 때문에 크기는 사용자의 브라우저 글꼴 크기에 따라 달라집니다.
웹 사이트가 성장함에 따라 더 많은 유틸리티 클래스를 계속 추가할 수 있습니다. 저는 여기에서 상당히 실용적인 접근 방식을 취합니다. 일을 하고 있는데 색상이나 무엇인가에 대해 다른 클래스가 필요하면 추가합니다. 나는 가능한 모든 클래스를 태양 아래 추가하지 않습니다. CSS 파일 크기가 커지고 코드가 혼란스러워집니다. 때로는 더 큰 프로젝트에서 몇 가지 다른 파일이 있는 styles/utilities/
디렉토리로 나누는 것을 좋아합니다. 그것은 프로젝트의 필요에 달려 있습니다.
유틸리티 클래스는 전 세계적으로 공유 되는 공통적이고 반복되는 스타일 지정 명령의 툴킷 으로 생각할 수 있습니다. 서로 다른 구성 요소 간에 동일한 CSS를 지속적으로 다시 작성하는 것을 방지하는 데 도움이 됩니다.
구성요소 스타일
우리는 잠시 동안 홈페이지를 완성했지만 여전히 웹사이트의 일부인 온라인 상점을 구축해야 합니다. 여기서 우리의 목표 는 판매하려는 모든 차의 카드 그리드를 표시하는 것이므로 사이트에 일부 구성 요소를 추가해야 합니다.
pages/shop.jsx 에 새 페이지를 추가하여 시작하겠습니다.
export default function Shop() { return <main> <div className="lockup"> <h1>Shop Our Teas</h1> </div> </main> }
그런 다음 표시할 차가 필요합니다. 각 차에 대한 이름, 설명 및 이미지(public/ 디렉토리에 있음)를 포함합니다.
const teas = [ { name: "Oolong", description: "A partially fermented tea.", image: "/oolong.jpg" }, // ... ]
참고 : 이것은 데이터 가져오기에 대한 기사가 아니므로 쉬운 경로를 선택하고 파일 시작 부분에 배열을 정의했습니다.
다음으로, 차를 표시할 구성 요소를 정의해야 합니다. components/
디렉토리를 만드는 것으로 시작하겠습니다(Next.js는 기본적으로 이것을 만들지 않습니다). 그런 다음 components/TeaList
디렉토리를 추가하겠습니다. 둘 이상의 파일이 필요한 구성 요소의 경우 일반적으로 모든 관련 파일을 폴더 안에 넣습니다. 그렇게 하면 components/
폴더를 탐색할 수 없게 되는 것을 방지할 수 있습니다.
이제 component/TeaList/TeaList.jsx 파일을 추가해 보겠습니다.
import TeaListItem from './TeaListItem' const TeaList = (props) => { const { teas } = props return <ul role="list"> {teas.map(tea => <TeaListItem tea={tea} key={tea.name} />)} </ul> } export default TeaList
이 구성 요소의 목적은 차를 반복하고 각 차에 대한 목록 항목을 표시하는 것이므로 이제 구성 요소/TeaList/TeaListItem.jsx 구성 요소를 정의하겠습니다.
import Image from 'next/image' const TeaListItem = (props) => { const { tea } = props return <li> <div> <Image src={tea.image} alt="" objectFit="cover" objectPosition="center" layout="fill" /> </div> <div> <h2>{tea.name}</h2> <p>{tea.description}</p> </div> </li> } export default TeaListItem
Next.js의 내장 이미지 구성 요소를 사용하고 있습니다. 이 경우 이미지는 순전히 장식용이므로 alt
속성을 빈 문자열로 설정했습니다. 여기에서 긴 이미지 설명으로 인해 화면 판독기 사용자를 당황하게 하는 것을 방지하고자 합니다.
마지막으로 component/TeaList/index.js 파일을 만들어 구성 요소를 외부에서 쉽게 가져올 수 있습니다.
import TeaList from './TeaList' import TeaListItem from './TeaListItem' export { TeaListItem } export default TeaList
그런 다음 ../components/TeaList에서 가져오기 ../components/TeaList
와 <TeaList teas={teas} />
요소를 Shop 페이지에 추가하여 모두 함께 연결해 보겠습니다. 이제 우리 차가 목록에 표시되지만 그렇게 예쁘지는 않을 것입니다.
CSS 모듈을 통해 구성 요소와 함께 스타일 배치
먼저 카드 스타일 지정( TeaListLitem
구성 요소)부터 시작하겠습니다. 이제 프로젝트에서 처음으로 하나의 구성 요소에만 해당하는 스타일을 추가하려고 합니다. 새 파일 components/TeaList/TeaListItem.module.css 를 작성해 보겠습니다.
파일 확장자의 모듈이 궁금할 것입니다. 이것은 CSS 모듈 입니다. Next.js는 CSS 모듈을 지원하며 이에 대한 몇 가지 좋은 문서를 포함합니다. .TeaListItem과 같은 CSS 모듈에서 클래스 이름을 작성하면 자동으로 .TeaListItem
과 같은 이름으로 변환됩니다 . TeaListItem_TeaListItem__TFOk_
많은 추가 문자가 추가된 . TeaListItem_TeaListItem__TFOk_
. 결과적으로, 우리 사이트의 다른 클래스 이름과 충돌할 염려 없이 원하는 클래스 이름을 사용할 수 있습니다.
CSS 모듈의 또 다른 장점은 성능입니다. Next.js에는 동적 가져오기 기능이 포함되어 있습니다. next/dynamic을 사용하면 전체 번들 크기에 추가하지 않고 필요할 때만 해당 코드가 로드되도록 구성 요소를 지연 로드할 수 있습니다. 필요한 로컬 스타일을 개별 구성 요소로 가져오면 사용자는 동적으로 가져온 구성 요소에 대해 CSS를 지연 로드 할 수도 있습니다. 대규모 프로젝트의 경우 코드의 상당 부분을 지연 로드하고 가장 필요한 JS/CSS만 미리 로드하도록 선택할 수 있습니다. 결과적으로 나는 보통 로컬 스타일이 필요한 모든 새 구성 요소에 대해 새 CSS 모듈 파일을 만듭니다.
파일에 몇 가지 초기 스타일을 추가하는 것으로 시작하겠습니다.
.TeaListItem { display: flex; flex-direction: column; gap: var(--space-sm); background-color: var(--color, var(--off-white)); color: var(--dark); border-radius: 3px; box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1); }
그런 다음 TeaListitem
구성요소의 ./TeaListItem.module.css
에서 스타일을 가져올 수 있습니다. 스타일 변수는 JavaScript 객체처럼 제공되므로 이 클래스와 유사한 style.TeaListItem.
참고 : 클래스 이름은 대문자로 사용할 필요가 없습니다. 모듈 내부의 대문자 클래스 이름 규칙(및 외부 소문자)이 로컬 클래스 이름과 글로벌 클래스 이름을 시각적으로 구분한다는 것을 발견했습니다.
이제 새 로컬 클래스를 사용하여 TeaListItem
구성 요소의 <li>
에 할당해 보겠습니다.
<li className={style.TeaListComponent}>
배경색 선(예: var(--color, var(--off-white));
)이 궁금할 것입니다. 이 스니펫이 의미하는 바는 기본적 으로 배경이 --off-white
값이 된다는 것입니다. 그러나 카드에 --color
사용자 정의 속성을 설정하면 대신 해당 값을 무시하고 선택합니다.
처음에는 모든 카드가 --off-white
가 되기를 원하지만 나중에 개별 카드의 값을 변경할 수도 있습니다. 이것은 React의 props와 매우 유사하게 작동합니다. 기본값을 설정할 수 있지만 특정 상황에서 다른 값을 선택할 수 있는 슬롯을 만듭니다. 따라서 CSS 버전의 props와 같은 CSS 사용자 정의 속성을 생각하는 것이 좋습니다.
이미지가 컨테이너 내에 유지되도록 하기 위해 스타일이 여전히 멋지게 보이지 않습니다. layout="fill"
prop이 있는 Next.js의 Image 구성 요소는 position: absolute;
프레임워크에서 위치: 상대;가 있는 컨테이너를 넣어 크기를 제한할 수 있습니다.
TeaListItem.module.css 에 새 클래스를 추가해 보겠습니다.
.ImageContainer { position: relative; width: 100%; height: 10em; overflow: hidden; }
그런 다음 <Image>
가 포함된 <div>
에 className={styles.ImageContainer}
를 추가해 보겠습니다. 나는 CSS 모듈 안에 있기 때문에 ImageContainer
와 같은 비교적 "단순한" 이름을 사용하므로 외부 스타일과 충돌하는 것에 대해 걱정할 필요가 없습니다.
마지막으로 텍스트 측면에 약간의 패딩을 추가하고 싶으므로 마지막 클래스를 하나 추가하고 디자인 토큰으로 설정한 간격 변수에 의존해 보겠습니다.
.Title { padding-left: var(--space-sm); padding-right: var(--space-sm); }
이 클래스를 이름과 설명이 포함된 <div>
에 추가할 수 있습니다. 이제 우리 카드가 그렇게 나빠 보이지 않습니다.
글로벌 스타일과 로컬 스타일의 결합
다음으로 카드를 그리드 레이아웃으로 표시하고 싶습니다. 이 경우 우리는 로컬 스타일과 글로벌 스타일의 경계에 있습니다. TeaList
구성요소에서 직접 레이아웃을 코딩할 수 있습니다. 그러나 목록을 그리드 레이아웃으로 바꾸는 유틸리티 클래스를 갖는 것이 다른 여러 곳에서 유용할 수 있다고 상상할 수도 있습니다.
여기에서 전역 접근 방식을 취하고 styles/utilities.css 에 새 유틸리티 클래스를 추가해 보겠습니다.
.grid { list-style: none; display: grid; grid-template-columns: repeat(auto-fill, minmax(var(--min-item-width, 30ch), 1fr)); gap: var(--space-md); }
이제 모든 목록에 .grid
클래스를 추가할 수 있으며 자동으로 반응하는 그리드 레이아웃을 얻을 수 있습니다. --min-item-width
사용자 정의 속성(기본값 30ch
)을 변경하여 각 요소의 최소 너비를 변경할 수도 있습니다.
참고 : 소품과 같은 사용자 정의 속성을 생각하는 것을 잊지 마십시오! 이 구문이 낯설어 보이면 Chris Coyier의 " minmax()
And min()
이 있는 본질적으로 반응하는 CSS 그리드"를 확인할 수 있습니다.
이 스타일을 전역적으로 작성했으므로 우리 TeaList
구성요소에 className="grid"
를 추가하는 데 기발한 작업이 필요하지 않습니다. 그러나 이 글로벌 스타일을 추가 로컬 상점과 결합하고 싶다고 가정해 보겠습니다. 예를 들어, 우리는 "차 미학"을 조금 더 가져와서 다른 모든 카드에 녹색 배경을 갖도록 만들고 싶습니다. 우리가 해야 할 일은 새로운 components/TeaList/TeaList.module.css 파일을 만드는 것입니다.
.TeaList > :nth-child(even) { --color: var(--green); }
TeaListItem
구성 요소에서 --color custom
속성을 만든 방법을 기억하십니까? 이제 특정 상황에서 설정할 수 있습니다. CSS 모듈 내에서 여전히 자식 선택기를 사용할 수 있으며 다른 모듈 내에서 스타일이 지정된 요소를 선택하는 것은 중요하지 않습니다. 따라서 로컬 구성 요소 스타일을 사용하여 하위 구성 요소에 영향을 줄 수도 있습니다. 이것은 우리가 CSS 캐스케이드를 활용할 수 있게 해주기 때문에 버그가 아니라 기능입니다! 이 효과를 다른 방식으로 복제하려고 하면 세 줄의 CSS가 아닌 일종의 JavaScript 수프가 될 것입니다.
그런 다음, 로컬 .TeaList
클래스를 추가하면서 TeaList
구성요소에 글로벌 .grid
클래스를 유지하려면 어떻게 해야 합니까? style.TeaList
와 같은 작업을 수행하여 CSS 모듈에서 .TeaList
클래스에 액세스해야 하기 때문에 구문이 약간 펑키해질 수 있습니다.
한 가지 옵션은 문자열 보간을 사용하여 다음과 같은 것을 얻는 것입니다.
<ul role="list" className={`${style.TeaList} grid`}>
이 작은 경우에는 이 정도면 충분할 수 있습니다. 더 많은 클래스를 혼합하고 일치시키는 경우 이 구문이 내 두뇌를 약간 폭발시키므로 때때로 클래스 이름 라이브러리를 사용하기로 선택할 것입니다. 이 경우 더 합리적인 목록으로 끝납니다.
<ul role="list" className={classnames(style.TeaList, "grid")}>
이제 Shop 페이지를 완료했으며 TeaList
구성 요소가 전역 및 로컬 스타일을 모두 활용하도록 만들었습니다.
균형 조정법
이제 스타일을 처리하기 위해 일반 CSS만 사용하여 찻집을 만들었습니다. 사용자 지정 Webpack 설정, 외부 라이브러리 설치 등을 처리하는 데 오랜 시간을 할애할 필요가 없다는 것을 눈치채셨을 것입니다. 그것은 바로 우리가 Next.js와 함께 작업하는 데 사용한 패턴 때문입니다. 또한 모범 CSS 사례 를 권장하고 자연스럽게 Next.js 프레임워크 아키텍처에 맞습니다.
우리 CSS 조직은 4가지 핵심 요소로 구성되어 있습니다.
- 디자인 토큰,
- 글로벌 스타일,
- 유틸리티 클래스,
- 구성 요소 스타일.
사이트를 계속 구축함에 따라 디자인 토큰 및 유틸리티 클래스 목록이 늘어날 것입니다. 유틸리티 클래스로 추가하는 것이 의미가 없는 스타일은 CSS 모듈을 사용하여 구성 요소 스타일에 추가할 수 있습니다. 결과적으로 우리는 지역 및 글로벌 스타일링 문제 사이에서 지속적인 균형을 찾을 수 있습니다. 또한 Next.js 사이트와 함께 자연스럽게 성장하는 직관적이고 성능이 뛰어난 CSS 코드를 생성 할 수 있습니다.