조리복 프로젝트 - Next의 SSR 제대로 알고 사용하기

Sally·2022년 12월 28일
0

조리복

목록 보기
3/4
post-thumbnail

Next로 Server Side Rendering하기

같은 조리복 팀원의 세지의 말을 인용해보고자 한다
Next 제대로 사용 못하면 라우터 달린 리액트다

무의식으로 리액트에서처럼 코딩을 한다면, SSR 프레임워크를 사용한 보람이 없어진다. CSR을 그저 Next에서 구현했을 뿐...

그래서 Next에서 제공해주는 SSR관련 함수들을 잘 살펴보아야한다 👀
해당 옵션들을 모른 채 그냥 진행한다면, CSR을 하는 것이다

SSR vs SSG 뭘 선택할까?

Next에서는 SSR과 SSG 둘 중 하나를 선택하여 Pre-rendering을 진행할 수 있다.

SSR과 SSG는 모두 Server측에서 Pre-rendering이 된다. 이 때에 추가적인 처리(getStaticProps등)를 하게 되면 외부 데이터까지 fetching되어 완성된 HTML파일이 유저의 브라우저에 전달되게 된다.

완성된 HTML파일을 넘기기 때문에 SSR과 SSG 모두 SEO에 장점을 가지게 된다
추가적으로 자바스크립트가 동작되지 않는 브라우저 상황이라도 완성된 화면은 보여줄 수 있다 (유저와의 상호작용은 불가능 할 수 있어도 말이다)

그렇다면, 이 SSR과 SSG 에는 무슨 차이가 있는 것일까? 어떤 상황에 SSR을 사용하고 SSG를 사용하여야할까?

Next 공식 문서에 안내되어 있는 그림을 보면 이해하기 쉽다

SSG


SSG로 설정해놓은 페이지의 경우, build타임에 만들어지게된다.
해당 html파일을 가까운 CDN등에 저장하여 유저에게 요청이 오게 되면 해당 파일을 바로 반환해서 보여준다

build타임에 만들어지기 때문에 매번 유저의 요청마다 새롭게 만드는 것이 아니다.
한 번 만들어진 HTML파일은 유저의 요청마다 재전달된다.

그렇기 때문에,
유저마다 다른 화면을 보여주어야 하거나
실시간으로 데이터가 바뀌는 경우 SSG는 적합하지 않다.

만약 SSG를 적용하게 된다면,
일정기간동안 모든 유저에게 동일한 내용을 보여주어야 하는 이벤트 페이지나 자주 업데이트가 일어나지 않는 회사 소개 페이지, 안내 페이지등에 적합할 것이다.

예를 들어보자, 설 기념 이벤트 안내 페이지를 만들어본다고 가정해보았을 때에는 SSG가 더 적합할 것이다.
우선, SEO에 더 최적화된 페이지로 제공함으로써 검색엔진에 더 많이 노출될 수 있다. 이는 홍보와 마켓팅을 많이 해야하는 이벤트 안내페이지로써 적합한 선택이다.
또한 설이라는 이벤트 기간까지 동일한 내용을 여러 유저에게 보여주고, 이벤트 기간 내에서는 데이터가 변할 경우가 거의 없으므로 SSG가 적합할 것이다.

주의할점!
Next가 development mode일 때에는 SSG페이지라도 모든 페이지들은 request들 마다 pre-rendered가 된다 production모드일때만, SSG는 build타임에 한 번 만들어지게 된다

SSR

SSR의 경우, HTML이 유저에게서 온 요청마다 서버에서 만들어지게 된다.

유저로부터 페이지에 대한 요청이 오게 되면, NextJS는 우선 외부 데이터를 fetch하게 된다. 그리고 받아온 데이터들을 토대로 HTML파일을 만들어 유저에게 제공하게 된다

그렇기 때문에 실시간으로 정보가 바뀌거나, 유저에 맞춰서 정보를 보여주어야 할 때에는 SSG보다는 SSR을 사용하는 것이 좋다

공식 문서에 따르면 SSG보다는 SSR이 느릴 수 밖에 없기 때문에 꼭 필요할 때만 사용할 것을 권장하고 있다.

만약 SSR을 적용하게 된다면,
서비스의 상품리스트(실시간 순위라던지) 메인 홈 화면을 선택할 것 같다.
메인 홈 화면의 경우, 유저가 서비스에 접속했을 때 가장 먼저 보여지는 화면이기 때문에 빠르게 유저에게 보여줘야 할 필요가 있다.
추가적으로, 서비스에 판매해야하는 상품들을 SSR을 통해 SEO에 더 유리하도록 하여 검색엔진에 더 잘 걸리도록 할 것 같다.

Next에서의 비동기 통신 with React Query

Pre-rendering을 적용할 페이지 선정하기

조리복 프로젝트에서 pre-rendering할 페이지를 선정하는 것이 우선이였다.
우리 프로젝트에서는 SSG는 도입하지 않기로 하였다.
왜냐하면, 우리 서비스에서는 정적인 페이지가 없었고(유저가 글을 올리고, 그리고 해당 글을 다른 사람이 게시글을 다는 형식이였기 때문에) 만약 있다고 하더라도 유저에게 빠르게 보여주어야 하는 화면이 아니기 때문에 불필요 했기 때문이다.

그래서 SSR을 적용하되, 2가지 페이지에만 SSR을 적용하기로 하였다.

  • HOME 페이지
    제일 먼저, 유저에게 보여지는 화면이기 때문에 빠르게 완성된 화면을 보여주는 것이 중요하다고 생각했기 때문에 도입하기로 하였다
  • 상세 페이지
    상세 페이지의 경우에도 우리 서비스 중에서 가장 중요한 파트 중 하나이기 때문에 유저에게 빨리 완성된 화면을 보여주는 것이 중요하다고 생각하여 도입하기로 하였다.

조리복에서 겪었던 비동기문제 with MSW and React-Query

서문이 조금 길었는데, 이제 본격적인 이야기 시작이다. SSR을 구현하면서 하라는 데로 했는데 "이게 왜 안 되지...?" 라는 상황을 마주했기 때문에 이를 기록으로 남겨보고자 한다

Next에서 제공하는 getServerSideProps

우선, Next에서 SSR을 사용하기 위해서는 해당 함수를 사용하여야한다.

export async function getServerSideProps(context) {
// 해당 부분에서 비동기 통신을 진행한다 
 const res = await fetch(`https://.../data`)
 const data = await res.json()

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

이곳에서 props로 return하게 되면 각각의 page들의 props로 fetching된 데이터가 넘어가게 된다

그 이유는 NextJS에서 보여지는 _app.tsx를 보게 되면

 <Component {...pageProps} />;

라고 보여지는 부분이 있는데, 해당 pageProps에 getServerSideProps에서 반환한 props 가 들어가게 된다.
그래서 페이지 단에서 props로 getServerSideProps로 처리한 데이터에 접근할 수 있게 된다.

서버에서 데이터를 받아오던 도중 에러가 발생하면 어떻게 처리될까?
getServerSideProps에서 에러가 발생하게 되면 Next에 지정된 500에러 페이지가 보여지게된다

하지만 React-Query와 함께 사용하여야 한다면?

데이터를 한 번 받아온 이후에 서버에서 데이터의 fresh관리 등을 쉽게 하기 위해서 (지정해둔 stale time이 지나게 되면 데이터의 유효성을 알아서 평가해주는 편리한 리액트 쿼리 잃을 수 없지...😘) 리액트 쿼리를 조리복에서는 사용하고 있다.

다행히도 리액트 쿼리에서는 친절하게 Next와 같이 사용하여야 할 때를 안내해주고 있다.
공식 문서에 따르면 Next에서 선택할 수 있는 옵션은 2가지이다.

1번은 initialData

export async function getServerSideProps() { 
  const posts = await getPosts()
  return { props: { posts } }
}

function Posts(props) {
  const { data } = useQuery({
    queryKey: ['posts'],
    queryFn: getPosts,
    initialData: props.posts,
  })

  // ...
}

앞서 소개했던 getServerSideProps를 최대한 사용하는 방식이다.
React-Query에서는 initialData를 산정할 수 있는데 이곳에 props로 넘겨받은 data를 넘겨주는 방식이다.

해당 방식은 편리하긴 하지만
initialData를 props로 계속해서 넘겨야할 수 도 있다.
page컴포넌트 내에 바로 useQuery가 존재하면 다행이지만, 여러 단계가 거쳐지는 children단계에 위치한 컴포넌트라면 props drilling이 발생하게 된다

2번은 Hydration

해당 방식은, 데이터의 안전성을 더 검증할 수 있다.
서버단에서 받아온 데이터를 HTML 삽입해서 유저 화면에 보여지는 것 까지는 initialData와 같다.
하지만 브라우저 상에서 자바스크립트 코드가 동작되는 시점에 React Query는 upgrade(또는 hydrate)할 수 있다.
서버에서 렌더링된 데이터가 stale한지 검증하여 stale하다면 바로 refetch 해올 수 있다.

// _app.jsx
import { Hydrate, QueryClient, QueryClientProvider } from '@tanstack/react-query'

export default function MyApp({ Component, pageProps }) {
  const [queryClient] = React.useState(() => new QueryClient())

  return (
    <QueryClientProvider client={queryClient}>
      <Hydrate state={pageProps.dehydratedState}>
        <Component {...pageProps} />
      </Hydrate>
    </QueryClientProvider>
  )
}
// pages/posts.jsx
import { dehydrate, QueryClient, useQuery } from '@tanstack/react-query';

export async function getStaticProps() {
  const queryClient = new QueryClient()

  await queryClient.prefetchQuery(['posts'], getPosts)

  return {
    props: {
      dehydratedState: dehydrate(queryClient),
    },
  }
}

function Posts() {
  // This useQuery could just as well happen in some deeper child to
  // the "Posts"-page, data will be available immediately either way
  const { data } = useQuery({ queryKey: ['posts'], queryFn: getPosts })

  // This query was not prefetched on the server and will not start
  // fetching until on the client, both patterns are fine to mix
  const { data: otherData } = useQuery({ queryKey: ['posts-2'], queryFn: getPosts })

  // ...
}

해당 방식은, initialData에 비해서 작성해야할 코드 양이 늘었지만 데이터의 안전성을 보장할 수 있다는 점에서 이득이 있을 것이라고 판단했다.
그래서 조리복에서는 hydrate방식을 채택하게 되었다.

내가 코드를 맞게 작성한 것인가... 실험을 통해 알아보자

자 그럼, 코드도 다 알려 준데로 작성했다.
이제 동작 시키면...

제대로 동작되지 않는다. ㅎ

SSR이 제대로 되었는지 어떻게 확인할까?
chrome 브라우저에서 자바스크립트 구동을 끄고 getServerSideProps로 지정해놓은 데이터가
HTML상에 잘 보여지고 있는지 확인하거나, 네트워크에서 최초로 온 html 응답값을 까보자 해당 부분에서 보여지고 있다면 자바스크립트가 동작하기 이전에 (클라이언트 사이드 렌더링 되기 이전에) 데이터를 받아왔다는 의미이다.

그렇다 우리 조리복에서는 또 고려해야할 점이 있었다.
바로 지금 동작중인 서버가 없다는 것이다. 😥
개발 스케쥴상 서버 개발을 맨 뒤로 밀어 두고 MSW를 통해서만 개발을 진행하고 있다.

그렇기 때문에 Server Side Rendering이 제대로 되지 않는 것이 MSW여서 인지,(사실 따지고 보면, MSW는 자바스크립트 코드니깐 브라우저에 올려져서 code가 run해야 구동되는것이 아닌가...) 코드 잘못인지 한 번에 판단하기 힘들어졌다.

그래서 무료 API를 사용하여 실제 구동중인 서버를 통해서 조리복에서 작성한 코드방식 그대로 옮겨 실험해보았다.

확실히 실험해보기 위해서,chrome 브라우저에서 자바스크립트 구동을 끄고, 강제 새로 고침 한 후에
네트워크 에서 html파일의 응답값까지 확인했다.
(*맨 처음에 보여지는 데이터는 이전 요청에 대한 응답값이다 현재 실험과는 관계가없다)

서버에서 받아온 응답값 "이미지 url"이 화면에 보여지고 있는 것을 확인할 수 있다!

추가적으로 자바스크립트 작동을 허용하고, hydrate동작 방식을 확인하면 위와 같은 결과가 나온다.

해당 api는 매 요청마다 랜덤하게 결과를 제공하기 때문에 리액트 쿼리가 유저에게 보여지는 데이터가 stale하다고 판단하여 새롭게 다른 이미지 url로 받아오는 것을 볼 수 있다

해당 실험을 통해 코드 자체는 틀리지 않은 것을 확인할 수 는 있었다.
물론, 서버가 완성되고 나서 다시 확인해보면 다를 수 있지만 일단은 그렇다 😎

0개의 댓글