Next.js - Routing

tunggary·2022년 1월 19일
1

Next.js

목록 보기
4/7
post-thumbnail

next.js 에서는 Routing을 쉽게 할 수 있도록 지원하고 있다. pages 폴더에 파일시스템 방식으로 파일, 폴더를 만들면 자동으로 파일, 폴더 이름을 기반으로 Routing을 지원한다.

pages/index.js → /
pages/posts/first-post.js → /posts/first-post

Dynamic Routes

1. [id]

만약 유저 id별로 페이지를 만들어야 한다면 routing할 파일, 폴더는 다음과 같이 생성하면 된다.

pages/blog/[slug].js → /blog/:slug (ex /blog/hello-world)
pages/[username]/settings.js → /:username/settings (ex /foo/settings)

2. Catch-all routes

Catch-all routes는 pages/post/[...slug].js 과 같이 파일 이름을 대괄호 안에 ...을 쓰고 key로 사용할 값을 적은 후 파일을 생성하면 된다.

pages/post/[...slug].js → /post/a, /post/a/b, /post/a/b/c 등등 (/post 는 안됨)

3. Optional catch all routes

Optional catch all routes는 pages/post/[[...slug]].js 과 같이 파일 이름을 이중 대괄호 안에 ...을 쓰고 key로 사용할 값을 적은 후 파일을 생성하면 된다. Catch all routes와 똑같지만 이 경우 /post도 포함된다.

pages/post/[...slug].js → /post ,/post/a, /post/a/b, /post/a/b/c 등등

getStaticPaths

사용법

그렇다면 id별로 데이터는 어떻게 가져오는가? 만약 페이지에서 getStaticProps를 사용하여 데이터를 fetching 하는 경우 getStaticPaths를 이용해서 getStaticProps에 id를 넘겨 줄 수 있다.

getStaticPaths는 build time에 실행되기 때문에 오직getStaticProps를 사용해서 데이터를 가져오는 경우만 사용할 수 있다.

next.js는 dynamic routing일 경우 Pre-rendering을 getStaticPaths 함수의 key값을 이용해서 한다. 따라서 getStaticPaths 함수는 return 값으로 key값을 포함해야 Pre-rendering을 할 수 있다.

예를 들어 pages/posts/[id].js 와 같이 routing할 파일을 만들었으면
[id].js 파일에 getStaticPaths 함수는 다음과 같이 id(key) 값을 return 해야한다.

export async function getStaticPaths() {
  ...
  return {
    paths: [
      { params: { id: '1' } },
      { params: { id: '2' } }
    ],
    fallback: ...
  }
}

이 경우 build time에 /posts/1 과 /posts/2 페이지를 [id].js 페이지의 컴포넌트를 이용해서 생성한다.

이처럼 params object는 파일 이름과 같은 키 값을(여기선 id) 가지고 있어야 한다. 만약 페이지 이름이 pages/posts/[postId]/[commentId] 이면 [comment].js 의 getStaticPaths 함수는 다음과 같이 return 해야한다.

export async function getStaticPaths() {
  ...
  return {
    paths: [
      { params: { postId: '1', commentId: '1' } },
      { params: { postId: '2', commentId: '1' } }
    ],
    fallback: ...
  }
}

다른 경우로 Catch-all routes 일 때 페이지 이름이 pages/posts/[...id].js 이면 [...id].js 의 getStaticPaths 함수는 다음과 같이 return 해야한다.

export async function getStaticPaths() {
  ...
  return {
    paths: [
      { params: { id: ["a", "b", "c"] } },
      { params: { id: ["a", "b"] } },
      { params: { id: ["a"] } }
    ],
    fallback: ...
  }
}

fallback

1. fallback: false

fallback 값이 false일 경우
1. getStaticPaths가 return 한 경로의 페이지만 build 한다.
2. getStaticPaths가 반환하지 않은 모든 path에 대해서 404 페이지를 반환한다.

따라서 적은 수의 path만 프리렌더링 해야하는 경우, 새로운 페이지가 추가 될 일이 많지 않은 경우(새로운 페이지가 자주 추가 된다면 추가될 때마다 다시 빌드해줘야한다)에 사용한다.

예를 들어 pages/test/[id].js 파일에 다음과 같이 getStaticPaths함수를 작성하면

export async function getStaticPaths() {
  return {
    paths: [{ params: { id: "a" } }],
    fallback: false,
  };
}

빌드했을때 /test/a 페이지에 대해서만 Pre-rendering 한다. 그리고 build된 페이지 이외의 페이지에 대해서 접근하면 404페이지가 보여진다.

2. fallback: true

fallback 값이 true인 경우

  1. getStaticPaths가 반환한 path들은 빌드 타임에 HTML로 렌더링된다
  2. 이외의 path들에 대한 요청이 들어온 경우, 404 페이지를 반환하지 않고, 페이지에서 isFallback이 true일때 렌더링되는 화면으로 보여진다.
  3. 서버에서 getStaticPaths가 반환한 path 이외의 요청된 path에 대해서 getStaticProps 함수를 이용하여 HTML 파일과 JSON 파일을 만들어낸다
  4. 서버에서 작업이 끝나면, 요청된 path에 해당하는 JSON 파일을 받아서 새롭게 페이지를 렌더링한다. 사용자 입장에서는 fallback 페이지 → 요청한 데이터의 페이지와 같은 순서로 화면이 변하게된다.
  5. 새롭게 생성된 페이지를 기존의 빌드시 프리렌더링 된 페이지 리스트에 추가한다. 같은 path로 온 이후 요청들에 대해서는 이때 생성한 페이지를 반환하게된다.

예를 들어 pages/test/[id].js 파일에 다음과 같이 코드를 작성하면

import { useRouter } from 'next/router'
...

export async function getStaticPaths() {
  return {
    paths: [{ params: { id: "a" } }],
    fallback: true,
  };
}

export async function getStaticProps({ params }) {
  const data = getDataFromAPI(params.id);
  return {
    props: { data },
  };
}

export default function Post({ data }) {
  if (router.isFallback) {
    return <div>Loading</div>
  }
  return (
    <Layout>
      ...
    </Layout>
  );
}

최초 빌드시 빌드 파일에는 /test/a 파일에 대한 build 파일만 있지만

/test/b 페이지에 접근하면 페이지에서는 Loading 이라는 화면이 뜨고 그 동안 서버에서 getStaticProps 를 통해 b에 대한 데이터를 추가적으로 가져온 후 화면에 b에 대한 페이지를 보여주게 된다. 그리고 build 파일에 /test/b 페이지에 대한 build 파일이 추가된다.

예제를 통해 보았듯이 fallback 일때의 화면이 필수적이다. 따라서 fallback 버전의 화면을 설정하지 않으면 build 과정에서 오류가 난다.

3. fallback: 'blocking'

fallback: true 일때와 대부분 동일하지만, fallback 버전의 화면이 필요없다. 즉 서버에서 데이터를 추가적으로 가져오는 동안 빈화면이 보여진다.

404, 500 Pages

pages/404.js 파일을 만들면 404페이지를 직접 커스텀 할 수 있다.

export default function Custom404() {
  return <h1>404 - Page Not Found</h1>
}

404 에러의 경우 서버 자체는 존재하지만 서버에서 요청한 것을 찾을 수 없을 때 발생한다. 보통 HTTP에서 사용자가 요청하는 페이지나 파일을 찾을 수 없을 때 가장 많이 발생한다. 가장 자주 발생하는 원인은 페이지가 이동되거나 삭제된 경우이다.

마찬가지로 pages/500.js 파일을 만들면 500페이지를 직접 커스텀 할 수 있다.

export default function Custom500() {
  return <h1>500 - Server-side error occurred</h1>
}

500 에러의 경우 서버의 동작에서 발생하는 에러 중 더 정확한 에러 코드가 아닌 경우 발생한다. 즉 예외적인 또는 예측하지 못한 에러의 경우 500 에러로 출력된다고 생각할 수 있다. 다양한 원인이 있겠지만 발생되는 원인을 간단히 살펴보면 다음과 같을 수 있다.

  • 서버 통신의 Timeout 시간 지연 오류
  • 서버 트래픽 과부하
  • 서버 언어의 구문 에러(스크립트 문법 오류)

Link Component

1. 사용법

next.js에서 <Link> 태그를 이용하면 페이지간 이동을 할 수 있습니다.

import Link from 'next/link'

export default function FirstPost() {
  return (
    <>
      <h1>First Post</h1>
      <h2>
        <Link href="/">
          <a>Back to home</a>
        </Link>
      </h2>
    </>
  )
}

이러한 기능은 <a> 태그하고 유사한데, 굳이 <a> 를 쓰지않고 next.js에서 지원하는 <Link> 를 사용할까?

<a> 태그의 경우 페이지를 이동할 때 network 탭을 확인해보면 필요한 파일들을 전부 새로 다 받아오면서 새로고침이 된다.
<Link> 태그의 경우 페이지를 이동할 때 이미 이전 화면에서 받아온 파일들에 추가적으로 필요한 파일들만 더 받아온다. 따라서 새로고침이 되지 않고 빠르게 화면이 전환된다.

그럼 어떻게 추가적으로 필요한 파일들만 빠르게 가져오는가?

next.js는 CSR의 장점과 SSR의 장점을 모두 가지고 있다고 앞서 배웠다. 이는 build time에 페이지 코드에서 <Link> 태그로 감싸져 있는, 즉 이 페이지에서 접근할 수 있는 페이지의 파일들을 Pre-fetching 하고 최초 페이지 최초 접속 후에는 클라이언트 측에서 화면을 바로 전환하기 때문에 추가적으로 필요한 데이터만 서버로부터 받아와 빠른 페이지 전환을 할 수 있는 것이다.

이처럼 <Link> 태그는 Client-side Navigaion이 가능하게 한다. 이를 확인해 볼 수 있는 방식이 있는데, 아래처럼 화면에 배경색을 클라이언트에서 직접 바꾼 후 <Link> 를 이용하여 페이지를 전환하면 배경색이 그대로 유지가 되는 것을 확인할 수 있다. 만약 <a> 태그처럼 이동하는 페이지의 모든 데이터를 새로 받아와 화면을 보여주는 경우라면 배경색은 유지가 되지 않을 것이다.

3. className

Link 태그를 커스텀하기 위해 다음과 같이 className을 주게 되면 적용이 되지 않을 것이다.

<Link href='/' className='styles.link'> home </Link>

className을 주기 위해서는 내부의 <a> 태그를 넣은 후 여기에 className을 줘야한다.

<Link href='/'>
	<a className='styles.home'> home </a> 
</Link>

4. URL masking

push 함수처럼 URL masking 기능을 사용할 수 있는데, 방법은 <Link> 태그 as 속성에 보여지고자 하는 url을 전달하면 된다.

import Link from "next/link";

export default function FirstPost() {
  return (
    <>
      <h1>First Post</h1>
      <h2>
        <Link href="/" as="/about">
          <a>Back to home</a>
        </Link>
      </h2>
    </>
  );
}

Push

Link 태그와 달리 코드 실행 중에 url 을 변경하고 싶을 때 push 함수를 사용하는데, push 함수는 useRouter 를 import하여 사용할 수 있다.

import { useRouter } from 'next/router'

1. 사용법

기본적으로 push 함수를 사용하는 방법은 단순히 인자로 변경하고자 하는 경로를 전달하면 된다.

import { useRouter } from "next/router";

export default function Home() {
  const { push } = useRouter();

  const onClick = () => {
    push("/about");
  };
  return (
    <div>
      <div onClick={onClick}>go to about</div>
    </div>
  );
}

query 를 함께 넘겨 주고 싶을 때는 객체에 pathname, query 를 함께 적어주면 된다.

import { useRouter } from "next/router";

export default function Home() {
  const { push } = useRouter();

  const onClick = () => {
    push({
      pathname: "/about",
      query: {
        id: 10,
      },
    });
  };
  return (
    <div>
      <div onClick={onClick}>go to about</div>
    </div>
  );
}

2. URL masking

push 함수의 두번째 인자로 보여지고자 하는 url을 전달할 수 있는데, 이는 쉽게 말해 push 함수 실행 후 브라우저에서 보이는 url은 첫번째 인자로 넘긴 url 혹은 pathname 대신 두번째 인자로 넘긴 url이 보여지게 되는 것이다. 이러한 URL masking 은 사용자에게 query 혹은 url 을 숨기고 싶을때 사용할 수 있다.

실제로 라우팅되는 경로는 첫번째 인자로 넘긴 경로이며 query를 전달했으면 바뀐 페이지에서 query 또한 그대로 사용할 수 있다.

import { useRouter } from "next/router";

export default function Home() {
  const { push } = useRouter();

  const onClick = () => {
    push(
      {
        pathname: "/about",
        query: {
          id: 10,
        },
      },
      "/hide"
    );
  };
  return (
    <div>
      <div onClick={onClick}>go to about</div>
    </div>
  );
}

3. Shallow Routing

Shallow Routing을 사용하면 getServerSideProps, getStaticProps를 다시 실행하지 않고도 url을 변경할 수 있다. 즉 url은 바뀌지 않고 query값만 바뀌게 할 수 있다. push 함수의 세번째 인자로 넘기는 객체의 속성으로 shallow : true 를 지정해주면 Shallow Routing을 사용할 수 있다. 이는 tab을 눌렀을 때 이동하게 하는 로직에 사용될 수 있을 것 같다.

import { useEffect } from 'react'
import { useRouter } from 'next/router'

// Current URL is '/'
function Page() {
  const router = useRouter()

  useEffect(() => {
    // Always do navigations after the first render
    router.push('/?counter=10', undefined, { shallow: true })
  }, [])

  useEffect(() => {
    // The counter changed!
  }, [router.query.counter])
}

export default Page

References

  1. https://nextjs.org/docs/routing/introduction
  2. https://velog.io/@mskwon/next-js-static-generation-fallback

0개의 댓글