[Next js] Route 이해하기

안광의·2022년 6월 19일
1

Next js 이해하기

목록 보기
2/2
post-thumbnail
post-custom-banner

시작하며

React 프로젝트를 Next js로 마이그레이션 하면서 느꼈던 차이점은 라우팅하는 방법이였다. Next js는 pages 폴더 내의 폴더, 파일명으로 자동으로 path가 설정되기 때문에 이를 먼저 이해하고 디렉토리 구조를 결정해야 한다. React와의 다른 라우팅 방법을 정리하면서 getstaticpaths 를 사용하여 dynamic routing 하는 방법도 정리해보려고 한다.



Route

path 설정

React

//App.js
import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
...

function App() {
  ...

  return (
    <div className='App'>
      <BrowserRouter>
        <Switch>
          <Route exact path='/' component={Main} />
          <Route path='/login' component={Login} />
          <Route path='/setting' component={Setting} />
          <Route path='/search/query=:word' component={Search} />
          <Route path='/search/query=' component={Search} />
          <Route path='/naver' component={NaverLogin} />
        </Switch>
      </BrowserRouter>
    </div>
  );
}

export default App;

리액트는 react-router-dom을 사용해서 path를 설정하고 동적 라우팅의 경우 :(콜론)을 사용하여 값을 설정해줘야 한다.

react-router-dom v5 기준으로 작성되어 v6과는 차이가 있음

Next js

//Next js pages 폴더 디렉토리 구조
src/pages
├── _app.tsx
├── index.tsx
├── naver.tsx
├── preview.tsx
├── search
│   ├── [word].tsx
│   └── index.tsx
└── setting
    ├── auto-complete.tsx
    ├── search-data.tsx
    └── site.tsx

Next js의 경우 따로 path를 설정하는 것이 아니라 /pages 혹은 /src/pages 폴더 내의 폴더명, 파일명에 맞춰서 자동으로 path가 설정 된다. (/src/pages 가 존재하는 경우에 /pages는 무시된다.)

동적 라우팅의 경우에는 파일명이나 폴더명을 [](대괄호)로 감싸주면 된다. 위의 예시에서 /search/test로 이동했다면 [word].tsx에 해당하는 컴포넌트가 렌더링 되며 "test"라는 값을 해당 컴포넌트에서 받아올 수 있다.



import Link from 'next/link'

function Home() {
  return (
    <ul>
      <li>
        <Link href="/">
          <a>Home</a>
        </Link>
      </li>
      <li>
        <Link href="/about">
          <a>About Us</a>
        </Link>
      </li>
      <li>
        <Link href="/blog/hello-world">
          <a>Blog Post</a>
        </Link>
      </li>
    </ul>
  )
}

export default Home

react-router 처럼 Next js도 next/linkLink를 사용하여 페이지를 이동할 수 있다.

  • href(필수) : 이동할 경로 또는 URL
  • as : 브라우저 URL 표시 줄에 표시 될 경로
  • passHref : 기본값은 false로, Link가 href 속성을 child에게 보낸다.
  • prefetch : 기본값은 true로, 백그라운드에서 페이지를 미리 가져온다. 뷰포트에 있는 모든 항목이 미리 로드 되며, prefetch={false}을 전달하여 비활성화 할 수 있다. false 여도 마우스를 hover 하면 프리페치가 계속 발생한다. 정적 생성을 사용하는 페이지는 더 빠른 페이지 전환을 위해 데이터와 함께 JSON 파일을 미리 로드한다. 프리 페치는 프로덕션에서만 활성화된다.
  • replace : 기본값은 false로, history 스택에 새 URL을 추가하지 않고 현재 상태만 교체한다.
  • scroll : 기본값은 true로 페이지 이동 후 상단으로 스크롤한다.
  • shallow : 기본값은 false로 getStaticProps, getServerSideProps, getInitialProps 를 실행하지 않고 현재 페이지의 경로를 업데이트한다.
  • locale : 활성 locale 이 자동으로 앞에 추가된다.

import Link from 'next/link'

function Home() {
  return (
    <ul>
      <li>
        <Link
          href={{
            pathname: '/about',
            query: { name: 'test' },
          }}
        >
          <a>About us</a>
        </Link>
      </li>
      <li>
        <Link
          href={{
            pathname: '/blog/[slug]',
            query: { slug: 'my-post' },
          }}
        >
          <a>Blog Post</a>
        </Link>
      </li>
    </ul>
  )
}

export default Home

Link는 href의 값으로 URL 객체도 받을 수 있다.

  • 사전 정의된 경로: /about?name=test
  • 동적 경로: /blog/my-post


next/route

react-router-domuseLocation useHistory(v6부터 useNavigate)의 기능을 Next js에서는 'next/router'의 useRouter 로 구현할 수 있다.

import { useRouter } from 'next/router';

export default function Home() {
  const router = useRouter();
  console.log(router);
  // ...
}
  • pathname : string : 현재 경로로 /pages 의 페이지 경로이며(파일명), basePath 또는 locale 은 포함되지 않는다.
  • query : object : 동적 라우트 매개변수를 포함하는 객체로 구문 분석된 쿼리 문자열
  • asPath : string : basePath 또는 locale 없이 브라우저에 표시되는 경로(query 포함)
  • isFallback : boolean : 현재 페이지의 fallback 모드 여부
  • basePath : string : 활성화된 basePath
  • locale : string : 활성화된 locale
  • locales : string[] : 지원되는 모든 locale
  • isReady : boolean : 라우터 필드가 클라이언트 측에서 업데이트되고 사용할 준비가 되었는지 여부. useEffect 메소드 내에서만 사용해야하며 서버에서 조건부로 렌더링 하는 데에 사용해야 한다.
  • isPreview : boolean : 애플리케이션의 미리보기 모드 여부

router.push

클라이언트 사이드의 라우팅 처리를 할 수 있는 기능으로 react-router-dom useHistory(v6부터 useNavigate) 의 기능과 유사하다.

router.push(url, as, options)
  • url(필수) : 이동할 경로
  • as : 브라우저에서 표시된 경로
  • options
    • scroll : 기본값은 true로 페이지 이동 후 상단으로 스크롤한다.
    • shallow : 기본값은 false로 getStaticProps, getServerSideProps, getInitialProps 를 실행하지 않고 현재 페이지의 경로를 업데이트한다.
    • locale : 새 페이지의 로케일을 나타낸다.
import { useRouter } from 'next/router'

export default function ReadMore({ post }) {
  const router = useRouter()

  return (
    <button
      type="button"
      onClick={() => {
        router.push({
          pathname: '/post/[pid]',
          query: { pid: post.id },
        })
      }}
    >
      Click here to read more
    </button>
  )
}

Link 와 마찬가지로 첫번째 인자로 URL 객체를 받을 수 있다.

Dynamic Route 페이지 생성하기

Dynamic Route된 페이지는 빌드 시에 path의 값을 알 수 없기 때문에 SSR로 페이지를 구현해야 하지만 getstaticpaths를 사용하면 SSG나 ISR로 페이지를 미리 생성할 수 있고, 새로운 path에 대해 요청이 왔을때 올바른 path인 경우에 새로운 페이지를 렌더링할 수 있다.

path가 고정되어 있는 경우

export const getStaticPaths = () => {
  const paths = [
    { params: { id: '1' } },
    { params: { id: '2' } },
    { params: { id: '3' } },
  ];

  return {
    paths,
    fallback: false,
  };
};

export const getStaticProps = async (context: {
  params: { id: string };
}) => {
  const res = await axios.get(`https://test.api/${id}`);

  return {
    props: { info: res.data.info },
    revalidate: 20,
  };
};

export default function DefaultPage({ info }) {
  return <div>{info}</div>
}

/test/[id].tsx 파일에서 id로 받을 path가 '1', '2', '3' 으로 고정되어 있다면getStaticPaths 함수에서 정해진 값을 리턴해주면 getStaticProps 가 그 값을 받아서 빌드 시에 정적 페이지를 생성한다. revalidate: 20 옵션을 설정해주었기 때문에 20초마다 페이지를 새로 생성하므로 업데이트 된 fecthing 데이터를 반영한다. getStaticPaths 가 리턴하는 객체에 fallback: false를 설정하면 '1', '2', '3' 외에 다른 path의 요청이 올 경우에는 404 에러 페이지가 출력된다.

path가 변하는 경우

export const getStaticPaths = async () => {
  const res = await axios.get(`https://test.api/list`);
  const paths = res.data.list.map(({ id }) => {
    return { params : { id } }
  })

  return {
    paths,
    fallback: 'blocking',
  };
};

export const getStaticProps = async (context: {
  params: { id: string };
}) => {
  const res = await axios.get(`https://test.api/${id}`);
  
  if (!res.data.info) {
    return {
      notFound: true,
      revalidate: 20,
    };
  }

  return {
    props: { info: res.data.info },
    revalidate: 20,
  };
};

export default function DefaultPage({ info }) {
  return <div>{info}</div>
}

만약 path로 사용하는 값이 추가로 생성되거나 삭제될 수 있는 데이터의 id라면 fallback: 'blocking' 을 사용하면 된다. 빌드 시 getStaticPaths 함수에서 해당 시점의 id를 받아 리턴하면 각 id를 path로 하는 정적 페이지가 생성되는데 fallback: 'blocking' 으로 설정했기 때문에 새로운 path에 대해 요청을 받으면 그 시점에 새로운 정적 페이지를 생성하게 된다.

단, 주의할 점은 기존 데이터가 삭제되어서 revalidate 옵션에 의해 새롭게 페이지 생성시 에러가 발생하더라도 Next js는 이전에 정상적으로 생성되었던 페이지를 출력하기 때문에

  if (!res.data.info) {
    return {
      notFound: true,
      revalidate: 20,
    };
  }

이렇게 해당하는 데이터가 없을때 강제로 404 페이지를 리턴하도록 설정해주어야 삭제된 데이터에 해당하는 페이지가 출력되는 것을 막을 수 있다.

마치며

SSR, SSG, ISR에 대해서 이해하고 route하는 방법을 아는 것 만으로도 React 프로젝트를 Next js로 마이그레이션 하는 과정을 진행하는데 크게 어려움이 없었다. Next js는 공식문서가 잘 정리되어 있기 때문에 기존에 React를 사용하여 구현했던 기능들을 어떻게 대체해야 하는지 쉽게 찾을 수 있어서 무리없이 진행할 수 있었다.

profile
개발자로 성장하기
post-custom-banner

0개의 댓글