[classpick 개발일지 #1] Next.js13으로 마이그레이션하기 - emotion 써도 되나? 고민하기

김유진·2023년 5월 29일
10

Nextjs

목록 보기
9/9
post-thumbnail

내 마음을 대변하는 누군가의 리플....^^;

classpick 개발은 꼭 NextJs로 해보고 싶어서 프로젝트 세팅을 하던 중, 저번달에 NextJs 13가 정식 출시되었고 어느정도 안정화 되었다는 소식이 있어서 꼭 사용해보고자 하여 기존에 Nextjs 12로 세팅해놨던 프로젝트를 NextJs 13으로 마이그레이션을 하였다.

사실 기존에 Nextjs12로 사용할때는 스토리북 세팅 해둔 것도 잘 되었고, emotion에 대한 에러가 하나도 뜨지 않아서 불편함을 잘 몰랐다.
그런데 세팅을 끝내고 개발환경을 싹 돌리는데 아래와 같은 에러가 뜨더구나...

두둥...
바로 emotion 관련 세팅 때문에 client 컴포넌트로 인식, 에러가 발생하는 것이다. 헉 하면서 설마 emotion을 쓰지 못하는건가? 라고 생각하며 이것저것 자료를 찾아봤는데 정답은 아래에 있었다.

https://github.com/emotion-js/emotion/issues/2928

(아니 2022년 10월에 올라온 이슈인데 아직도 해결을 안했냐고)
일해주세요 emotion 개발팀!! 🥹 emotion 이슈에 올라온 것을 보니까 NextJs13의 app디렉토리에서는 아직 emotion 관련된 해결이 안된 것으로 보인다.

https://nextjs.org/docs/app/building-your-application/styling/css-in-js

Next 공식문서를 보니까 styled-component는 어느정도 해결이 되어 있으므로 emotion과 매우 유사한 방식으로 개선하고 싶다면 styled component를 이용해도 될 것으로 보인다.
하지만 이 글을 쓴 것은 단순히 해결책을 정리하고자 쓴 글이 아니고, 왜 NextJs 13부터는 무엇이 바뀌었길래 이를 사용할 수 없는지 !!! 정리해보고 싶어서 쓰게 된 글이다.

Next 13의 app 디렉토리

먼저 새롭게 바뀌게 된 Next 13의 app 디렉토리의 동작 방식에 대해서 이해해야 한다. 물론 Emotion컴포넌트가 들어가게 되는 모든 파일에 use client라고 박아두면 마음은 편하겠으나..서버 컴포넌트를 공부해야하는 입장에서 이렇게 단순하게 해결하려고 하면 더이상 배울 게 없지 않을까 싶다.

root에 존재하는 Layout 파일에 emotion cache를 추가해주고 모든 파일에 use client를 쓰면 코드 작성 시에 불필요하고 중복되는 코드를 작성할 뿐만 아니라, Next를 쓰는 의미도 사라지게 될 것이다.

사실, 클라이언트 컴포넌트를 제대로 잘 섞어쓰면 되는거 아냐?! 싶다. 그러나 app 컴포넌트를 제대로 쓰는 법을 배우고 싶기도 해서 조금 더 욕심 내서 tailwind로 바꿔볼까 생각이 들기도 한다.

먼저 Next13의 app 디렉토리에 대해서 더 잘 이해하기 위해서는 아래 글을 꼼꼼히 읽어보아야 한다.
https://nextjs.org/docs/getting-started/react-essentials

서버 컴포넌트

서버 컴포넌트는 Next 13부터 실험적으로 새롭게 생긴 기능이다. 서버 컴포넌트를 사용하게 되면 개발자는 서버 컴포넌트의 장점과 클라이언트 컴포넌트의 장점을 아우르는 서비스를 제작할 수 있다. 즉,

클라이언트 사이드 앱의 풍부한 상호작용성과 전통적인 서버 랜더링의 개선된 성능을 개선할 수 있다.

보통 페이지를 만들 때 하나의 컴포넌트로 취급하지 않고 작은 컴포넌트 단위로 쪼개서 만들 것이다. 더 작은 상호작용이 필요한 UI일 경우에는 클라이언트 컴포넌트를 사용할 수 있다. 대부분의 컴포넌트는 interactive하지 않으므로, 서버컴포넌트로 작성해도 될 것이다.

서버 컴포넌트의 장점이 뭔데?

장점이 있어야 이용할 만한 매력을 느끼지 않을까?! 서버 컴포넌트의 이점을 정리해보자.

  • 데이터를 패칭하는 데 있어서 DB에 한 층 가까워질 수 있으므로, 불필요한 Javascript 관련된 파일 번들링, 처리에 시간을 덜 사용할 수 있기 때문에 성능을 향상시킬 수 있다.
  • Javascript 번들 크기가 줄어들고 초기 페이지 로드 속도가 매우 빨라진다. 기본적으로 클라이언트 측 런타임은 캐시가 가능하고 크기가 예측 가능하여서 애플리케이션이 커져도 필요한 시간이 증가하지는 않는다.

app/

서버 컴포넌트를 쉽게 이용하기 위해서 Next13부커는 app/ 디렉토리에 있는 모든 컴포넌트는 기본적으로 서버 컴포넌트로 취급되는 것이다. 만약 클라이언트 컴포넌트를 사용해야 한다면 선택적으로 use client를 사용하면 되는 것이다.

클라이언트 컴포넌트

클라이언트 컴포넌트로 사용하고 싶다면 맨 앞 파일에 use client를 덧붙이면 된다. 단, 주의해야 할 점은 해당 파일을 포함하여 import된 다른 모듈과 자식 컴포넌트들은 클라이언트 번들로 포함된다는 점이다.

작동 방식

서버에서는 React가 모든 서버 컴포넌트를 랜더링한 이후에 그 결과를 클라이언트에 전송한다.여기서 당연히 클라이언트 컴포넌트는 제외되고 전송될것이다.
이제 런타임에 클라이언트에서 클라이언트 컴포넌트 및 넘겨받은 서버 컴포넌트의 랜더링 결과를 화면에 그려낼 것이다.

언제 클라이언트/서버 컴포넌트를 쓸까?

이제 기본적으로 서버 컴포넌트를 지원하는 상황에서 클라이언트, 서버 컴포넌트를 적절히 잘 섞어 쓰는 것이 매우 중요해졌다고 볼 수 있다.

꿀팁 대 방출

client/server 컴포넌트를 사용할 때의 꿀팁 모음이다. 🍯

1. 클라이언트 컴포넌트를 최대한 자식에 넣어라.

위에서 말한것처럼 클라이언트 컴포넌트를 선언하게 되면 그에 포함되는 모듈과 함께 자식 컴포넌트들도 모두 클라이언트로 처리를 해버린다. 그렇기 때문에 로고, 링크 등 정적 요소를 포함하는 레이아웃과 대화형 검색 창이 있을 경우 전체 레이아웃을 최대한 서버 컴포넌트로 만들고, 상호작용과 관련한 컴포넌트만 클라이언트 컴포넌트로 작성해도 된다.

2. 서버/클라이언트 컴포넌트 중첩

위에서 말한 것처럼 클라이언트 컴포넌트 안에 서버 컴포넌트를 작성하게 된다면 클라이언트 컴포넌트로 작성한 것과 같은 별 효과가(..)없을 것이다. 그렇기 때문에 아래와 같이 작성하는 것을 공식문서에서는 추천하더라.

  • 지원되지 않는 패턴 : 서버 컴포넌트를 클라이언트 컴포넌트로 가져오기 🚫
'use client';
 
// This pattern will **not** work!
// You cannot import a Server Component into a Client Component.
import ExampleServerComponent from './example-server-component';
 
export default function ExampleClientComponent({
  children,
}: {
  children: React.ReactNode;
}) {
  const [count, setCount] = useState(0);
 
  return (
    <>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      <ExampleServerComponent />
    </>
  );
}
  • 권장되는 패턴 : 서버 컴포넌트를 Props로 client에게 전달하기
    Server 컴포넌트를 위한 구멍을 표시해주는 것이다.
    client 컴포넌트에서 구멍난 곳을 이미 랜더링된 서버 컴포넌트로 채워 넣는다는 느낌으로다가 생각하면 편하다.
'use client';
 
import { useState } from 'react';
 
export default function ExampleClientComponent({
  children,
}: {
  children: React.ReactNode;
}) {
  const [count, setCount] = useState(0);
 
  return (
    <>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      {children}
    </>
  );
}

예시로 나온 ExampleClientComponent는 children이 누구인지 알지 못한다. 해당 클라이언트 컴포넌트는 누구인지모를 자식 컴포넌트를 잘 배치해주기만 하면 되는 것이다. ㅎㅎ

import ExampleClientComponent from './ExampleClientComponent';
import ExampleServerComponent from './ExampleServerComponent';

function ParentServerComponent() {
  return (
    <ExampleClientComponent>
      <ExampleServerComponent />
    </ExampleClientComponent>
  );
}

그럼 이제 이렇게 코드를 작성할 수 있다!
서버 컴포넌트인 ParentServerComponent입장에서는 ExampleClientComponentExampleServerComponent를 모두 가져와서 사용하게 되는데 그들의 랜더링이 독립적으로 각각 일어나, 서버 컴포넌트가 먼저 랜더링을 할 수 있게 된다.

일반적으로 이렇게 서버 컴포넌트는 작성하는 코드 패턴이 정해져 있고 이를 지켜서 코드를 작성하는 것이 중요하다고 판단될 수 있겠다.

그래서 emotion은 어떻게 할 건데?

현재 emotion 때문에 화면의 첫 페이지부터 오류를 마주하고 있다..ㅎㅎ
아직은 emotion에 대한 서버컴포넌트 관련한 뾰족한 수가 없는 상황이다. 있다고 해도,

/** @jsxImportSource @emotion/react */

이렇게 프로바이더를 줘 놓고, emotion을 사용하고 싶은 컴포넌트에는 use client라고 언급을 하는 수 밖에 없다. 이렇게 코드를 작성하고 서버 컴포넌트와 클라이언트 컴포넌트를 유연하게 대처해도 되겠다.
그러나 이렇게 사용하게 되면 개발하다가 불필요한 프로바이더를 여러번 작성해야할 수도 있고 머리털 뽑힐 것 같기 때문에....
styled-component를 이용하여 스토리북을 완성하고 서버 관련된 컴포넌트에 스타일이 필요할 경우에는 Tailwind css를 이용해야겠다.
사실은 아직도 styled-component가 최적화 진행중이고, 완벽히 서버컴포넌트로 사용하기 어려운 면이 많다. 그렇기 때문에 정말 결국에는 tailwind를 배워야 할 것 같다는 생각도 든다.
개인적으로 클래스명이 너무 길어지는거 별로 안좋아해서, tailwind는 정말 사용하기 싫었는데 이렇게 css in js가 서버사이드랜더링이 발전함에 따라 사용하기 어려워지는 현상이 일어난다면 나도 배움에 있어서 생각을 바꿔야 하는게 아닌가 싶다.

2개의 댓글

comment-user-thumbnail
2023년 6월 2일

고민하는 모습이 멋있습니다! 글 잘 읽었습니다!!

답글 달기
comment-user-thumbnail
2023년 10월 28일

개인적으로 tailwind는 부트스트랩 시절 느낌나기도 하고... css 뿐만 아니라 남이 정해놓은 tailwind만의 네이밍을 또 추가적으로 공부를 해야한다는 점에서 싫어 하기도 하고 가독성도 극혐이라 tainwind는 안좋아하는 편인데, 요즘엔 vanilla extract가 뜨고 있더라구요.

답글 달기