Front-End/Next

Next.js 프레임워크 라우팅 구조와 데이터 패칭

Voyage_dev 2024. 8. 4. 16:51
✔️ Next.js 프레임워크의 라우팅 구조와 데이터 패칭에 대해 살펴보자

 

페이지 라우트 구조

페이지 라우터 구조는 pages라는 디렉토리 하위의 경로에 따라서 라우터 구조를 자동으로 가져가고 있다. Next.js를 템플릿을 사용해서 프로젝트를 시작을 하면 pages라는 디렉토리가 기본으로 만들어지게 된다. 해당 디렉토리 하위에 폴더도 만들 수 있고 파일도 만들 수 있다.

  • /pages/index.tsx : 웹 사이트의 루트(localhost:3000)
  • /pages/hello.tsx : 경로는 /hello 로 접근할 수 있다(localhost:3000/hello)
  • /pages/hello/[id].tsx : 대괄호 [] 는 여기에 어떠한 문자도 올 수 있음을 의미한다
    • /pages/hello/123
    • /pages/hello/world 등 가능
    • 서버사이드에서는 123, world가 id 변수에 담겨져서 들어온다
  • /pages/hello/[…props].tsx : /hello 하위의 주소가 모두 가능하다
    • /pages/hello/world
    • /pages/hello/world/123
    • /pages/hello/world/123/456
    • 서버사이드에는 props 변수에 해당 경로가 담겨져서 들어온다
// /pages/hello/[…props].tsx

import { NextPageContext } from "next";
import { useRouter } from "next/router";
import { useEffect } from "react";

export default function HelloAll({props: serverProps}: {props: string[]}) {
    const {
        query: {props},
    } = useRouter();

    useEffect(() => {
        console.log('props', props);
        console.log(JSON.stringify(props) === JSON.stringify(serverProps))
    }, [props, serverProps]);

    return (
        <>
            <p>Hello</p>
            <ul>
                {
                    serverProps.map((item) => (
                        <li key={item}>{item}</li>
                    ))
                }
            </ul>
        </>
    )
}

export const getServerSideProps = (context: NextPageContext) => {
    const {
        query: {props},
    } = context;

    return {
        props: {
            props
        }
    }
}

 

 

서버 라우팅과 클라이언트 라우팅

Next.js는 서버 사이드 렌더링을 수행하면서 동시에 클라이언트 사이드 렌더링도 수행한다. 우리가 routing 방식을 사용할 때 크게 두 가지 방식을 생각할 수 있다.

 

<a>와 <Link> 태그 방식의 차이점은 무엇일까?

  • <a> 태그는 어플리케이션에 필요한 모든 리소스(webpack, framework, main 등)를 다 가져온다
    • 서버에서 렌더링도 수행하고, 클라이언트에서 hydrate도 진행한다. (콘솔이 server, client 모두 찍힌 것을 보면 알 수 있다)
  • <Link> 태그는 서버 사이드 렌더링은 하지 않고, 클라이언트 자바스크립트만 불러와서 클라이언트 라우팅, 렌더링 방식으로 진행한다 → 더 사용을 권장한다
// pages/index.tsx

import { NextPage } from "next";
import Link from "next/link";

const Home: NextPage = () => {
  return (
    <ul>
      <li>
        <a href="/routing">a 태그로 이동</a>
      </li>
      <li>
        <Link prefetch={false} href="/routing">
          next/link로 이동
        </Link>
      </li>
    </ul>
  );
};

export default Home;

// pages/routing/index.tsx

export default function Routing() {
  console.log(typeof window === "undefined" ? "server" : "client");

  return <p>라우팅</p>;
}

export const getServerSideProps = () => {
  return {
    props: {},
  };
};

Next.js는 서버 사이드 렌더링의 장점(빠른 최초 페이지 제공)과 SPA의 장점(자연스러운 라우팅) 같은 장점을 각각 가지고 만들어진 프레임워크이다. 이와 비슷한 이유로 window.location.push 보다 router.push의 사용을 더 권장한다.

 

/api

pages/api 디렉토리에는 서버의 API를 정의하는 폴더가 있다. 우리가 pages에서 어떤 폴더를 만들면 그 폴더가 라우터 구조가 된다고 살펴보았는데 이 API라는 이름은 네이밍 스페이스 규칙을 가지고 있어서 이 /api 하위 파일은 HTML을 요청하는게 아니라 단순 서버 요청을 주고받는 역할을 한다. 즉, 다른 pages 하위 파일들은 HTML을 요청하지만 /api 하위 파일들은 단순한 서버 요청을 주고 받는다.

서버 데이터를 가지고 BFF(Backend For Frontend)를 만들거나, CORS(Cross-Origin Resource Sharing) 문제를 해결하기 위해 사용할 수 있다.

  • 이렇게 HTML을 받아오는게 아니라 JSON 데이터를 불러오는걸 확인할 수가 있다

데이터 패칭

Next.js에서는 SSR 지원을 위한 몇 가지 데이터 불러오기 전략이 있다.

이 함수들은 pages 하위 디렉토리에서만 사용할 수 있다.

  • getStaticPaths : 접근 가능한 주소를 정의하는 함수
    • 무엇? - Next.js가 해당 경로를 전략적으로 pre-rendering 하기 위해서 사용
    • 언제?
      • 데이터가 headless CMS, DB, 파일 시스템, 퍼블릭 캐시 등에서 올 때
      • 페이지가 미리 렌더되어서 빠르게 실행이 되어야 할 때
    • 실행? - 프로덕션 빌드 시에만 함수가 실행이 되고, 런타임에서는 실행이 되지 않는다
    • 어디서?
      • getStaticProps와 같이 사용해야 한다
      • getServerSideProps와 함께 사용할 수 없다
export const getStaticPaths = (async () => {
  return {
    paths: [
      {
        params: {
          name: 'next.js',
        },
      }, 
    ],
    fallback: true, 
  }
}) satisfies GetStaticPaths

// return에 paths와 fallback props가 들어가게 된다
  • getStaticProps : 앞에서 정의한 페이지(by getStaticPaths)를 기준으로 해당 페이지로 요청이 왔을 때 제공할 props를 반환하는 함수
    • 무엇? - 미리 호출된 페이지를 빌드 타임에 pre-render 하고 이 때 getStaticProps의 props 리턴값을 가지고 렌더링한다
    • 언제?
      • 유저의 요청 전 빌드 시 페이지를 렌더링하는데 필요한 데이터가 주어졌을 때
      • 데이터가 headless CMS에서 올 때
    • 실행? - 서버에서 실행, 클라이언트에서는 절대 실행되지 않는다
      • next build 시에 항상 실행
      • 옵션 fallback : true → 백그라운드에서 실행된다
      • 옵션 fallback : blocking→ 초기 렌더 전에 호출된다
      • revalidate 메서드를 통해 백그라운드에서 실행 가능하다
    • 어디서?
      • 오직 page 디렉토리에서 export 가능하다
      • _app, _document, _error 등 페이지에서는 export 불가능하다
import type { InferGetStaticPropsType, GetStaticProps } from 'next'
 
type Repo = {
  name: string
  stargazers_count: number
}
 
export const getStaticProps = (async (context) => {
  const res = await fetch('<https://api.github.com/repos/vercel/next.js>')
  const repo = await res.json()
  return { props: { repo } }
}) satisfies GetStaticProps<{
  repo: Repo
}>
 
export default function Page({
  repo,
}: InferGetStaticPropsType) {
  return repo.stargazers_count
}

유저가 어떤 값들을 요청하기 전에 이미 특정 경로를 지정하고 그 경로에 렌더링을 해야하는데 그때 필요한 데이터들을 불러올 때 사용

  • getServerSideProps : 위의 두 함수는 정적 페이지 제공을 위해 사용했다면, getServerSideProps는 서버에서 실행되고, 페이지 진입 전 실행된다.
    • 무엇? - 페이지를 요청하는 타임에 데이터를 요청하고 렌더링을 진행한다
    • 언제?
      • 유저의 개인화된 데이터에 의존하는 페이지이거나 요청 시 데이터를 받아서 만들어야 하는 페이지는 getServerSideProps로 렌더링을 해야 한다.
import type { InferGetServerSidePropsType, GetServerSideProps } from 'next'
 
type Repo = {
  name: string
  stargazers_count: number
}
 
export const getServerSideProps = (async () => {

  const res = await fetch('https://api.github.com/repos/vercel/next.js')
  const repo: Repo = await res.json()

  return { props: { repo } }
}) satisfies GetServerSideProps<{ repo: Repo }>
 
export default function Page({
  repo,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
  return (
    <main>
      <p>{repo.stargazers_count}</p>
    </main>
  )
}

 

 

getStaticProps와 getServerSideProps는 동시에 같이 사용할 수가 없다. 예를 들어, 블로그 같은 글을 만들 때는 어떤 유저가 들어오더라도 같은 결과물을 보여줘야 하기 때문에 getStaticProps와 getStaticPaths로 미리 프리렌더링을 해놓은 페이지를 보여주는 것이 조금 더 빠를 수도 있다. 반대로 어떤 대시보드를 만들고 거기에서 유저마다 개인화된 데이터를 보여줘야 되는 서비스를 만들 때는 getServerSideProps를 사용하는 편이 훨씬 더 적합할 것으로 보여진다. 이렇게 목적에 맞게 이러한 함수들을 데이터를 패칭을 하는 함수들을 사용하면 서버 사이드 렌더링에서 가져갈 수 있는 장점들을 잘 가져갈 수 있을것이다.

 

Next는 12버전까지는 페이지 라우팅 방식을 모두 기본 설정으로 사용을 했는데 13버전부터 앱 라우터 방식이 새롭게 등장하게 되었다. 다음에는 앱 라우터 방식과 같이 사용할 수 있는 리액트 서버 컴포넌트에 대해서 알아보자!