Next.js + Vercel TTFB 문제 2

Next.js
2024-03-19

이전 글에서 Edge runtime으로 TTFB 문제를 해결했었는데, Express를 Next.js로 이미그레이션 후에는 Edge runtime을 사용할 수 없어서 다른 방법으로 문제를 해결했다.

Vercel + Edge runtime

장점

  • Edge runtime은 제한된 웹 표준 API만 사용해서 비용, 효율적인 면에서 좋다.
  • V8 엔진에 구축되므로 컨테이너나 가상 머신이 필요 없는 분리된 실행 환경에서 실행할 수 있다.
  • Vercel은 전세계적으로 Edge Network를 구축했기 때문에, 사용자에게 가장 가까운 데이터 센터 혹은 DB에 가까운 지역에서 실행돼서, 지연이 크게 줄어든다.

단점

  • 일부 Node.js API를 지원하지 않기 때문에, 어떤 Node.js modules는 사용 못할 수도 있다.

    ex) 파일 시스템을 읽거나 쓸 수 없다. 내 블로그 프로젝트에서는 sharp(이미지 리사이징 모듈), jsonwebtoken을 이용할 수 없었다.

  • MongoDB 스키마를 구현하면 에러가 발생한다. (https://mongoosejs.com/docs/nextjs.html)

의문점

다른 프로젝트에서는 TTFB 문제가 발생하지 않았는데 블로그에서만 TTFB 문제가 발생했다.

그래서 프로젝트들의 빌드 로그를 살펴봤는데, 블로그에서만 모든 라우트가 Dynamic으로 동작하고 있었다.

블로그

image

(모든 라우트가 Dynamic으로 동작하여 서버에서 렌더링된다)

다른 프로젝트

image

(Dynamic Route들만 Dynamic으로 동작하고, 나머지는 Static으로 동작한다)

라우트가 Dynamic으로 동작할 때 TTFB 속도가 느린 이유

Vercel + Node.js Runtime은 cold starts로 인해 함수를 처음 호출할 때 지연이 발생한다.

그래서 TTFB 문제가 발생했던 것이었다.

해결 방법

라우트의 동적, 정적 렌더링은 Next.js의 Full Route Cache 기능과 연관이 있다.

이 기능이 적힌 문서를 보고, 왜 모든 라우트가 Dynamic으로 동작하는지 일단 확인해보았다.

라우트가 동적으로 렌더링되는 이유

  1. 동적 함수인 cookies, headers그리고 searchParams 를 사용했을 때
  2. dynamic = 'force-dynamic' 또는 revalidate = 0 라우트 segment 구성 옵션을 사용했을 때
  3. 캐시되지 않은 fetch 요청이 있을 때

참고 : https://doromo.vercel.app/post/19#적용-해제

내 블로그가 동적으로 렌더링 됐던 이유

루트 Layout에서 전역으로 쿠키를 불러오고 저장할 수 있게 해주는 next-client-cookies 라이브러리를 사용하고 있었는데, Provider을 설정할 때, cookies 동적 함수를 사용되었던 것이다.

→ 루트 Layout에서 동적 함수를 사용하고 있어서, 모든 라우트가 동적으로 렌더링됐다.

export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="kr" suppressHydrationWarning={true}> <body> <script dangerouslySetInnerHTML={{ __html: setInitialThemeMode }} /> <Recoil> <ClientCookiesProvider value={cookies().getAll()}> <Provider> <TopNav /> <ReactQuery>{children}</ReactQuery> <ScrollToTop /> <Footer /> </Provider> </ClientCookiesProvider> </Recoil> </body> </html> ); }
export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="kr" suppressHydrationWarning={true}> <body> <script dangerouslySetInnerHTML={{ __html: setInitialThemeMode }} /> <Recoil> <ClientCookiesProvider value={cookies().getAll()}> <Provider> <TopNav /> <ReactQuery>{children}</ReactQuery> <ScrollToTop /> <Footer /> </Provider> </ClientCookiesProvider> </Recoil> </body> </html> ); }
"use client"; import { CookiesProvider } from "next-client-cookies"; export const ClientCookiesProvider: typeof CookiesProvider = (props) => ( <CookiesProvider {...props} /> );
"use client"; import { CookiesProvider } from "next-client-cookies"; export const ClientCookiesProvider: typeof CookiesProvider = (props) => ( <CookiesProvider {...props} /> );

해결 방법

Express에서 Next.js로 이미그레이션 후에 Next.js의 Route Handlers를 사용하고 있는데, Route Handlers에서는 브라우저에 쿠키를 직접 설정할 수 있으므로, 라이브러리를 제거하고 동적 함수를 제외했다.

관련 포스트

post thumbnail

Next.js + Vercel TTFB 문제

로컬 환경에서는 애플리케이션의 TTFB(Time To First Byte)가 70~150ms정도 걸리는데, 배포 환경에서는 TTFB가 느리면 1500ms, 빠르면 400ms정도가

2024-03-12
post thumbnail

데스크톱, 모바일 호환되는 가로 슬라이더 구현

Next.js 14, Tailwindcss, Typescript 터치 이벤트는 일반적으로 터치 스크린을 가진 디바이스에서 이용가능하다. 하지만, 터치 스크린을 갖는 디바이스를 포함

2024-03-12
post thumbnail

[번역] Next.js 14

Next.js 14는 다음과 같은 가장 중점으로 둔 릴리스이다. - Turbopack: App & Pages Router에서 5,000번의 테스트 통과 - 로컬 서버 시작이 53%

2024-03-12
post thumbnail

[번역] 캐싱

Next.js는 렌더링 작업과 데이터 요청을 캐싱하여 어플리케이션의 성능을 향상시키고 비용을 감소시킨다. 기본적으로 Next.js는 성능을 향상시키고 비용을 줄이기 위해 가능한 한

2024-03-12

도로모의 기술 블로그

# Contact : jyw966@naver.com

Copyright © doromo. All Rights Reserved.