NextJS의 정적 페이지에 대한 데이터 가져오기

김기훈·2023년 3월 24일
0

NEXTJS

목록 보기
3/3

1. 페이지 pre-rendering의 문제점

NextJS에는 내장된 페이지 pre-rendering 기능이 있는데 이 내장된 프로세스의 단점은 pre-rendering한 페이지가 컴포넌트가 첫 번째 렌더링 사이클을 마친 이후의 스냅샷을 콘텐츠로 갖고 있다는 것이다.

어떤 라우터가 있다면 요청은 라우터에 전해지고 페이지로 이동할 때 여기에서 pre-rendering한 페이지를 반환하지만 데이터는 포함 되어 있지 않은 상태일 수 있다.

이게 무슨 말인지 자세히 알아보자면, 예를 들어 데이터를 fetch해오는 함수를 useEffect안에 포함시켰다고 생각해보자.

물론 우리가 보기에 useEffect가 실행되고 데이터를 가져와 랜더링이 업데이트 되면 데이터는 HTML 안에 잘 포함되어 있는 것처럼 보인다.

하지만 여기서 우리가 주의해야 할 점은 useEffect가 컴포넌트 함수가 실행되고 난 후에 실행되는 방식으로 작동한다는 것이다.

다시 말하자면 처음 페이지의 컴포넌트가 렌더링 될 때 useEffect는 실행되지 않기 때문에 데이터를 가져오는 함수는 실행되지 않고 이는 데이터를 가져오지 않은 상태의 컴포넌트가 한번 랜더링 된다는 것이다.

그 이후에 useEffect 함수가 실행되고 상태를 업데이트한 후에 컴포넌트 함수가 다시 실행될 것이다.

이제 실제 데이터를 가지고 목록을 다시 렌더링하게 된다. 이렇게 컴포넌트 렌더링이 두 번 일어나는 것이다.

물론 React는 useEffect 함수를 실행할 것이고 데이터를 받아와서 페이지를 업데이트할 것이다.

하지만 초기에 반환된 HTML 코드에는 데이터가 포함되어 있지 않은 상태일 것이다.

하지만 NextJS가 자동으로 생성하는 pre-rendering 된 페이지는 이 두 번째 사이클을 기다리지 않고 첫 번째 렌더링 사이클의 결과를 가져와서 pre-rendering한 HTML 코드를 반환한다.

첫 번째 렌더링 사이클의 결과 HTML 코드는 데이터가 비어있게 되고 이는 웹 크롤러가 페이지에 들어왔을 때 아무런 데이터도 검색하지 않는 결과를 낳게 되고 이는 SEO에 문제가 생기게 된다.

2. 해결책

다행히 NextJS는 이 문제의 해결책도 갖고 있는데 정적 생성과, 서버 사이드 랜더링이다.

1) getStaticProps

정적 생성에서 페이지 컴포넌트가 pre-rendering 되는 시점은 애플리케이션을 빌드하거나 Next 프로젝트를 빌드하는 시점이다.

이점은 매우 중요한데 정적 생성에서는 기본적으로 요청이 서버에 도달했을 때 서버에서 즉각적으로 페이지를 pre-rendering하지 않는 대신에 개발자가 프로덕션용 사이트를 빌드할 때 pre-rendering한다.

페이지 컴포넌트 파일 안에서 특수 함수를 export로 내보내면 되는데 이 함수는 pages 폴더 안에 있는 컴포넌트 파일들에서만 가능하다.

pages 폴더 안에 있는 컴포넌트 파일 중 데이터를 가져와야하는 컴포넌트에 getStaticProps라고 하는 함수를 export 하면 된다.

반드시 getStaticProps라고 해야 하는데 NextJS는 이 이름을 가진 함수를 찾을 것이고 이를 발견하면 이 pre-rendering 프로세스 중에 이 함수를 실행한다.

따라서 컴포넌트 함수를 바로 호출하지 않고 getStaticProps 함수를 먼저 호출한 뒤 반환된 JSX 스냅샷을 HTML 콘텐츠로 사용한다.

getStaticProps라는 이름에서 볼 수 있듯이 이 함수는 실제로 이 페이지에서 사용할 props를 준비하고 이 props는 페이지에서 필요한 데이터를 포함할 수 있다.

또한 getStaticProps는 비동기적으로 설정될 수 있어서 유용한데 여기에서 promise를 반환할 수 있다.

NextJS는 이 promise가 해결될 때까지 기다린다. 다시 말하면 데이터를 읽어 들일 때까지 기다린 뒤 이 컴포넌트 함수에서 사용할 props를 반환한다는 뜻이다.

이렇게 하면 이 컴포넌트 함수가 실행되기 전에 데이터를 읽어 들일 수 있어서 이 컴포넌트를 필요한 데이터와 함께 렌더링할 수 있게 된다.

여기 getStaticProps 함수 안에서는 일반적으로 서버에서 돌아가는 어떤 코드든지 전부 실행할 수 있기 때문에 파일 시스템에 접근할 수도 있고 데이터베이스에 연결할 수도 있다.

여기에 작성하는 모든 코드는 클라이언트 측에 들어가지 않기 때문에 클라이언트 측에서 절대 실행되지 않는데 이는 이 코드가 빌드 프로세스 중에 실행되기 때문이다.

하지만 일단 필요한 데이터를 얻는 작업을 모두 완료했으면 여기 getStaticProps에서 항상 객체를 반환해야 하며 이 객체에는 props 프로퍼티를 설정해 준다.

이렇게 하면 getStaticProps에서 이 DUMMY_MEETUPS를 읽어 들이고 준비한 다음 이 페이지 컴포넌트에서 사용할 props로 설정된다.

때문에 이 페이지 컴포넌트에서 props로 받아서 컴포넌트 내에서 사용할 수 있게 된다.

따라서 이 페이지 컴포넌트에서는 이제 상태를 관리할 필요가 없고 useEffect도 필요하지 않게된다.

이렇게 하면 클라이언트에서 서버 쪽으로, 정확히 하자면 빌드 프로세스 과정 쪽으로 데이터를 가져올 수 있다.

getStaticProps에는 인자로 context라는 것을 받을 수 있는데 useRouter와 같은 역할을 한다고 보면 된다.

그렇게 전달받은 context에는 해당 페이지의 params이 존재하므로, 이 값을 이용해서 서버에 데이터를 요청한 후, props로 전달하면 되지 않을까 싶지만, 다른 페이지에서 Link를 통해 해당 dynamic page로 이동할 경우, getStaticPaths를 사용해달라는 에러를 낸다. 이는 밑에서 getStaticPaths에 대해 알아보며 해결해보자.

정적 생성(SSG)에서는 개발자가 프로덕션용 사이트를 빌드할 때 사전 렌더링한다. 즉 사이트가 배포되고 나면 사전 렌더링한 페이지는 변경되지 않는다는 뜻이다. 때문에 데이터를 업데이트했는데 사전 렌더링한 페이지를 변경해야 한다면 해당 빌드 프로세스를 다시 시작하고 다시 배포해야 하기 때문에 최신 데이터를 못받아오는 경우가 생길 수 있다.

정적 생성(SSG)
SSG는 빌드를 진행할 때 pages 폴더에서 작성한 각 페이지들에 대해 각각의 문서를 생성해서 static한 파일로 생성힌다.
만약 해당 페이지에 대한 요청이 발생하게 되면, 이 페이지들을 재생성하는 것이 아니라 이미 생성이 된 페이지를 반환하는 형태로 동작힌다. 따라서 React의 CSR보다 응답속도가 빠르다는 장점이 있고 Next.js에서도 SSG형태로 사용하는 것을 지향하고 있다.
마케팅 페이지, 블로그 게시물, 제품의 목록과 같이 정적 생성된 정보를 각 요청에 동일한 정보로 반환하는 경우에 위 SSG를 사용한다. 물론 데이터가 변할 때마다 사이트를 다시 빌드해서 다시 배포할 수도 있다. 개인 블로그 같은 웹사이트에서는 이런 방법이 괜찮을 수 있지만 데이터가 매우 빈번하게 바뀌는 경우에는 매번 빌드해서 다시 배포하기 힘들 수 있다.

이런 문제점을 해결하기 위해 getStaticProps에서 반환된 객체에 revalidate라는 프로퍼티를 추가할 수 있는데 이를 통해 점진적 정적 생성이라는 기능을 사용할 수 있다.

revalidate에는 숫자가 필요한데 이 숫자는 요청이 들어올 때 이 페이지를 다시 생성할 때까지 NextJS가 대기하는 시간을 초 단위로 표시한 것이다. 즉 서버에서 몇 초 간격으로 페이지를 재생성한다. 다시 말해서 revalidate 값이 10이라면 이 페이지에 요청이 들어오면 적어도 10초마다 서버에서 페이지를 다시 생성한다는 것이다.

데이터가 한 시간마다 변하는 경우엔 3600으로 설정하고 항상 변하고 있다면 1초로 해야 할 것이다.
이 숫자를 무엇으로 설정하든 이 페이지는 배포 후 서버에서 주기적으로 다시 사전 생성할 것이기 때문에 일부 데이터가 변경될 때마다 다시 빌드하고 배포할 필요가 없어진다.

2) getServerSideProps

하지만 주기적인 업데이트로도 부족하고 요청이 들어올 때마다 페이지를 다시 만들어야 할 때가 있다.

이럴 때 getServerSideProps를 사용하면 되는데 getStaticProps와의 차이점이라면 이 함수는 빌드 프로세스 중에는 실행되지 않는다는 것이다.

getServerSideProps와 똑같이 객체를 반환하고 props 프로퍼티를 받는다. 이 함수는

요청이 들어올 때마다 실행되기 때문에 시간을 지정해서 revalidate 할 필요가 없다.

대신 context 매개변수를 인수로 받아야 하는데 그 콘텍스트 변수에서, 요청 객체에 접속할 수 있으며 응답 객체 역시 받아올 수 있다. 이 콘크리트 요청 객체에 접근하는 건 인증 작업을 할 때나
세션 쿠키를 확인할 때 도움이 될 수 있다.

두가지 메서드 중에 getServerSideProps이 모든 요청을 실행하기 때문에 더 좋아 보일 수 있지만 그게 단점이 될 수도 있다. 요청이 들어올 때까지 페이지가 만들어지기 기다려야 한다는 뜻이기 때문이다. 때문에 항상 바뀌는 데이터가 없다면, HTML 파일을 사전 생성하는 getStaticProps이 좀 더 낫다.

대신 getStaticProps에서는 요청과 응답에 대해 접근할 수 없기 때문에 콘크리트 요청 객체에 접속해야 한다면 getServerSideProps을 사용해야 한다.

3) getStaticPaths

NextJs는 미리 서버사이드에서 페이지에 대한 pre-rendering을 진행한다고 하였다.
그리고, 만약 미리 서버에서 데이터 패칭과 같은 행위가 필요할 경우 getStaticProps를 사용하고, 이 함수를 export한다는 것은 Next.js에게 "나 미리 SSG을 함께 해서 pre-rendering을 하고 싶습니다." 라고 요청하는 것이다.

근데, 만약 이 getStaticProps가 실행되는 장소가 dynamic path이고, context의 params을 알아야 한다고 하자.
첫 pre-rendering이 진행되는 시점에서 Next.js는 해당 params이 무엇인지를 알지 못한다. (왜냐하면 알게되는 순간은 링크를 눌러 이동했을 때에 그 순간이기 때문이고, 그 전까지는 pre-rendering상황에서는 알 수 없기 떄문이다)

즉, 다시말하자면 SSG시점에서 getStaticProps에게 context로 오는 값들을 미리 알려주어 이 값들을 가지고 pre-rendering을 하라고 일러줘야한다.

getStaticPaths는 dynamic path 파일의 getStaticProps 아래 설정해주면 된다.
(객체를 리턴하고, 프로퍼티로 paths라는 배열값을 가지는 내용이 존재해야 한다. 배열 요소는 객체로, 그 안에 params가 존재해야 한다.)

해당 옵션을 통해, 첫 pre-rendering 당시 서버는 어떤 페이지를 랜더링해놔야 할지를 알게 된다.

실제로 build를 해보면 해당 dynamic routes들의 페이지들도 모두 랜더링이 된 것을 볼 수 있는데 build를 완료하면, 해당 dynamic route로 전송되야 하는 props의 데이터들이 json 형태로 저장되어 있고, 만약 클라이언트가 Next.js 서버로부터 해당 페이지를 내려받는다면 이때 이 json 데이터들이 같이 한번에 딸려서 패치되는것을 볼 수 있다. 즉, 각각의 dynamic page에 들어가기 이전에, 이미 필요한 데이터들은 다 json 형태로 패칭되는 상태이다.

이말인 즉슨, getStaticProps의 내부에서 무언가를 작업해서 props로 넘겨줄 때, json 형태로 데이터가 다 날아가는 상태이므로 보안이 설정되지 않았다면 중요한 개인정보와 같은 것을 pre-rendering하는것은 피하는게 좋다.

만약 dynamic path가 수십만개면?

상상해보면, 수십만개나 되는 dynamic path를 일일이 getStaticPaths로 전달해주는것은 불가능에 가깝다.
그래서 현실적으로 말해서 저렇게 업데이트를 하는 경우는 없고, 추가적인 옵션인 fallback을 통해 해결한다.

fallback? : 이 옵션은 getStaticPaths에게 전부 다 첫 build 타임에 pre-rendering을 하는 것이 아닌, client가 필요할 때마다 dynamic path에 대해 getStaticPaths를 호출하여 페이지를 SSG을 실행한 후 유저한테 JSON을 전달하라는 의미가 된다. 즉, 처음부터 전부 다가 아닌, 필요에 따라 라는 형태의 랜더링을 하게된다.

위와같이 설정할 경우, 실제 pre-rendering이 되는 dynamic page는 p1만 해당하고, 나머지는 요청이 들어올 때마다 rendering을 해서 전해진다. 다만 위와같은 경우로 제작했을 때, 유저가 Link가 아닌 고의적으로 URL에 page 경로를 입력하여 들어가려고 할 때 에러를 만들어낸다.

그 이유는 새로운 경로를 url에 쳐서 엔터를 누른다는것은 새로운 리퀘스트가 간다는 의미인데, 그 상황에서 서버가 새로운 json 데이터를 마저 생성하기도 전에 페이지적으로 뭔가를 보여주려고 시도하기 때문에 그렇다.

따라서, 이를 해결할 방법으로 이처럼 dynamic route에 대해서는 해당 필요한 값이 SSG 에 의해 생성되지 않았을 때의 처리를 따로 해주던가 blocking option을 통해 미리 json 데이터가 만들어지기 전까지는 페이지를 보여주지 않도록 대기시킬 수도 있다.

참고로, 존재하지 않는 데이터에 대해서 요청을 날리게 되는 경우가 있다. 예를들어 id는 3까지인데 4에 대한 내용을 요청했으면 로딩이 끝난 직후 필요한 데이터가 프랍에 없기 떄문에 에러를 낸다. 그래서 이것을 사전에 막아야 한다. getStaticProps 로직 내에서 return으로 404를 내도록 만들게 하면 된다.

정리하자면

getStaticProps를 이용해서 미리 pre-rendering을 하려고 할 때, 해당 페이지에 dynamic path가 존재한다면 처리해야 할 내용은 3가지이다.

  1. getStaticPaths를 설정하고, fallback을 true로 설정하거나 아니면 모든 dynamic page에 해당하는 값들을 param으로 리턴해줘서 pre-rendering하기
  2. 만약 fallback을 true로 설정했을 경우, getStaticProps에 path에러처리를 위한 return {notFound:true} 처리
  3. fallback을 true로 설정했을 경우, 클라이언트단에서 아직 prop이 준비되지 않은 상황에 대한 예외처리를 해줘야 함
profile
평생 공부하기

0개의 댓글