Next.JS 기존 프로젝트 v13 / TypeScript / App Router 마이그레이션

SUNG JUN LEE·2023년 10월 23일
0

Next.js를 처음 공부하던 레포지토리를 마이그레이션 해보자.

V13 / TypeScript 마이그레이션

프로젝트 버전

  • next : 12.1.2
  • react : 17.0.2
  • react-dom : 17.0.2
  • eslint : 8.12.0
  • eslint-config-next : 12.1.2

우선 next, react, react-dom, eslint, eslint-config-next 의 버전들을 업데이트 해야한다.

CLI
npm i next@latest react@latest react-dom
npm i -D eslint@latest eslint-config-next@latest

이후 TypeScript, @types/react, @types/node 를 설치하자.

CLI
npm i -D typescript @types/react @types/node

이후 모든 JS 파일들을 .ts, .tsx 로 변경해주고, 추가적으로 root 경로에 tsconfig.json 을 생성하고 개발모드(npm run dev) 를 실행하면 알아서 나머지 환경이 구성된다.

마이그레이션 후 버전 및 디렉토리 구조

마이그레이션 작업이 완료된 줄 알았으나, TypeScript Eslint parser를 설치해주어야 했다.
ESlint가 타입스크립트 코드를 린팅해주기 위해서는 @typescript-eslint/parser 를 설치해주어야 한다.

Path alias

우선 마이그레이션 전에 절대경로 설정을 해주려고 한다.
불필요하게 길어지는 상대경로에서 절대경로 설정으로 해주면 편하기도 하고 읽기도 쉬워진다.

// tsconfig.json
{
... 기존 옵션들
"baseUrl": '.',
"paths": {
	"@/app/*": ["app/*"],
	"@/components/*": ["components/*"],
	"@/styles/*": ["styles/*"]
	}
}

App Router

앱라우터로 변경되면서 _document, _applayout 으로 통합되거나 등 많은 변화가 있는데 추후 정리하는것으로 하고 우선 마이그레이션 작업을 진행

우선 layout 으로 통합 작업부터 진행 해보자.

두 구조가 통합됨으로써 기존의 SEO, Modal 엘리먼트 등 을 추가되는 layout 에서 해야 되는 구조로 바뀌었고 Context 같은 Provider 를 넣어주어야 할때는 클라이언트 컴포넌트로 바꾸어야 한다고 한다.

Provider 관련된 거는 공통 레이아웃 컴포넌트랑 비슷하게 따로 빼면 되고, 기본적인 틀만 적용하였다.

// app/layout.tsx

import { ReactNode } from 'react';
import type { Metadata } from 'next';

import Layout from 'components/layout/Layout';

import 'styles/globals.css';

export const metadata: Metadata = {
  viewport: {
    // width=device-width, initial-scale=1, maximum-scale=1
    width: 'device-width',
    initialScale: 1,
    maximumScale: 1,
  },
  icons: {
    icon: '/favicon.ico',
  },
  title: 'Next,js PlayGround',
  description: 'Welcome to Next.js PlayGround',
  keywords: ['Next.js', 'TypeScript', 'PlayGround'],
  authors: [{ name: 'Opdata' }],

  // ... openGraph, 등 구체적인 메타데이터 설정 가능
  // default 혹은 dynamic metadata 설정 가능
};

type RootLayout = {
  children: ReactNode;
};

const RootLayout = ({ children }: RootLayout) => {
  return (
    <html lang="ko">
      <body>
        <Layout>{children}</Layout>
      </body>
    </html>
  );
};

export default RootLayout;

기본적으로 알아야할 건, app 디렉토리 구조에서 기본적으로 만들어진 컴포넌트는 서버컴포넌트 이며, 우리가 기존에 사용하던 hook, 브라우저 API 를 사용할 수 없다.

그래서 서버컴포넌트 / 클라이언트컴포넌트 를 쓰게될지 잘 구별하여 사용하여야 한다.

우선 잠깐 마이그레이션 해본 경험으로는 필요에 따라 맞춰서 사용하면 될거라고 생각되고 클라이언트 컴포넌트로 사용하고 싶다면 코드 맨 윗줄에 use client 를 넣어주면 된다.

app 디렉토리 페이지 구성

기본적인 app 디렉토리의 구조는 사진과 같은 느낌으로 가면 된다.
경로에 해당하는 폴더를 만들고 파일은 page 라는 이름으로 만들면 되고, 추가적으로 페이지에 해당되지 않게 하려면 (...name) 이런식으로 하면 된다고 확인한거 같은데 위 레포에서는 사용할 필요가 없었다.

그리고 위 코드를 작성해준 layout 은 꼭 app 내에 있어야 한다고 한다.

또한, 다른 사람들의 Next 13 버전의 폴더 구조를 보았는데 app 내에 components 같은 폴더 구조를 넣어주어도 상관없고, 나 처럼 외부에 넣어도 되는 것 처럼 꼭 정해진 규칙이 있는게 아닌 상황이다.

개인적인 생각으로는 바깥으로 놓는게 더 보기 편해지고 depth를 줄이는 방법이 아닐까 싶다.

기존 React에서도 개인적으로는 src 폴더 내에 쭉 있는게 마음에 들지 않는데 굳이 정해진게 아니라면 Next 에서는 바깥으로 빼놓고 사용하는게 좋지 않을까 라는 생각이다.

그 외 반영했던 부분

메소드 변경사항

아 그리고 13버전으로 업데이트 되면서 app 라우터를 사용하면 기존에 사용하던 getServerSideProps 등 이런 메소드를 사용할 수 없어서 작업 중 문서를 좀 많이 보게 되었다.

import MeetupDetail from 'components/meetups/MeetupDetail';

export const getDetailMeetup = async (params) => {
  const queryString = Object.keys(params)
    .map((key) => `${key}=${params[key]}`)
    .join('&');

  const res = await fetch(`http://localhost:3000/api/detail?${queryString}`, {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
    },
  });
  // console.log(res);
  return res.json();
};

const MeetUpDetail = async ({ params }) => {
  const res = await getDetailMeetup(params);

  return (
    <MeetupDetail
      image={res.image}
      title={res.title}
      address={res.address}
      description={res.description}
    />
  );
};

export default MeetUpDetail;

이런 느낌으로 그냥 fetch API 를 사용하여 가져오면 된다. 오히려 참고 해야 할 요소들이 더 적어진 느낌이다. 물론 fallback 이나 이런 것들에 대해서 자세히 설정하는 건 또 별개의 일이 되었지만...

dynamic 페이지 에서의 params

page 라우터 일때는 getStaticPaths 이런걸로 params 값을 가져와서 API로 쏘고 데이터를 가져오고 이런 형태 였다면 쓸수없게 되어서 바꾸어 주었다.

import MeetupDetail from 'components/meetups/MeetupDetail';

export const getDetailMeetup = async (params) => {
  const queryString = Object.keys(params)
    .map((key) => `${key}=${params[key]}`)
    .join('&');

  const res = await fetch(`http://localhost:3000/api/detail?${queryString}`, {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
    },
  });
  // console.log(res);
  return res.json();
};

const MeetUpDetail = async ({ params }) => {
  const res = await getDetailMeetup(params);

  return (
    <MeetupDetail
      image={res.image}
      title={res.title}
      address={res.address}
      description={res.description}
    />
  );
};

export default MeetUpDetail;

해당 페이지에서 { params } 로 가져오고 요청 코드에 쏘고.. 이런식으로 사용하면 될 듯 하다.


다음에는 next/image 변경에 대해 적용 예정

참고 링크

profile
FE developer

0개의 댓글