의문점

image.png

빌드를 한 결과에서 왜 auth/callback, auth/callback-email을 제외한 모든 페이지가 정적 페이지일까?

memos페이지는 분명히 쿠키를 통해 사용자 데이터에 접근함에도 불구하고 말이다.

사실 확인

  1. memos페이지에서는 현재 시간을 찍는 DebugCache의 값이 바뀌고 introduce페이지에서는 바뀌지 않는다.

    이 말은 즉슨 실제로 memos페이지는 사용자가 페이지를 요청 시 동적으로 페이지를 만들고 introduce는 정적으로 빌드타임 때 페이지를 제공한다는 것을 의미한다.

  2. 현재 동적으로 생성되는 것은 Middleware, /auth/callback, /auth/callback-email만 있다. auth관련 페이지는 search params값을 기반으로 쿠키를 설정 후 redirect하는 페이지이기에 당연한 일이다. 특히 search params와 쿠키에 접근하기에 동적으로 만들어져야한다.

  3. 현재 Static에 분류되는 페이지는 sitemap.xml, opengraph-image 같은 이미지나 XML파일 그리고 not-found페이지이다.

    이들은 서버 데이터에 접근하는 것이 없기에 Static 페이지로 생성이 되었다.

  4. 그렇다면 memos페이지에서는 분명히 쿠키에 접근을 하고 search params에 따라 dialog를 보여주기도 하는 등 Dynamic API를 많이 사용한다. 그럼에도 왜 SSG로 분류가 되는가?

    1. middleware에서 로그인하지 않은 사용자는 login페이지로 redirect를 해주는데, 빌드타임 과정에서 redirect 후 login페이지로 분류되어 SSG가 되었을까?

      • redirect코드를 제거해도 동일하다.
      • 또한 Middleware는 라우팅 레벨에서 동작해 페이지의 static/dynamic 여부 결정에는 영향을 주지 않는다.
    2. [lng]/layout.tsx에서 설정한 generateStaticParams때문일까?

      export async function generateStaticParams() {
        return SUPPORTED_LANGUAGES.map(lng => ({ lng }));
      }
      

      generateStaticParams은 동적 라우트에 정적 생성을 설정하는 함수이다.

      • 그렇다면, Full Route Cache가 아니라 단순히 HTML 껍데기만 만든 것도 SSG로 분류하는 것이 아닐까?
  5. Next.js는 클라이언트 컴포넌트에서의 쿠키 접근은 페이지의 동적/정적 분류에 영향을 주지 않는다.

    RSC에서 쿠키를 사용하더라도 그것이 빌드 타임에 실행되는 코드가 아니라 런타임에 실행되는 코드라면 (API라우트, Server Actions) 페이지 자체는 정적으로 분류될 수 있다.

  6. Dynamic Rendering(SSR)로 바뀌는 조건을 다시 살펴보자.

    렌더링하는 동안, Dynamic API나 {cache:’no-store’} 옵션을 가진 fetch가 발견된다면, Next.js는 해당 라우트를 동적으로 바꾼다.

    여기서 드는 의문점

    1. Toaster같은 컴포넌트는 정적 렌더링에 영향을 미치지 않는가?

      Toaster는 단독으로 존재하고 ThemeProvider, SidebarProvider, QueryProvider는 외부를 감싸 내부에서 children props를 각 컴포넌트의 children props로 넘겨주고 있다.

      이들은 모두 공통점이 클라이언트 컴포넌트라는 것이다. 클라이언트 컴포넌트는 클라이언트에서만 렌더링된다. 그렇기에 빌드타임 때 물론 서버에서 렌더링을 하지 않는다는 것을 의미한다.

      ⇒ 클라이언트 컴포넌트는 CSR이 아니다. 클라이언트 컴포넌트는 SSR에서는 서버에서 렌더링된다. 그리고 클라이언트에서 또 렌더링된다. 그래서 가끔 ‘서버와 클라이언트의 HTML이 불일치합니다’라는 에러가 발생하는 것이다.

      ⇒ 서버 컴포넌트는 항상 서버에서 렌더링이 되지만, SSR과는 다르다. Next.js에서 첫 페이지 접속시에만 SSR이 실행되고, 그 이후에는 CSR로 동작한다. 하지만 서버 컴포넌트는 첫 페이지가 아니더라도 서버에서 렌더링이 된 청크를 직렬화하여 클라이언트에 보내준다.

    2. 그렇다면 서버 컴포넌트에서 cookies에 접근하면 어떨까?

      'use server';
      
      import { cookies } from 'next/headers';
      
      export default async function ServerComponent() {
        const cookieStore = cookies();
        console.log(cookieStore.getAll());
      
        return <div></div>;
      }
      

      위 컴포넌트를 layout.tsx에 삽입해보았다.

      • 여전히 정적으로 생성한다.

        즉 동적 API인 cookies에 접근하는 것과 SSG가 동작하지 않는 것은 관련이 없다는 것을 의미한다.

        근데 위 말은 아래 Next.js 공식문서와 상반되는 내용이다.

        During rendering, if a Dynamic API or a fetch option of { cache: 'no-store' } is discovered, Next.js will switch to dynamically rendering the whole route

        만약 동적 API나 cache:’no-store’ 옵션의 fetch가 렌더링 도중 발견되면, Next.js는 라우트 전체를 동적 렌더링으로 바꾼다.

        그리고 Next.js에서 동적 렌더링에 대한 정의를 아래와 같이 하고 있다.

        With Dynamic Rendering, routes are rendered for each user at request time.

        동적 렌더링에서 라우트는 사용자가 요청할 때마다 렌더링을 수행한다.

        그렇다면 SSG와 동적 렌더링은 다른 것일까? SSG에서도 동적 렌더링을 수행할 수 있는 것인가? 예를들어 정적으로 렌더링한 것 + 동적으로 렌더링하여 이를 하나의 트리로 합쳐서 전달하는 것처럼 말이다.

  7. 빌드 폴더인 .next의 정적으로 생성된 파일을 살펴보면, 무게가 다르다.

    1. next/static/chunks/app/[lng]/memos/page-b4ad..js

      image.png

    2. next/static/chunks/app/[lng]/introduce/page-728...js

      image.png

    각각 빌드된 파일의 미니맵이다. 이것만 봐도 intrduce페이지같이 정적 페이지로 의도한 페이지는 페이지 전체가 빌드 타임 때 렌더링이 되었고, memos페이지같이 동적 페이지로 의도한 페이지는 빌드 타임때 일부만 정적으로 만들어진 것을 확인할 수 있다.

  8. update와 introduce페이지는 정적으로 만들어진 html이 존재하고, memos는 비어있다.

    .next/server/app 폴더이다. 내부에는 generateStaticParams의 반환값으로 넘긴 en과 ko 각각의 폴더가 존재하고, 각 폴더에는 introduce.html, update.html을 비롯한 여러가지 정적인 파일들이 존재한다. 여기서 중요한 것은 memos폴더는 비었다는 것이다.

    image.png

    그리고 introdoce.html에 접속하면 아래와 같이 렌더링이 된다.

    image.png

    왜 위와같이 모든 요소가 잘 안보이냐면, introduce페이지의 각 컴포넌트에는 framer-motion의 애니메이션이 있는데 initial 값이 opacity:0이라 그러하다.

    image.png

    opacity:0을 제거하니 아래와 같은 페이지가 보여진다.

    image.png

    이미지가 왜 이렇게 큰가 살펴보니 css파일이 로드가 안되었다.

    image.png

    스타일 시트에 h-12에 매칭하는 스타일이 존재해야하는데, 실제로는 없는 것을 확인할 수 있다.

    왜 css파일이 없는지 살펴보면 위 파일은 Next.js에서 빌드 타임 때 생성한 html파일이고, css파일은 스크립트에서 다른 곳에서 불러오는 듯하다.

    image.png

    <aside> <img src="/icons/chat_lightgray.svg" alt="/icons/chat_lightgray.svg" width="40px" />

    _next

    : 클라이언트가 .next폴더의 빌드 결과물에 접근하기 위해 공개한 URL

    위 파일은 .next 아래 경로에 위치한다.

    image.png

    왜 위 css파일을 못불러왔나 살펴보면 아래와 같이 네트워크에서 404에러가 발생함을 알 수 있다.

    image.png

    그럼, 실제 빌드된 서버에서는 어떻게 받아올까?

    그렇다면, _next의 경로를 수정해보자.

  9. 조금 딴 길로 많이 셌다. 다시 기존 길로 돌아와보자. 위에서는 정적으로 만들어진 html파일을 분석해보았다. 그리고 memos는 정적인 html파일이 만들어지지 않은 것을 확인했다. js파일은 존재하지만 html파일은 존재하지 않는다.

  10. 이는 generateStaticParams때문이다.

정리

정리하면 generateStaticParams함수를 이용하여 [lng]라는 동적 라우트에 대해 정적 빌드를 해주었다. 이 함수를 사용함으로써, 페이지 전체가 아니라 ko와 en에 해당하는 극소 일부만 정적 빌드되었음에도 빌드 후 SSG라는 표시가 되었던 것이다. 컴포넌트 내부에서는 쿠키와 search params에 접근하는 memos페이지이었음에도 말이다.