본문 바로가기
React

[Next.js] Next.js 시작하기

by 맨날개발 2023. 4. 18.
Next.js 리액트 프레임워크이기 때문에 기본적인 리액트의 사용방법을 숙지하고 있어야 합니다. 아래 내용은 Next.js 공식문서를 참조하였으며 리액트로만 앱을 작성했을때와의 차이점 위주로 소개하겠습니다.

 

Next.js 시작하기

리액트와 마찬가지로 Next.js에서도 쉽게 앱을 생성할 수 있도록 create-next-app 을 제공하고 있습니다. 터미널에 아래의 명령어를 실행하면 몇가지 옵션을 선택한 후에 설치를 진행할 수 있습니다.

13버전부터는 설치시 선택가능한 옵션을 제공하고 있으며, src/app 옵션을 사용하는 경우 아래의 설명과 달라질 수 있습니다. (버전 : Next.js 13.3)
npx create-next-app@latest start-nextjs

 

설치시 아래와 같이 디렉토리와 파일구조가 생성되는 것을 확인할 수 있습니다.

src
├─pages
│  │  index.js
│  │  _app.js
│  │  _document.js
│  │
│  └─api
│          hello.js
│
└─styles
        globals.css
        Home.module.css

 

페이지 라우트

Next.js에서는 리액트와 달리 라우트 설정을 달리 해줄 필요가 없습니다. pages 디렉토리 내부에 존재하는 .js, .jsx, .ts, .tsx 파일을 읽어들여 자동으로 라우트를 설정해줍니다. 한마디로 복잡한 설정 없이 pages 디렉토리 내부에 존재하는 파일이름으로 경로를 설정합니다.

예를 들어 pages/about.jsx 파일이 존재 하는 경우 /about 경로로 접속하는 경우 about.jsx 파일로 렌더링 됩니다.

 

다이나믹 라우트

nextjs는 또한 다이나믹 라우트를 지원합니다. pages/post/[pid].jsx 와 같은 이름으로 파일을 생성하는 경우 /post/1 또는 post/2 등과 같은 경로로 접속했을 때 [pid].jsx 파일로 렌더링 됩니다.

id값은 아래와 같이 router의 query 객체에 포함되어 있습니다.

import { useRouter } from 'next/router'

const Post = () => {
	const router = useRouter()
  const { pid } = router.query;

	return <div>{pid}</div>
}

export default Post;

 

다이나믹 라우트 경로가 2개 이상인 경우 디렉토리 또한 [경로] 를 통해서 사용할 수 있습니다. 예를 들어 /user/유저아이디/post/게시글아이디 와 같이 경로를 설정해야 한다면 pages/[uid]/post/[pid].jsx 경로로 파일을 구성하면 됩니다.

pages
└─user
  └─[uid]
    └─post
			└─[pid].jsx
import { useRouter } from "next/router"

const Post = () => {
  const router = useRouter(); 
  const { uid, pid } = router.query;

  return (
    <>
      <p>유저 아이디 : {uid}</p>
      <p>게시글 아이디 : {pid}</p>
    </>
  )
}

export default Post;

 

API

Next.js에서는 API 라우트를 지원하고 있습니다. 이를 통해 Next.js에서 필요한 serverless API를 구축할 수 있습니다.

엔드포인트를 구축하기 위해서는 pages/api 디렉토리 내부에 파일을 생성하면 됩니다. 페이지 라우트랑 동일하게 API 라우트 또한 디렉토리와 파일의 이름을 이용해서 경로를 자동으로 설정해줍니다.

예를 들어 pages/api/posts.js 파일을 생성했다면 /api/posts 의 주소로 API 요청시 posts.js에서 요청을 처리할 수 있습니다.

모든 게시글을 조회하는 예시를 통해 간단하게 사용방법을 알아보겠습니다. pages/api/posts.js 파일을 아래와 같이 구성하면 됩니다. 핸들러의 첫번째 인수로는 래핑된 request 객체, 두번째 인수로는 래핑된 response 객체를 전달해줍니다.

import { NextApiRequest, NextApiResponse } from "next";

interface Post {
	id: number;
	title: string;
	content: string;
}

const posts = [
	{ id: 1, title: 'title1', content: 'content1' },
	{ id: 2, title: 'title2', content: 'content2' },
	{ id: 3, title: 'title3', content: 'content3' },
]

const handler = async (req: NextRequest, res: NextApiResponse<Post[]>) => {
  res.status(200).json(posts);
}

export default handler;
Node에서 제공해주는 request, response를 그래도 전달하는 것이 아닌 사용하기 쉽도록 헬퍼 메서드가 포함된 request, response객체를 제공합니다.

 

해당 파일에서 모든 method에 대한 요청을 받기 때문에 method별로 다른 처리를 해야하는 경우 아래와 같이 처리할 수 있습니다.

import { NextApiRequest, NextApiResponse } from "next";

const handler = async (req: NextApiRequest, res: NextApiResponse<Post[]>) => {
	switch (req.method) {
		case 'GET':
			break;
		case 'POST':
			break;
		default:
			throw Error();
	}
}

export default handler;

 

아래와 같은 경로를 통해서 데이터를 가져올 수 있습니다.

fetch('http://localhost:3000/api/posts').then((response) => response.json())

 

 

다이나믹 API

페이지 라우트와 마찬가지로 API에서도 다이나믹 API를 지원합니다. api/posts/[pid].js 의 경로에 파일을 생성하는 경우 http://localhost:3000/api/posts/1 , http://localhost:3000/api/posts/2 와 같이 API를 요청하는 경우 [pid].js 파일에서 요청을 받을 수 있습니다.

 

API 핸들러 내부에서 동적 값은 req.query 객체에 담겨있습니다. 현재 파일 이름이 [pid].js 이기 떄문에 아래와 같이 사용할 수 있습니다. 그리고 queryString으로 전달 된 값도 req.query에서 동일하게 가져올 수 있기 때문에 주의해야 합니다. http://localhost:3000/api/posts/1?uid=hello 의 경로로 요청시 아래와 같이 사용할 수 있습니다.

const handler = (req: NextApiRequest) => {
	const { pid, uid } = req.query;
	console.log(pid, uid); // 1 hello
}

 

 

Data Fetching

SSR

기본적으로 nextjs 페이지는 SSR로 동작합니다. 즉, 서버에서 리액트로 작성한 UI를 모두 렌더링 한 뒤 프론트로 넘겨줍니다.

개발자 모드의 네트워크 탭을 확인해보면 Response에 html 렌더링 결과를 보내주는 것을 확인할 수 있습니다. 그에 반해 리택트 앱은 <div id="root"></div> 만 렌더링하여 보내주고 있습니다.

 

 

getServerSideProps

Next.js 에서는 페이지 컴포넌트에서 getServerSideProps 함수를 export 하는 경우 getServerSideProps 함수에서 반환된 데이터를 토대로 해당 페이지를 프리 렌더링합니다.

기본 형태는 아래와 같으며 props 객체 내부에 프로퍼티를 추가하는 경우 컴포넌트의 props에 전달됩니다.

export const getServerSideProps: GetServerSideProps = async (context) => {
  return {
    props: {}
  }
}

 

getServerSideProps 를 통해서 컴포넌트에 name 속성을 전달하는 방법은 아래와 같습니다.

export const getServerSideProps: GetServerSideProps<{ name: string }> = async (context) => {
  return {
    props: {
      name: 'hello'
    }
  }
}

const Test: NextPage<InferGetServerSidePropsType<typeof getServerSideProps>> = (props) => {
	return (
		<div>{ props.name }</div>
	)
};
 
export default Test;
getServerSideProps의 반환 값은 Promise 이어야 하기 떄문에 async를 붙여줍니다.

 

getServerSideProps 는 해당 페이지 요청시 매번 SSR로 호출 되기 때문에 화면을 그리기 시작하기 까지 시간이 오래 걸립니다. SEO를 활용해야 하는 경우가 아니라면 CSR을 사용하는게 더 나은 선택일 수 있습니다.

 

 

getStaticProps, getStaticPaths

getStaticProps 를 페이지에서 export 하는 경우 빌드단계에서 프리 렌더링 해줍니다. 구조는 getServerSideProps 와 유사합니다.

export const getStaticProps: GetStaticProps = async (context) => {
  return {
    props: {}
  }
}

 

getStaticProps 를 통해서 컴포넌트에 name 속성을 전달하는 방법은 아래와 같습니다.

export const getStaticProps: GetStaticProps<{ name: string }> = () => {
  return {
    props: {
      name: 'hello'
    }
  }
};

const Static: NextPage<InferGetStaticPropsType<typeof getStaticProps>> = (props) => {
  return (
    <>
      <div>{ props.name }</div>
      <div>hi</div>
    </>
  );
};

export default Static;
getStaticProps는 빌드 단계에서 생성되는 파일이기 때문에 데이터가 잦은 업데이트게 된다면 getStaticProps를 사용하는 것은 좋지 못합니다. - 다시 빌드 될때까지 변하지 않습니다.

 

getStaticPaths 는 단독으로 사용이 불가능하고 getStaticProps 를 사용할때 함께 사용됩니다. 추가적으로 페이지가 동적 라우트를 사용하는 경우에 사용합니다.

getStaticPaths 함수를 export 하는 경우 빌드 시 생성할 다이나믹 라우트 경로의 페이지를 리렌더링 하게 됩니다. 이때 params 으로 추가한 경로로만 페이지를 생성하게 됩니다.

export const getStaticPaths: GetStaticPaths = () => {
	return {
		paths: [],
		fallback: false // true or 'blocking'
	}
}

 

옵션

1. [fallback]

getStaticPaths 에서 설정 된 경로가 아닌 경로로 접속시에 대한 처리를 위한 옵션입니다.

  • true
    • 서버에서 static 페이지를 생성합니다.
    • 이후 접속 시 생성된 static 페이지에 접근이 가능합니다.
    • router의 isFallback 속성으로 fallback 처리를 추가할 수 있습니다.(페이지 생성 전까지 보여줄 화면)
    • 너무 많은 static 페이지로 인하여 빌드 오래 걸리는 경우 해당 방법으로 사용이 가능합니다.
const router = useRouter();
if (router.isFallback) {
	return <div>잠시만 기다려 주세요.</div>
}
  • false : 404 페이지로 이동하게 됩니다.
  • ‘blocking’
    • true 옵션과 대부분 유사하지만 router의 isFallback 속성이 false 이기 때문에 fallback에 따른 처리를 할 수 없습니다.

 

2. [paths]

static 페이지 생성시에 사용되는 옵션입니다. 배열 요소인 객체에 params를 전달하는 경우 다이나믹 라우트 경로에 전달됩니다. 페이지 명이 [uid].js 인 경우 아래와 같이 전달하면 된다. 이때 값의 타입은 문자열 이어야 합니다.

export const getStaticPaths: GetStaticPaths = () => {
	return {
		paths: [{ params: { uid: `1` } }],
		fallback: false // true or 'blocking'
	}
}
getStaticProps와 getServerSideProps 내부에서 API Route를 호출하는 것은 좋지 못하다고 합니다. 두 곳에서 공통으로 사용할 수 있는 파일을 분리하면 중복 코드를 줄일 수 있습니다.