Next.js - Pages Router타입

김재환·2024년 1월 9일

Next.js

목록 보기
1/10

Next.js에서 특별하게 사용하는 타입은 많지 않기 때문에 공식문서만 읽어보아도 충분합니다. 이번 레슨에서는 공식 문서의 내용을 기반으로 타입을 활용하는 방법에 대해서 알아보도록 하겠습니다.

커스텀 App

_app.tsx 파일에선 웹사이트 전체에 공통적으로 렌더링 되는 App이라는 컴포넌트를 만들죠? 이 컴포넌트의 Props 형태는 정해져 있는데요. AppProps라는 타입을 next/app 패키지에서 불러와서 아래처럼 사용하면 됩니다.

 _app.tsx 

import Head from 'next/head';
import { AppProps } from 'next/app';
import Header from '@/components/Header';
import '@/styles/global.css';

export default function App ({ Component, pageProps }: AppProps) {
  return (
    <>
      <Head>
        <title>Codeitmall</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <Header />
      <Component {...pageProps} />
    </>
  );
}

프리 렌더링

정적 생성
Next.js에선 빌드 시점에 리액트 코드를 미리 렌더링해 둘 수 있는데요. 이런 걸 정적 생성(Static Generation)이라고 합니다.

import Image from 'next/image';

export async function getStaticPaths () {
  const res = await fetch('https://learn.codeit.kr/api/codeitmall/products/');
  const data = await res.json();
  const products = data.results;
  const paths = products.map((product) => ({
    params: { id: String(product.id) },
  }));

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

export async function getStaticProps(context) {
  const productId = context.params ['id'];

  let product;
  try {
    const res = await fetch(`https://learn.codeit.kr/api/codeitmall/products/${productId}`);
    const data = await res.json();
    product = data;
  } catch {
    return {
      notFound: true,
    };
  }

  return {
    props: {
      product,
    },
  };
}

export default function ProductPage({ product }) {
  return (
    <div>
      <h1>{product.name}</h1>
      <Image
        src={product.imgUrl}
        width="480"
        height="480"
        alt={product.name}
      />
    </div>
  );
}

위 예시 코드를 살펴보면, getStaticPaths()라는 함수에선 https://learn.codeit.kr/api/codeitmall/products/라는 API 주소에 리퀘스트를 보내서 상품 목록 데이터를 받았습니다. 이 데이터로 아이디 값들을 모아서 params 값들을 만들고요. 이 값을 바탕으로 getStaticProps() 함수에서는 context 값을 활용해 리퀘스트를 보내서 상품 데이터를 받아옵니다. 이걸 product라는 이름의 Prop으로 내려주고 있죠.

Next.js에선 기본적으로 화살표 함수로 만든 다음 아래와 같이 GetStaticPaths, GetStaticProps 타입을 지정하는 걸 권장합니다.

context 값에 마우스를 호버해 보면 타입이 잘 추론되죠?

import { GetStaticPaths, GetStaticProps } from 'next';
// ...

export const getStaticPaths: GetStaticPaths = async () => {
  // ...

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

export const getStaticProps: GetStaticProps = async (context) => {
  // ...

  return {
    props: {
      product,
    },
  };
}

이번엔 페이지의 타입을 정의해 보겠습니다. 우선 일반적인 리액트 컴포넌트의 Props 타입을 정의하듯이 Props를 정의하고요. 이걸 getStaticProps() 함수의 제네릭으로 지정해 줍니다.

interface Props {
  product: Product;
}

export const getStaticProps: GetStaticProps<Props> = async (context) => {
  // ...
  return {
    props: {
      product,
    },
  };
};

export default function ProductPage({ product }: Props) {
  return (
    <div>
      <h1>{product.name}</h1>
      <Image
        src={product.imgUrl}
        width="480"
        height="480"
        alt={product.name}
      />
    </div>
  );
}

서버사이드 렌더링

Next.js 서버에 리퀘스트가 들어올 때마다 렌더링을 해서 보내주는 서버사이드 렌더링의 경우에도 비슷한 방식으로 해주면 됩니다. 앞에서 보았던 예시에서 서버사이드 렌더링으로 바꾼 코드를 한번 보겠습니다.


import Image from 'next/image';

export async function getServerSideProps(context) {
  const productId = context.params['id'];

  let product;
  try {
    const res = await fetch(`https://learn.codeit.kr/api/codeitmall/products/${productId}`);
    const data = await res.json();
    product = data;
  } catch {
    return {
      notFound: true,
    };
  }

  return {
    props: {
      product,
    },
  };
}

export default function ProductPage({ product }) {
  return (
    <div>
      <h1>{product.name}</h1>
      <Image
        src={product.imgUrl}
        width="480"
        height="480"
        alt={product.name}
      />
    </div>
  );
}

getServerSideProps() 함수의 타입은 화살표 함수로 만든 다음에 GetServerSideProps로 지정하면 되고요. 마찬가지로 Props 타입도 정의한 다음, 아래처럼 제네릭으로 지정하고, 페이지 컴포넌트에도 정의하면 됩니다. 이 부분은 자주 쓰일 테니까 기억해 두시면 좋습니다.


interface Props {
  product: Product;
}

export const getServerSideProps: GetServerSideProps<Props> = async (context) => {
  // ...
  return {
    props: {
      product,
    },
  };
};

export default function ProductPage({ product }: Props) {
  return (
    <div>
      <h1>{product.name}</h1>
      <Image
        src={product.imgUrl}
        width="480"
        height="480"
        alt={product.name}
      />
    </div>
  );
}

API 라우트

마지막으로 API 라우트의 타입을 살펴보면 아래와 같이 리퀘스트와, 리스폰스에 타입을 지정하면 됩니다. 어렵지 않죠?

import type { NextApiRequest, NextApiResponse } from 'next'
 
export default function handler(req: NextApiRequest, res: NextApiResponse) {
  // ...
}
profile
안녕하세요

0개의 댓글