치춘짱베리굿나이스

App 라우터와 Pages 라우터 본문

ClientSide/Next.js

App 라우터와 Pages 라우터

치춘 2023. 5. 13. 13:00

App 라우터, Pages 라우터

Pages 에서 App으로 넘어가기

내가 Next.js 공부를 시작한지도 어언 1달 정도가 되었는데… 5월 초쯤 (5월 5일?) v13.4.0 릴리즈부터 갑자기 앱 라우터가 stable 해 지면서 권장사항으로 바뀌었다

진행하던 미니 프로젝트 레포 초기화 할 때만 해도 App 라우터는 experimental이라 pages로 가는 것을 추천했었는데 이런 대격변이 아닐 수 없다

이렇게 갑작스럽게 바뀔 줄 알았으면 진작에 App 라우터로 초기화했지!…

Next.js 공식문서엔 Next 13버전으로 업데이트되면서 변경된 사항들 몇몇개를 같이 소개하고 있지만 여기서는 App 라우터로만 변경하는 것을 적어보기로 한다 (사실상 번역만 하는 수준)

패키지 업데이트

npm install next@latest react@latest react-dom@latest

next, react, react-dom을 모두 최신 버전으로 업데이트하자

npm install -D eslint-config-next@latest

eslint를 사용 중이라면 eslint config도 전부 업데이트한다

폴더 구조와 라우터

/, /about, /songs, /songs/[song] 네 종류의 경로 라우팅을 각각의 방식으로 구성해본 모습이다

pages 시절엔 파일명으로 라우팅을 해 줬다면, app 에서는 폴더별로 경로를 구분한다

  • 모든 경로는 폴더명으로 구분하며, 페이지 컴포넌트는 폴더 내부의 page.tsx 에서 default export 되는 컴포넌트를 사용한다
    • 루트 경로 (/) 는 최상단 app 폴더의 page.tsxlayout.tsx로 표현된다
  • slug (Dynamic Route) 또한 폴더명으로 구분되며, 작명 방식은 pages 때와 같다
  • layout.tsx는 해당 경로와 하위 경로의 페이지에 공통적으로 적용되는 레이아웃을 정의한다
    • 루트 경로 (/) 의 레이아웃 (root layout) 파일은 필수이다
    • 루트 경로의 레이아웃에는 html, body 태그가 필수이다 (Next.js에서 별도로 생성해주지 않는다)
  • _app.tsx_document.tsx 가 불필요해졌다
    • _app.tsx_document.tsx를 migration 할 때는 내용물을 전부 루트 경로의 layout.tsx 에 붙여넣으라고 되어 있다
    • 직접 해 봤는데 약간의 수정이 필요하긴 하다 (layout.tsx는 props 로 children 을 받는다)

헤더 메타데이터 표현

// pages

import Head from 'next/head';

export default function Page() {
    return (
        <>
            <Head>
                <title>타이틀</title>
                <meta name='theme-color' content='#ffffff' />
        <meta name='robots' content='index, follow' />
            </Head>
            <main>
                <div>하이하이~</div>
            </main>
        </>
    );
}

기존의 page 라우터에서는 Next에서 지원해주는 Head 컴포넌트를 이용하여 메타데이터를 표현하면 Next가 이를 직접 헤더 쪽으로 옮겨줬었다

// app

import { Metadata } from 'next';

export const metadata: Metadata {
    title: '타이틀',
    themeColor: '#ffffff',
    robots: { index: true, follow: true },
}

const Page = () => {
    ...
}

export default Page;

app 라우터에서는 대신 Metadata 형식의 객체를 정의하는 방식으로 헤더 메타데이터와 SEO를 지원한다고 한다

어떤 건 string 을 받고 어떤 건 내부에 하위 객체를 받는 항목이 있어서 좀 헷갈리기 때문에 찾아봐가면서 넣는 것이 좋다

메타데이터 객체를 export하는 위치는 page.tsx, layout.tsx 둘 다 상관 없이 똑같이 동작한다

데이터 받아오기

export async function getStaticProps() {
  const data = await getData();
  return {
    props: { data },
  };
}

interface Props {
  data: DataType;
}

const Page = ({ data }: Props) => {
    ...
}

export default Page;

page 라우터에서는 getStaticProps, getServerSideProps 등의 함수를 페이지 파일 내부에 선언해서 Next가 데이터를 받아오게끔 했었다

getStaticPropsgetServerSideProps 의 반환값은 자동으로 페이지 컴포넌트의 props 으로 전달되었으며, 페이지 컴포넌트들은 기본적으로 클라이언트 사이드 컴포넌트로 동작했다

const Page = async () => {
    const data = await getData(); 

    ...
}

export default Page;

app 라우터에서 모든 페이지 컴포넌트들은 서버사이드 컴포넌트로, 컴포넌트 내에서 데이터를 fetch 하면 컴포넌트가 알아서 서버사이드로 처리되는 방식이다

  • 컴포넌트에서 async-await 키워드를 사용할 수 있다
    • 컴포넌트를 async 함수로 선언하고 데이터 받아올 때 await 를 사용하면 서버사이드로 데이터를 받아오게 된다
  • 만약 페이지 내용을 클라이언트 컴포넌트로 구성하고 싶다면, 내용을 별도의 컴포넌트로 분리한 뒤 import하여 prop으로 데이터를 넘겨주는 방법이 적절하다

useRouter() 훅

import { useRouter } from 'next/router';

...
const router = useRouter()
// router.pathname
// router.query
...

원래 useRouternext/router 경로에서 import 되었으며, 경로명 (pathname) 과 쿼리 (search params) 까지 한번에 받아와주는 훅이었다

import { useRouter, usePathname, useSearchParams } from 'next/navigation';

...
const router = useRouter();
const pathname = usePathname();
const query = useSearchParams();
...

새로운 useRouter 는 아예 별도의 경로인 next/navigation에서 import 된다

  • 기존의 next/router에 있던 useRouter 는 app 라우터에서 지원하지 않는다
    • 기존의 useRouter는 pages 라우터에서만 사용가능
    • 그 외에도 isFallback, locale, basePath, asPath, isReady 등의 기능들이 삭제되었다고 한다
  • pathname 과 search params 를 전달하는 훅이 아예 별도로 분리되었다 (usePathname, useSearchParams)
    • 두 훅을 조합하여 페이지 url 의 변화를 알아차릴 수 있다
  • 따라서 새로운 useRouter 는 pathname, search params를 반환하지 않는다
    • pages에서 migration 해 올 때 유의하자

당연하게도 useRouter, usePathname, useSearchParams는 훅이므로 클라이언트 컴포넌트에서만 사용가능하다

스타일링

  • tailwind, scss module 은 서버사이드에서도 정상적으로 사용 가능하다
  • styled-components, emotion 등 CSS-in-JS 라이브러리는 클라이언트 사이드 컴포넌트에서만 사용 가능하다

여기서 엄청난 애를 먹고 있는데, 모든 스타일링을 emotion으로 해 놨더니 모든 컴포넌트를 클라이언트 사이드로 돌려야 하는 불상사가 발생했다

사실상 모든 컴포넌트가 클라이언트사이드로 동작하면 Next를 쓰는 의미가 없다고 느껴져서… scss module 로 마이그레이션 진행 중인데 emotion 객체형을 썼더니 복붙으로 해결이 안 돼서 고칠 게 산더미다 ㅡ,,ㅡ;;

여담

예전에 react-router-dom v6가 나왔을 때에도 비슷한 상황이었는데 (기껏 react-router-dom v5로 공부해서 겨우겨우 컴포넌트 하나씩 만들어보고 있던 시절) Next도 안정화까지 조금 오래 걸릴 줄 알았던 app 라우터가 갑자기 뚱 하고 나와버리니 당황스럽지 않을 수 없다

역시 프론트엔드 생태계는 너무 빨리 업데이트돼서 문제… 문제..? 재밌..재밌다


참고 자료

https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration

Comments