Profile image
Jinyoung
Dev

Next.js 버전 업그레이드 (v14 ➡️ v15)

Next.js 버전 업그레이드 (v14 ➡️ v15)
0 views
18 min read

버전 업그레이드 이유

Next.js의 최신 버전은 v15입니다. 그리고 이 버전은 2024년 10월에 발표되었습니다(Stable 기준)

지금 제가 만들고 있는 이 포트폴리오/블로그 모듈(현재 보고 있는 이 블로그 웹) 작성 시작 시점은 2025년 2월이었지만 당시에 모듈을 만들면서 많이 참고했던 다른 모듈이 Next.js v14 기준으로 작성되어서 자연히 이 모듈도 v14 기준으로 만들어졌습니다.

모듈에 원하는 기능 구현이 어느 정도 다 추가되었고 다음으로 무엇을 할까 고민하다가 Next.js 버전을 올려봐야겠다고 생각하게 되었습니다.


업그레이드 과정

Chat with LLM

업그레이드를 어떻게 해야 할지, 특히 FE 모듈의 버전 업그레이드를 해본 적이 없었기 때문에 막연했습니다. 그래서 최근에 사용하기 시작한 Cursor의 도움을 받기로 했습니다.

Cursor에서 package.json 파일을 열고 Chat 기능을 통해 질문했습니다.

Next.js 버전을 현재의 14.2.5에서 15버전으로 업그레이드 하려고 합니다. 어떤 것들을 고려해야 할지 알려주세요.

LLM은 다음과 같이 답변합니다.

Next.js 14에서 15로 업그레이드하는 것에 대해 다음과 같은 중요한 고려사항들을 알려드리겠습니다:

1. 주요 변경사항:
- Next.js 15는 App Router가 기본 라우터가 되었습니다
- Node.js 18.17 이상이 필요합니다
- React 18.2.0 이상이 필요합니다

2. package.json 업데이트 필요 사항
  {
    "dependencies": {
      "next": "^15.0.0",
      "@next/third-parties": "^15.0.0"
      // ... existing code ...
    }
  }

3. 호환성 체크 필요사항:
- 현재 사용 중인 @next/third-parties 등의 Next.js 관련 패키지들도 함께 업그레이드해야 합니다
- 프로젝트에서 사용 중인 다른 패키지들의 Next.js 15 호환성을 확인해야 합니다
- 특히 next-auth의 경우 Next.js 15와의 호환성을 확인하시기 바랍니다

4. 권장 업그레이드 절차:
- 먼저 개발 환경에서 테스트하기
- package.json의 버전을 업데이트하고 npm install 또는 yarn install 실행
- 전체 애플리케이션을 빌드하고 테스트하기
- 특히 다음 기능들을 중점적으로 테스트:
- 라우팅
- API 라우트
- 미들웨어
- 이미지 최적화
- MDX 관련 기능

5. 백업 권장
- 업그레이드 전 현재 코드의 백업을 만들어두시기 바랍니다
- git을 사용중이라면 새로운 브랜치에서 업그레이드를 진행하시기 바랍니다

6. 성능 모니터링
- 업그레이드 후 성능 변화를 모니터링하시기 바랍니다
- Vercel Analytics를 사용 중이시니 성능 메트릭을 확인하시면 좋습니다

이러한 사항들을 고려하여 단계적으로 업그레이드를 진행하시기를 추천드립니다.

막연하게 버전 업그레이드를 어떻게 할까 고민했었는데 구체적인 실행 계획을 보니 이대로 진행하면 되겠다 싶었습니다. 다만 여기에는 몇가지 사실과 다른 점이 있습니다. 예를 들어, Next.js 15부터 App Router가 기본 라우터가 되었다는 부분은 사실과 조금 다릅니다. 그리고 package.json에 필요한 업데이트 필요사항도 조금 이상합니다. @next/third-parties 모듈은 이미 최신 버전을 사용하고 있기 때문입니다.
이렇게 몇가지 사실과 다른 것들이 있긴 했지만 전체적으로 봤을때는 매우 훌륭한 업그레이드 실행 계획이라고 생각했습니다.

그래서 일단은 next 모듈의 버전을 최신으로 변경하고 이 모듈과 연관되어 있는 모듈의 버전도 최신화 하는 것부터 시작하기로 했습니다. 그 이후에 모듈을 빌드 해보고 이상이 없는지 체크하는 것입니다.

모듈 버전 변경

아래 목록은 저의 포트폴리오/블로그 모듈의 package.json에서 이번 Next.js 버전 업그레이드를 위해 변경된 모듈의 이름과 각 모듈 별로 변경된 버전을 나타냅니다.

  • "next": 14.2.515.2.1
    • 현재 최신의 Next.js 버전은 15.2.1입니다. (2025년 3월 7일 현재 기준)
  • "react": ^18^19.0.0
    • Next.js v15에서도 react 18을 지원하기 때문에 꼭 해야 할 필요는 없었지만 이왕 하는김에 react 버전도 업그레이드 하기로 결정했습니다.
  • "react-dom": ^18^19.0.0
    • react 버전과 같이 react-dom 버전도 19로 올렸습니다.
  • "@types/react": ^18^19
    • react 버전과 같이 react-dom 버전도 19로 올렸습니다.
  • "@types/react-dom": ^18^19
    • react 버전과 같이 react-dom 버전도 19로 올렸습니다.
  • "eslint": ^8^9
    • Next.js 공식 문서를 보면 ESLint 9을 지원한다고 합니다. 그래서 같이 버전을 올렸습니다.
  • "eslint-config-next": 14.2.515.2.1
    • next 모듈과 같이 14.2.5에서 15.2.1로 변경했습니다.
  • 그 이외의 다른 모듈들은 Next.js 버전과 크게 호환성 측면에서 문제가 없다고 판단했습니다.

모듈 버전을 변경하고 나서 .next, .velite, node_modules 디렉토리를 삭제하고 나서 npm install을 통해 전체 모듈을 설치했습니다.

그리고 나서 npm run dev 명령어를 통해 로컬에 Next.js 서버를 실행시켰습니다.

모듈 코드 수정

살짝 떨리는 마음으로 브라우저에 접속하여 localhost:3000을 입력했습니다. 다행히 화면에 Home 메뉴가 아무 문제없이 실행되었습니다. 좌측 하단에 이전까지 볼 수 없었던 아주 작은 Next.js 아이콘이 생긴 것을 확인할 수 있었습니다.

nextjs15 icon

브라우저 화면 상으로는 문제가 없었습니다. 하지만 서버 로그를 보니 아래 에러 로그가 출력되었습니다.

Error: Route "/blog/[...slug]" used `params.slug`. `params` should be awaited before using its properties.
Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis
    at getBlogFromParams (src/app/blog/[...slug]/page.tsx:22:15)
    at BlogPage (src/app/blog/[...slug]/page.tsx:62:21)
  20 |   searchParams: BlogPageProps["searchParams"]
  21 | ) {
> 22 |   const slug = params?.slug?.join("/");

위 서버 로그는 /blog/{slug} 경로로 접속한 경우에 출력되는 서버 에러 로그입니다. 이 라우팅 경로를 처리하는 서버 컴포넌트의 경로는 app/blog/[...slug]/page.tsx 입니다.

이 컴포넌트의 원래 코드는 다음과 같습니다.

type BlogPageProps = {
  params: {
    slug: string[];
  };
  searchParams: {
    lang?: string;
  };
};

async function getBlogFromParams(
  params: BlogPageProps["params"],
  searchParams: BlogPageProps["searchParams"]
) {
  const slug = params?.slug?.join("/");

  // find blog by params and return it
}

const BlogPage = async ({ params, searchParams }: BlogPageProps) => {
  const blog = await getBlogFromParams(params, searchParams);
  return <BlogPageDetail blog={blog} />;
};
  • [1-8]: type BlogPageProps를 통해 컴포넌트의 인자 타입을 정의
  • [10-17]: 블로그 글 ID를 받아서 블로그 글 데이터를 리턴
  • [19-22]: 컴포넌트 코드. getBlogFromParams 함수를 호출하여 blog를 획득한 후에 BlogPageDetail 컴포넌트를 통해 구체적인 블로그 글 상세 페이지를 렌더링 합니다.

여기서 오류는 14번째 라인에서 발생합니다.

Cursor는 CLI의 로그를 선택하여 해당 메시지를 Chat으로 전송할 수 있습니다. CLI 창에서 오류 원인을 분석하고자 하는 메시지를 선택한 후에 Command + L 키를 누르면 바로 채팅 인터페이스에 반영됩니다. 그리고 곧바로 엔터를 누르면 LLM이 오류 원인을 분석하여 해결책을 제시합니다.

LLM이 제시한 원인과 해결책은 다음과 같습니다.

Next.js 15에서 발생한 새로운 에러에 대해 설명드리겠습니다. 이 에러는 searchParams를 동기적으로 사용하려고 할 때 발생합니다. Next.js 15에서는 searchParams를 비동기적으로 처리해야 합니다. 다음과 같이 코드를 수정해야 합니다:

export async function getBlogFromParams(
  params: BlogPageProps["params"],
  searchParams: BlogPageProps["searchParams"]
) {
  const parameters = await params;
  const slug = parameters?.slug?.join("/");

  // find blog by params and return it
}
type BlogPageProps = {
  params: Promise<{
    slug: string[];
  }>;
  searchParams: Promise<{
    [key: string]: string | string[] | undefined;
  }>;
};

그리고 이렇게 Promise 방식으로 변경된 이유까지 설명합니다.

이러한 변경이 필요한 이유:
1. Next.js 15에서는 성능 최적화를 위해 searchParams를 비동기적으로 처리합니다
2. 이는 서버 컴포넌트에서 더 효율적인 데이터 로딩을 가능하게 합니다
3. 모든 searchParams 접근은 await를 사용해야 합니다

관련하여 다음 사항들도 확인해주세요:
- 다른 곳에서도 searchParams를 사용하는 부분이 있다면 같은 방식으로 수정해야 합니다
- 이 변경은 Next.js 15의 주요 변경사항 중 하나입니다
- 이는 서버 컴포넌트의 성능 향상을 위한 변경입니다

페이지를 동적으로 처리하는 경우 Param을 받아서 처리하는데 이 부분이 비동기 방식으로 변경된 것 같았습니다.

명확한 이유와 그 배경에 대해서는 잘 모르겠지만 일단 위 사항대로 변경하니 서버 에러 로그가 사라졌습니다. BlogPage 이외에도 서버 컴포넌트로서 Dynamic Route 방식으로 작동하는 컴포넌트가 몇 개 있어서 같은 방식으로 수정했습니다. 그리고 다른 페이지들도 테스트 해본 결과 아무런 문제가 발생하지 않았습니다.

그래서 npm run build 명령어를 통해 빌드를 수행했고 전혀 에러 로그가 출력되지 않은 것을 확인했습니다. main 브랜치에 변경된 코드를 푸시하여 배포까지 진행했습니다.

모든 게 아무런 문제 없이 끝났습니다! 🤓


서버 오류 로그의 원인

이렇게 순조롭게 업그레이드가 끝나고 나니 유일하게 발생했던 오류의 근본적인 원인에 대해 파악하고 싶어졌습니다. 오류 로그를 자세히 다시 보면 https://nextjs.org/docs/messages/sync-dynamic-apis 를 방문하라고 안내합니다.

페이지에 접속해보면 오류의 원인을 설명합니다.

nextjs15_dynamic_api_warn.png

Dynamic rendering을 하도록 Dynamic API(ex: params, searchParams)를 사용하는 컴포넌트가 모듈 어디엔가 있고 이 Dynamic API의 동작 방식이 비동기로 바뀐 것 같습니다.

그런데 구체적으로 변경되었는지는 확인할 수 없었습니다.

진짜 왜 바뀐 걸까?

해답은 Next.js 15 릴리즈 문서에 있었습니다 (https://nextjs.org/blog/next-15#async-request-apis-breaking-change)
그리고 구글링 및 Claude와 ChatGPT의 도움을 받아 아래와 같이 Why에 대해서 정리할 수 있었습니다.

  • Server Side Rendering(SSR)의 경우, 요청별 데이터를 처리할 때 동적 API(Dynamic API)를 사용
  • v15 이전까지 이러한 동적 API는 동기식으로 작동함
  • 다만 컴포넌트의 모든 요소가 이 동적 API 호출 데이터에 의존하는 것이 아니기 때문에 서버에 요청이 도착하기 전에 최대한 준비할 수 있는 것은 준비할 수 있어야 함
  • v15에서 동적 API가 비동기식으로 전환됨에 따라 일부 구성 요소를 미리 렌더링하여 애플리케이션의 전반적인 성능을 개선할 수 있음 (일부 데이터가 준비되는대로 먼저 스트리밍)
  • 또한 서버 컴포넌트는 기본적으로 비동기 방식으로 작동하기 때문에 API의 작동 방식을 동기에서 비동기도 변경하는 것이 이 패러다임에 더 부합한다고 볼 수 있음

즉, 문제가 됐었던 BlogPage 컴포넌트는 Dynamic rendering을 위해 Dynamic API를 사용합니다. 그렇기 때문에 Next.js v15로 업그레이드 하면서 영향을 받았던 것입니다. (다만 BlogPage 컴포넌트는 위에서 설명하지는 않았지만 내부에 generateStaticParams 함수를 두어서 빌드 시점에 미리 렌더링 되고 실제 배포되어서는 Static rendering으로 동작합니다.)

여기까지 확인하니 어느 정도는 이해가 되었기 때문에 여기서 Next.js v15 업그레이드 작업을 마무리 했습니다.

참조 문서 및 영상


이걸로 끝?

다만 몇 가지 걸리는 게 있었습니다.

  1. Next.js의 수 많은 렌더링 방식에 대해 완전히 숙지한 상태가 아니기 때문에 Dynamic API의 비동기 전환이 구체적으로 왜 렌더링 성능상 이점이 있는지 확실하게 이해되지는 않았습니다.

  2. Next.js v15 공식 문서를 보면 새롭게 추가되거나 이번 버전으로 인해 Stable 버전이 된 것들이 있었습니다.

    • React 19
    • Turbopack Dev (Stable)

앞으로 이러한 개념들에 대해 정리하고 공유하는 블로그 포스팅을 할 기회가 있기를 바랍니다.

그럼 끝까지 읽어주셔서 감사합니다!

Comments (0)

Checking login status...

No comments yet. Be the first to comment!