Next.js data Fetching

BBAKJUN·2022년 6월 14일
1

React

목록 보기
7/7
post-thumbnail

왜 쓰러왔는가?

회사 인트라넷 백오피스에서 Next.js 9버전을 사용하고있는데 이제는 버전업을 해야할거같다.
회사 기술스텍을 말하자면

  • next.js 9 version
  • apollo client 2 version
  • mobx + context API
  • class component

음 조금 레거시한 버전이긴해서 오늘 회의 내용중 하나였던 버전업 이야기가 나왔었고
최우선으로 서버 data fetching 부분부터 버전업을 위해
getInitalProps 를 getServersideProps로 바꾸는 작업을 해야할거같다.

여담으로 현생이 바쁘다.

  • 회사에선 인트라넷 백오피스 개발
  • 집에선 하트링 개발하면서
    (mobx + context Api 사용하는법) + 프론트에서 객체지향개발을 공부중
  • 오랜만에 노마드코더로 React Native로 네이티브 앱 개발도 공부중이다.
  • 요즘 퇴근하고 롤하는 맛이 참 좋다

본론

버전업 작업 및 리팩토링에 들어가기 앞서

  • server-side-rendering이 무엇인지?
  • static-generation이 무엇인지?
  • getInitialProps 를 언제 왜 사용하는지?
  • getServersideProps 를 언제 왜 사용하는지?
  • getStaticPaths 를 언제 왜 사용하는지?
  • getStaticProps 를 언제 왜 사용하는지?

여섯가지를 자세하게 다루어볼것이다.

server-side-rendering

다들 서버사이드렌더링 혹은 SSR로 많이 부른다.
그리고 면접 단골질문이긴하다.
난 암기식 공부가 싫어서 외우지 않으려했지만,, 뭐 내가 싫다고해서 안할수는 없어서
그당시 공부하며 작성했던 글이 있을텐데,,, 찾아보니 없다 ㅋㅋㅋㅋㅋㅋㅋㅋㅋ

그러니 여기서 잠깐 다루고 가보겠음

주소창에 url을 입력했을때

1. DNS 조회하여 IP 정보, 포트 정보를 가져온다.
2. 웹 브라우저가 HTTP 요청 메세지를 생성
3. socket 라이브러리를 통해 전달 -> tcp/ip 연결 및 데이터 전달
4. TCP/IP 패킷 생성, HTTP 메세지 포함
5. 요청 패킷을 서버에 전달
6. 서버에서는 HTTP 메시지를 해석해서 HTTP 응답 메시지를 클라이언트에게 전달
7. 클라이언트에서는 받은 응답 메시지로 브라우저 렌더링을 함

여기서 SSRCSR의 차이점이 발생하게 된다.

SSR - CSR 차이점

Server Side Rendering
서버로부터 완전하게 만들어진 html 파일을 받아와 페이지 전체를 렌더링 하는방식이다

장점

  • SEO 및 검색엔진 최적화에 유리
  • 빠른초기로딩 : 사용자가 기다리는 시간이 적다

단점

  • TTV, TTI의 간의 시간 간격, 빈 껍대기의 웹페이지가 존재하는 시간이있다.
  • 요청마다 새로고침(깜빡임 생김)
  • 새로운요청이 생길때마다 바뀌지 않아도 되는부분이 재렌더링되는 이슈가있다.

Client Side Rendering
사용자의 요청에 따라 필요한 부분만 응답 받아 렌더링하는 방식이다

장점

  • 초기화면 렌더링제외하고 빠른 렌더링속도
  • 서버부하 감소(필요부분만 요청 및 응답하기때문)
  • 모든 js 파일을 초기 렌더링시 가져오기에 깜빡임현상이 적다

단점

  • SEO 및 검색엔진 최적화에 불리
  • 초기로딩 느림 : 모든 js 파일을 받아와야하기때문

그렇다면 next 에서 ssr은?
src/pages 디렉토리에 있는 컴포넌트에서 ssr을 사용하면된다.

src/pages/user/[user_id]라는 페이지가 있고 마이페이지 컴포넌트라서 user데이터가 필요할것이다.
그리고 getServerSideProps를 사용한다고 가정하자.

export default function MyPage({ user }) {
  // Render data...
}

export async function getServerSideProps({ params : { user_id } }) {
  const res = await fetch(`https://.../data`, data: { id : user_id })
  const user = await res.json()

  return { props: { user } }
}

user 데이터는 MyPage에서 자주 업데이트 되는 데이터이기 때문에 미리 렌더링해야한다 생각하여 서버측 데이터 페칭을 진행하여 Mypage 에게 props로 전달해주는 것과 같이 작성할수있다.

static-Site-Generation

그렇다면 요놈은 언제 작성해야할까?

페이지를 한번 만들고 CDN에서 제공할수있으므로 모든 요청에 대해 서버가 페이지를 렌더링하도록 하는것보다 훨씬 빠르기땜에 가능하다면(??) 사용하는게 좋다.

  • 마케팅 페이지
  • 블로그 게시물 및 포트폴리오
  • 전자상거래 제품 목록
  • 도움말 및 문서

하지만 우선 이페이지를 미리 렌더링이 가능한지?
에 대하여 본인에게 질문을 던져보아야한다.

미리 렌더링이 불가능한 경우
페이지가 자주 업데이트 되어 데이터가 표시되고 모든 요청에 다라 페이지 컨텐츠가 변경될수있다.

이는 유저에게 불친절한? 경험을 준다... (잦은 깜빡임)

static generation 을 사용할거면
필요한경우 getStaticPaths or getStaticProps 를 내보내면 된다.

이부분이 가장 감명 깊다. 유저에게 불친절한 경험을 준다라...
면접보러 다닐땐 유저친화적인 개발자가 된다해놓고 지킨적이 있었나 라고 생각해본 문구였다

getInitialProps

export default function MyApp({count}) {
	return <div>count : {count}</div>
}

MyApp.getInitialProps = async (ctx) => {
  const res = await fetch('https://api.github.com/repos/vercel/next.js')
  const { stargazers_count } = await res.json()
  return { count: stargazers_count }
}

getInitialProps는 페이지에서 server-side-rendering을 활성화하고 초기 데이터 채우기를 실행할 수 있다. 이는 서버에서 데이터 페칭을 하여 페이지로 내보내기 때문에 SEO에 유용하다.

해당 메서드는 초기 페이지 로드시에만 서버에서 실행된다.
next/link next/router 등 클라이언트 내부에서 라우팅 요청에서는 클라이언트 측에서 실행된다.

context object

  • pathname : /pages 디렉토리 밑의 경로
  • query : url의 쿼리스트링 부분을 객체로 parsing
  • aspath : 브라우저에 표시된 실제 경로(query 포함)
  • req : Http 요청 객체
  • res : Http 응답 객체
  • err : 렌더링 중 오류가 발생한경우 오류 객체

getServersideProps

이놈은 9.3버전부터 생김
그렇다고 getInitialProps 와 크게다른가를 보면 크게 달라보이지는 않는다.

getServersideProps 이놈은
props, notFounds, redirect의 3가지 리턴값을 가진다

서버측과 대화하는 페이지라고 생각해보자

  • 서버에서는 message를 뱉어낸다
  • 할말이없으면 아무런 message도 뱉지 않는다.
import type { GetServerSideProps } from "next";

export default function Conversation({ message }) {
  return (
    <div>{message}</div>
  )
}

export const getServerSideProps: GetServerSideProps = async (context) {
  const res = await fetch(`https://.../data`)
  const message = await res.json()

  if (!message) {
    return {
      notFound: true,
    }
  }

  return {
    props: { message }, 
  }
}

위의 코드는 서버에서 백엔드에게 요청을 보내
message가 없다면 notFoundtrue를 리턴하고
message가 존재하면 message를 리턴한다.

notFound를 true로 반환된다면 404 에러 페이지로 리다이렉팅 될것이다.


redirect는 다른 예시로 알아보자

우리 회사 인트라넷을 예시로 들어보겠다.
pages/** 밑에 list와 show 페이지가 있다.

list 는 말그래도 모든 데이터를 보여주는 getDatas의 느낌이고
show 는 디테일 페이지다. getData의 느낌이라고 보면 될것이다.

리스트 페이지에서 데이터가 없다면 빈 데이터를 보여주면 되지만
디테일 페이지에서 데이터가 없다면??? 리스트 페이지로 리다이렉팅 해주는 방법이 좋을거같다.
(실제로 오늘 회의에서도 나온 내용이기도함)

import type { GetServerSideProps } from "next";

export async function getServerSideProps:GetServerSideProps(context) {
  const res = await fetch(`https://.../data`)
  const data = await res.json()

  if (!data) {
    return {
      redirect: {
        destination: '/list',
        permanent: false,
      },
    }
  }

  return {
    props: {}, // will be passed to the page component as props
  }
}

위의 코드처럼 사용하면 되는데

redirect : { destination : string , permanent : boolean }
만약 리다이렉트시 status code를 변경해야할 필요가 있는경우에는
premanent 대신 statusCode 라는 키를 사용하면 된다.

context object

  • params: 이 페이지가 동적 경로params 를 사용하는 경우 경로 매개변수 를 포함합니다
  • req: Http 요청 객체
  • res: Http 응답 객체
  • query: 쿼리 스트링 데이터가 담겨있다.
  • preview: Preview 모드 사용 유무 (boolean)
  • previewData: Preview 모드 사용시 전달된 데이터
  • resolvedUrl: Request 된 URL 의 좀 더 일반화된 (간소화된?) 버전의 url
  • locale: 현재 locale 정보
  • locales: 지원되는 모든 locale 정보
  • defaultLocale: 기본 locale 정보

getStaticProps & getStaticPaths

두 API들은 같이 쓰이는 경우가 많을거같아서 함께 설명하겠습니다.

html이 빌드타임에 생성되고,
빌드할때 데이터를 가져와서 html을 생성후
사용자의 요청이 들어올때마다 빌드된 html을 재사용한다.

import type { GetStaticProps, InferGetStaticPropsType } from "next";

type Data = {
  author : string
  content: string
}

const Page = ({ data } : InferGetStaticPropsType<typeof getStaticProps>) => {
  ...
}

export const getStaticProps: GetStaticProps = async () => {
  const { data }: Data[] = await axios.get(`${END_POINT}/post`)

  return { props: { data } }
}

export default Page

아무래도 요청시 미리 만들어놓은 파일을 보여주기때문에 성능적으로 속도가 빠르긴하다.
하지만? 데이터가 매번 바뀌어야할 페이지라면 이 방법은 불리한 방법이 될수도있다.

데이터가 바뀌지않는

  • 상품 리스트 페이지
  • 마이페이지
  • 블로그 게시물
  • 포트폴리오
  • ...

위와 같은 정적데이터가 들어갈 페이지에 적합하다.
CRUD가 가능한 페이지보다는 뽞뽞!! 데이터가 꽂혀있을만한 사이트에 적합!

Dynamic Routes를 사용하여 SSG를 만들경우에
getStaticProps를 사용한다면 getSaticPaths를 함께 써줘야한다.

import type { GetStaticProps, InferGetStaticPropsType, GetStaticPaths } from "next";

type Data = {
  author : string
  content: string
}

const Page = ({ data } : InferGetStaticPropsType<typeof getStaticProps>) => {
  ...
}
  
export const getStaticPaths: GetStaticPaths = async () => {
  const posts = await axios.get(`${END_POINT}/post`)
  const paths = posts.map(({ id }) => ({ params: { id: `${id}` } }));


  return {
    paths,
    fallback: false,
  };
}

export const getStaticProps: GetStaticProps = async () => {
  const { data } = await axios.get(`${END_POINT}/post`)

  return { props: { data } }
}

export default Page

getStaticPaths에서 parms에 빌드하고픈 페이지를 넣으면된다.

user/[id].tsxid값이 1이라고 가정할때

params : {
	id : 1
}

로 들어가야한다.

즉 빌드할 페이지들을 모두 서버에서 가져와서 정적문서를 뽑아놓겠단 것

만약 미리 빌드한 페이지말고 다른 페이지가 필요할것같을때는 fallback을 params로 넣어두었다.

유저리스트에서 유저 정보가 id50 까지밖에 없다고 가정해보자
id 값이 51번인 페이지를 요청시

미리 만들어두었던 유저디테일 페이지가 아닌 그런 유저가 없다는 페이지를 보여줘야하는데
fallback 이 false라면 빈껍데기의 유저디테일 페이지를 보여줄것이다.

이를 방지하여 fallback 을 true로 리턴하여 조건부 렌더링 처리를 하면된다.

아래 코드와 같이 말이다!

import type { GetStaticProps, InferGetStaticPropsType, GetStaticPaths } from "next";

type Data = {
  author : string
  content: string
}

const Page = ({ data } : InferGetStaticPropsType<typeof getStaticProps>) => {
  const router = useRouter();

  if(router.isFallback) return <div>그런 유저 없습니다...</div>
  else return <UserDetailPage {...props} />
}
  
export const getStaticPaths: GetStaticPaths = async () => {
  const posts = await axios.get(`${END_POINT}/post`)
  const paths = posts.map(({ id }) => ({ params: { id: `${id}` } }));

  return {
    paths,
    fallback: true,
  };
}

export const getStaticProps: GetStaticProps = async () => {
  const { data } = await axios.get(`${END_POINT}/user`)

  return { props: { data } }
}

export default Page

끝맺음

사실 지금까지 getServerSideProps만 사용하여 ssr을 사용해왔었는데,

렌더링 최적화를 위해서는 next에서 제공하는 data fetching API 를 적절히 사용해야하는 것을 이 글을 작성하며 알게되었다.

암튼 next.js 고마워요

profile
함께 일하고 싶은 환경을 만들어가는 프론트엔드 개발자 박준형입니다. 블로그 이전 [https://dev-bbak.site/]

2개의 댓글

comment-user-thumbnail
2022년 6월 15일

글이 좋네요.퍼가요~~ ^^

답글 달기
comment-user-thumbnail
2022년 11월 15일

제대로 배워갑니다 👍

답글 달기