NextJS - App Router (1)

이동창·2023년 6월 19일
1

App Router

새로 시작하는 프로젝트에 NextJS의 Page Router 대신 App Router를 체험해보고
Server Component에 대한 이해를 직접 체험하며 느끼기 위해 아래의 문서를 읽고 정리해보는 중


Server Component

서버에서 렌더링해서 갖다주는 컴포넌트
App Router에서는 모든 페이지가 default로 서버 컴포넌트이다.
만약 client components로 작성하고 싶다면 'use client'를 상단에 작성해줘야 한다.

Client Component

Client Component라고 모든걸 클라이언트에서 렌더링하는건 아니다.
서버에서 먼저 pre-render가 되고, 이후에 client에서 hydrated가 된다.

Hydration이란, react가 서버에서 렌더해 이미 존재하는 HTML에
markup과 event listner등을 붙여서 어플리케이션을 렌더하는 과정을 말한다.
어차피 root 아래에 있는건 이 과정에서 만들어진다고 보면 되니
서버에서 pre-render 되는게 많지 않다고 보면 될 듯

모든 Client Component에 use client를 붙일 필요는 없다.
하나의 Client Component에 import 되는 컴포넌트들은 자동으로 Client Component로 취급된다.


언제 Client, Server Comoponent를 써야 하는가

일단 Client Component를 써야하는 곳만 제대로 알아도 좋을 것 같다.

  • Event Listener를 써야할 때
  • Use State와 Lifecycle Effects를 사용할 때 (useState, useEffect)
  • browser only API를 사용할 때 (window 객체 등등)
  • class 컴포넌트를 사용할 때




헷갈리지 않도록 하자

  1. Server Component에서 hook, event listener, browser API는 사용할 수 없다.
  2. Server Compoennt는 어떤 컴포넌트든 import 가능하다.
  3. Client Component에서 Server Component를 import 할 수 없고, 이에 따라 당연히 import하는 모듈은 Client Component로 취급된다.
  4. Server Compoennt 안에서 Client Compoennt의 children으로 Server Component를 집어 넣는 것은 가능하고, 이는 하위 Server Component를 Client Component로 취급하지 않게 한다.



잘 활용하는 방법

Server Component를 사용하는 조건이 생각보다 빡빡하다.
Use State나, Event Lisenter, Browser only API는 생각보다 많은 컴포넌트에서 사용되기에 여러 패턴을 잘 이용해야 Server Component를 폭넓게 사용할 수 있다.

1. Client Component는 가장 마지막 부분으로 이동하기

어플리케이션의 최상단에 Client Component를 사용하게 되면,
그 하위 모든 컴포넌트는 강제로 Client Component로 작성해야하게 된다.
따라서 많은 곳에서 Server Component의 장점을 누리려면,
Client Component를 사용하는 곳을 제대로 특정하고, 이를 하위 컴포넌트로 분리하여 작성하는 것이 좋다.

예를 들어, 위와 같은 입력폼을 만든다고 가정하자.
이 때, 이메일과 비밀번호를 받고 로그인하는 부분은 이벤트 리스너를 필수로 사용하게 된다.
구글 로그인도 마찬가지일 가능성이 높다.

그렇다면 저 부분만 하위 컴포넌트로 각각 따로 분리하여 Client Component를 사용하고,
다른 부분들은 Server Component를 이용하도록 하면 된다.


2. Client Component와 Server Component 조합하기

위에서 Client Component에 import 되는 모듈은 Client Component로 취급된다고 했다.
그렇다면 Client 하위 컴포넌트는 모두 Client여야만 할까?

아니다. Server Component도 쓸 수 있다.

다만 기존의 우리가 사용하던 import 구문을 통해서 하는건 아니고, 상위 Server Component에서, child나 props으로 넘겨줘야 한다.

잘못된 방법

'use client'

// 이 패턴은 동작하지 않는다:
// 클라이언트 컴포넌트는 서버 컴포넌트를 불러올 수 없다.
import 하위_서버_컴포넌트 from './example-server-component'

export default function 클라이언트_컴포넌트({
  children,
}: {
  children: React.ReactNode
}) {
  const [count, setCount] = useState(0)

  return (
    <>
      <button onClick={() => setCount(count + 1)}>{count}</button>

      <하위_서버_컴포넌트 />
    </>
  )
}

옳은 방법

// 이 패턴은 가능하다:
// 클라이언트 컴포넌트의 child나 prop으로 Server Component 전달 가능
import 하위_클라이언트_컴포넌트 from './example-client-component'
import 하위_서버_컴포넌트 from './example-server-component'

// Pages in Next.js are Server Components by default
export default function 상위_서버_컴포넌트() {
  return (
    <하위_클라이언트_컴포넌트>
      <하위_서버_컴포넌트 />
    </하위_클라이언트_컴포넌트>
  )
}

3. Server Component에서 Client Component로 전달하는 props

이건 조심해야 할 부분인데, Server Component에서 Client Component로 전달하는 props는 serializable 해야한다. 즉, 함수나, Dates와 같은 객체는 전달할 수 없다.

4. Server Component에서만 사용되는 코드 구분하기

Server Component와 Client Component는 서로 모듈을 공유할 수 있다.
그렇기에 서버에서만 사용되어야 하는 코드는 확실히 구분해두어야 예기치 못한 오류나 보안 문제를 겪지 않는다.
예를 들어 NEXT_PUBLIC이 붙지 않은 환경변수를 이용하는 코드가 들어있는 모듈을
Client Component에서 불러와 사용하면 예기치 않은 오류가 나타날 것 이다.

이를 방지하는 방법 중 server-only 패키지를 이용하는 방법이 있다.

이용 방법은 단순하다. npm install server-only로 설치하고,
모듈 최상단에 import server-only를 적어주면 된다.

client-only 패키지도 있는데, 비슷한 방식이다.

5. 외부 라이브러리 이용하기

외부 라이브러리 중 Server Component를 지원하지 않는 라이브러리가 아직 많기 때문에
무작정 라이브러리를 Server Component에서 import하려고 하면 에러가 나기 쉽다.

사실 만약 라이브러리에서 use client를 선언했다면 문제가 없다.
다만 대부분의 라이브러리는 이를 명시하지 않았을 가능성이 높기 때문에,
다음과 같은 과정을 거쳐야 하는 것이다.

예를 들어 useState를 이용하는 라이브러리를 Server Component에서 import하면 에러가 뜰텐데, 이를 한번 use client로 wrapping 해주면 Server Component 안에서도 import 할 수 있게 된다.

'use client'
 
import { Carousel } from 'acme-carousel'
 
export default Carousel
import Carousel from './carousel'
 
export default function Page() {
  return (
    <div>
      <p>View pictures</p>
 
      {/*  Works, since Carousel is a Client Component */}
      <Carousel />
    </div>
  )
}

6. Context

사실 위와 중복이다.
react-query, redux 와 같이 Context를 사용하는 라이브러리도
5번에서 설명한 패턴으로 처리하자.

당연히 하위 컴포넌트가 다 client로 바뀌지 않는다.
React에서 알아서 하위 컴포넌트 중 Server 컴포넌트가 있다면
따로 분리해서 pre-render를 진행해준다.

react-query와 Server Component을 사용한 예제

7. Server Component 끼리의 데이터 교환

Client Component는 props를 내려주든, Context를 활용하든
Component들끼리 데이터를 주고 받을 수 있는데, Server Component는 어떨까?

사실 굉장히 간단한데, native JavaScript 패턴을 사용하면 된다.

export const db = new DatabaseConnection()
import { db } from '@utils/database'
 
export async function UsersLayout() {
  let users = await db.query()
  // ...
}
import { db } from '@utils/database'
 
export async function DashboardPage() {
  let user = await db.query()
  // ...
}

이런 식이다.

참고자료

https://nextjs.org/docs/getting-started/react-essentials
https://velog.io/@timosean/Server-component-vs.-Client-Component
https://11001.tistory.com/221

0개의 댓글