# 2024. 03.

# 03. 19.

# next-auth 인증/인가 고민

로그인 과정에서 (next-auth 사용)
next-auth에서 제공하는 session-token 만으로 인증/인가를 담당해도 될까??
JWT를 안써도 될까?? 라는 고민중

next-auth를 활용한 인증 및 인가 이해하기 (opens new window) next-auth credential provider에서 jwt 사용하기 (해외) (opens new window) next-auth에서 세션 및 JWT에 대한 ISSUE (해외) (opens new window)

현재 개발하고 있는 서비스 자체가 NextJS 풀스택으로 개발중이다.
즉, route handler에서 들어오는 요청에 대한 세션에 접근할 수 있다. (요청한 유저의 신원을 조회할 수 있다.)

만약 API 서버가 따로 있다고 하면 JWT가 필요할 수 있겠다.

next-auth FAQ: next-auth에서 제공하는 JWT(session-token)의 장점은 뭔가요 (opens new window)

위 FAQ에,

NextAuth.js의 JSON 웹 토큰은 암호화 암호화(JWE)를 사용하여 포함된 정보를 JWT 세션 토큰에 직접 저장하여 보안을 유지합니다. 그런 다음 이 토큰을 사용하여 포함된 정보를 확인하기 위해 데이터베이스에 연결할 필요 없이 동일한 도메인의 서비스 및 API 간에 정보를 전달할 수 있습니다.

또한,

JWT는 서버 읽기 전용 쿠키에 저장되므로 사이트에서 실행 중인 타사 JavaScript가 JWT의 데이터에 액세스할 수 없으므로 암호화 없이도 클라이언트가 알아도 상관없는 정보를 안전하게 저장하는 데 JWT를 사용할 수 있습니다.

문항을 읽어보았을 때, Session-token만으로 인증/인가를 담당해도 될것같은 느낌을 받았다.

그럼 Session Expire 시간을 늘려야되나..??
일단 Session Token만 사용해보자.

# 03. 23.

# next-auth 클라이언트 세션 수정 가능

클라이언트에서 useSession, auth 세션 바꿀 수있음
next-auth 공식 문서 #updating-the-session (opens new window)

야 호

# prismaClient를 edge 환경에서 실행

middleware에서 prismaClient를 사용할 일이 생겼다.
middleware은 vercel edge 환경에서 실행 (opens new window)되는데, prismaClient를 edge 환경에서 실행하려면 Accelerate를 사용해야된다.

Accelerate는 Edge환경의 DB 캐싱서비스. 참고 (opens new window)
Accelerate 서비스를 생성하고 기존 DB URL을 연결하면 매핑된 새로운 URL을 반환해준다.

해당 URL에 요청하면 Accelerate에서 기존 우리의 DB에 요청을 하고 결과값을 응답해준다.
해당 요청에 대한 응답은 캐싱된다.

기존 DB도 캐싱기능이 있을텐데 Accelerate를 사용하는 이유는??
아마 Edge환경에 배포되어있기에 응답과 처리속도(Accelerate 구조상)가 빨라서 Edge 환경에서 쓰기 적합하지 않을까?
기존 prismaClient을 edge환경(리소스가 제한된 환경)에서 실행시키기에는 느리기에 내장되어있는 prisma/edge에서 prismaClient를 사용해야한다.

# 03. 24.

# ClientComponent와 ServerComponent의 동작 방식 feat. 번들 최적화 #1

번들 최적화 과정에서, ClientComponenet와 ServerComponent이 번들 크기에 어떻게 영향을 주는지 알고 싶었다.

NextJS에서 컴포넌트들을 직렬화 할 때 ClientComponent는 직렬화가 안되므로 자체 참조값(./ClientComponent.js)으로 변환시킨다.

이 때, 트리 구조로 컴포넌트들이 구성되어있는데 가장 최상단의 컴포넌트가 ClientComponent로 구성되어있으면
우리가 브라우저에 처음 접속했을 때 최상단 컴포넌트에 대한 참조값만 전달받는거 아닌가??

라는 의문도 들었다.

직접 테스트 해보았을 떄,

NextJS에서는 직렬화 단계에서 ClientComponent의 직렬화 가능한 부분(예를 들어, 시맨틱 태그, 텍스트, 스타일)은 직렬화 하고 나머지 onClick 함수등 직렬화 되지 않는 부분은 참조값으로 변환시켜 클라이언트 쪽에서 Hydration 하는듯하다.

좋은글
Next) 서버 컴포넌트(React Server Component)에 대한 고찰 (opens new window)

'use client' 즉, ClientComponent가 코드스플리팅의 분기점이 된다고 한다. ServerComponent와 ClientComponent 둘다 서버에서 렌더링되는건 마찬가진데, 'use client'를 사용하는 이유가 단순 서버에서 렌더링할 때 이 컴포넌트는 Hydration이 필요한 컴포넌트야라고 알려주는것 빼고는 다른 역할이 있는지 궁금하다.

# 03. 25.

# ClientComponent와 ServerComponent의 동작 방식 feat. 번들 최적화 #2

어제 했던 삽질에 이어서

// app/layout.tsx
import { createContext } from "react";

//  createContext is not supported in Server Components
export const ThemeContext = createContext({});

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>
      </body>
    </html>
  );
}

ThemeContext는 ServerComponent에서 사용할 수 없기에 아래와 같이 theme-provider라는 ClientComponent를 만들고 children props로 넘겨준다.

// app/theme-provider.tsx
"use client";

import { createContext } from "react";

export const ThemeContext = createContext({});

export default function ThemeProvider({
  children,
}: {
  children: React.ReactNode;
}) {
  return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>;
}

다시 돌아와서 app/layout.tsx에서 아래와 같이 Root를 ServerComponent로 유지할 수 있다

// app/layout.tsx
import ThemeProvider from "./theme-provider";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html>
      <body>
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  );
}

You should render providers as deep as possible in the tree – notice how ThemeProvider only wraps {children} instead of the entire <html> document. This makes it easier for Next.js to optimize the static parts of your Server Components.

출처 : NextJS 공식문서 rendering/composition-patterns (opens new window)

대충 Provider를 따로 Client Component로 만들어서 {children} props로 넘기면 그 내부의 컴포넌트는 ServerComponent로 사용할 수 있다는 내용이다.
이렇게 하면 추가적으로 Client Javascript Bundle Size도 줄일 수 있다고 한다.

# 추가 삽질

여러개의 'use client' 지시어를 사용하면 여러개의 client 번들로 나뉘어진다.
'use client' 지시어를 사용하면 해당 컴포넌트내에 있는 모든 모듈과(import) ChildComponent들이 client 번들에 추가되는걸로 간주된다.

출처 : If I pass a server component to a client component via the children prop, will it be rendered on the client? (opens new window)

서버 컴포넌트는 렌더링 과정에서 직렬화된 결과가 inline script로 삽입된다는 정보를 얻을 수 있었다.

출처 : 새로 등장한 React ServerComponent 이해하기 (opens new window)

서버 컴포넌트에서 데이터를 가져오는 경우, 클라이언트 컴포넌트에 props로 데이터를 전달하고 싶을 수 있는데, ServerComponent에서 ClientComponent로 전달되는 props는 React에서 직렬화할 수 있어야 한다.
이건 내 생각 👉 (서버 렌더링을 위해서는 직렬화를 해야되기 때문에 만약 ServerComponent에서 ClientComponent로 전달되는 props가 직렬화가 안될 경우에는 해당 ServerComponent도 직렬화가 안되기 때문으로 예상. 직렬화가 안되면 아예 js 번들로 빼고 참조값만 남겨야한다.)

출처 : rocketengine 티스토리 Getting Started - React Essentials (opens new window)

'use client' 지시어는 모듈 단위로 동작하며, ClientComponent에서 import 되는 Component는 모두 ClientComponent 이다. ServerComponent와 ClientComponent의 차이는 결국 직렬화 여부이다. ServerComponent의 경우 인라인 스크립트로 컴포넌트 정보가 제공되어 추가적인 네트워크 요청이 필요 없지만, ClientComponent의 경우 초기 렌더링 이후 컴포넌트의 정보를 추가적으로 요청하며 Hydate 시키는 과정이 필요하다.

출처 : Making Sense of React Server Components (opens new window)

# 정리

우리가 사이트에 방문하면 서버와 브라우저에선 다음과 같은 과정을 거친다.

  1. [Server] RootComponent부터 순회를 하면서 ServerComponent를 다 렌더링하고 직렬화 한다.
  2. [Server] ClientComponent는 js 번들에 포함시킨다.
    이 때, use client로 명시된 ClientComponent를 기준으로 선언된 모든 import나 ChildComponent는 js 번들에 포함된다. children으로 넘겨진 데이터는 실제로 ServerComponent에서 import된 Component이기 때문에 ServerComponent로 간주된다.
  3. [Server] ServerComponent를 직렬화한 결과를 inline script에 포함시킨 html을 브라우저에 응답한다.
  4. [Browser] 이미 렌더링 된 ServerComponent에 대한 렌더트리 정보는 js 번들에 포함되어있지 않으므로 inline script에 포함되어있는 ServerComponent를 직렬화한 결과를 해석하여 가상돔에 포함시킨다.
  5. [Browser] 이후 ClientComponent는 렌더트리 정보가 담긴 js 번들을 다시 서버에 요청하여 Hydrate 과정을 수행한다.

즉, 의문점에 대한 대답은
최상단 컴포넌트가 Client Component일 경우 초기 렌더링 이후 전체 렌더트리 정보가 네트워크를 통해서 전달된다.

# 결론

가장 인상깊었고 중요하게 생각하는 부분은

  • ServerComponent를 직렬화한 결과를 첫 html에 포함시켜 준다는것.

  • 'use client' 지시어는 파일이나 모듈 레벨에서 작동한다는것,

    • 즉, 'use client' 지시어가 선언된 컴포넌트를 기준으로 모든 import나 ChildComponent는 모두 ClientComponent이고 client bundle에 포함된다는것이다. 하지만 props (특히 children)로 넘겨진 데이터는 Server에서 렌더링할 수 있다. (ServerComponent에서 children props로 전달된 컴포넌트는 서버에 의해 고정된 정적인 데이터이기 때문.)
  • ClientComponent와 ServerComponent의 차이는 렌더트리 정보를 인라인으로 주입해주냐 추가적인 네트워크로 주입해주냐의 차이인것 같다. 즉, 적절히 활용하는게 맞는 방법인것 같다.

개인적으로 힘든 삽질이었다.
하지만 아직 완벽히 이해한 느낌은 아니다. 나머지 개념은 사용하면서 배워야겠다.

# 참고하면 좋을듯한 정보

NextJS App router 시뮬 사이트 (opens new window)
RSC Payload에 대해서 (해외) (opens new window)
How React server components work: an in-depth guide (opens new window)