Next.js 개발자가 꼭 알아야 할 SSR 질문 모음

Q&A 큐레이션

1. Next.js SSR + react-query 조합에서의 serializing 에러

안녕하세요! Next.js SSR + react-query 조합을 사용하려고 하는데요, page 컴포넌트 내 getServerSideProps 함수에서 prefetching을 받아온 후에 serializing 에러가 발생합니다. (Next.js는 13버젼입니다.) 에러 내용은 다음과 같습니다. Error: Error serializing `.dehydratedState.queries[0].state.data.headers` returned from `getServerSideProps` in "/top". Reason: `object` ("[object AxiosHeaders]") cannot be serialized as JSON. Please only return JSON serializable data types. 해당 에러 내용으로 구글링을 해보니, 대부분 getServerSideProps 함수 반환 코드에서 return { props: { dehydratedState: JSON.parse(JSON.stringify(dehydrate(queryClient))), }, }; 와 같이 dehydrate(queryClient)값을 JSON화 -> Object화를 하라고 하는데요, 이와 같이 사용해도 또 다시 아래와 같은 에러가 납니다. TypeError: Converting circular structure to JSON --> starting at object with constructor 'ClientRequest' | property 'socket' -> object with constructor 'Socket' --- property '_httpMessage' closes the circle Backend API는 Express.js를 사용하고 있으며, res.status(200).json({ data: ~ })와 같은 방식으로 응답을 주고 있습니다. 어떻게 해결할 수 있을까요? 코드 첨부가 안되네요, 아래는 page 컴포넌트가 위치한 파일의 전체 코드입니다. import type { ReactElement } from 'react'; import { dehydrate, QueryClient, useQuery } from '@tanstack/react-query'; import { format } from 'date-fns'; import TopMusicContainer from '~containers/TopMusicContainer'; import Layout from '~layouts/Layout'; import type { NextPageWithLayout } from '~pages/_app'; import TopMusicService from '~services/topMusicService'; import * as MusicType from '~types/musicType'; export async function getServerSideProps() { const queryClient = new QueryClient(); await queryClient.prefetchQuery(['fetchTopMusic'], () => { const params: MusicType.ListRequestType = { filter: 'title', keyword: '', page: 1, limit: 25, time: format(new Date(), 'yyyyMMddHH'), }; return TopMusicService.list(params); }); return { props: { dehydratedState: JSON.parse(JSON.stringify(dehydrate(queryClient))), }, }; } const Top: NextPageWithLayout = (): JSX.Element => { const { data, isLoading } = useQuery({ queryKey: ['fetchTopMusic'], queryFn: () => { const params: MusicType.ListRequestType = { filter: 'title', keyword: '', page: 1, limit: 25, time: format(new Date(), 'yyyyMMddHH'), }; return TopMusicService.list(params); }, }); return ( <section> <TopMusicContainer /> </section> ); }; Top.getLayout = function getLayout(page: ReactElement) { return <Layout>{page}</Layout>; }; export default Top;


답변

안녕하세요. Axios response를 그대로 사용하시면 함수 등 JSON으로 변환이 불가능한 것들이 포함되어 있어서 axios response에서 data만 추출해야 하는 것으로 알고 있는데요. 혹시 TopMusicService.list(params); 의 리턴 타입이 어떻게 되나요? 참고한 SO 포스트: - https://stackoverflow.com/questions/67204170/getserversideprops-functions-response-cannot-be-serialized-as-json-in-next-js

외 1개 답변 보러 가기

2. next js에서 사용자 검증시 화면 안깜빡거림은 ssr만 가능한가요?

안녕하세요, 리액트 넥스트로 앱을 만드는 개발자 입니다. 사용자 검증을 csr(useEffect), ssr(getServerSideProps)둘 다 해보았습니다. 다만 csr의 경우 완전한 렌더링 이후 검증을 하기에 잠시나마 화면 깜빡임이 있습니다. ssr의 경우 서버에서 검증을 하기에 화면 깜빡임은 없지만 모든 화면에 ssr로직을 작성해야 합니다. 만약 화면 깜빡거림 없이 즉시 사용자 정보 UI를 화면에 나타내려면 SSR이나 getinitialProps를 사용하는 방법밖에 없나요?


답변

안녕하세요! 우선 "완전한 렌더링 이후"의 기준이 뭔지 잘 모르겠어서 "서버사이드에서 내려주는 최초 html이 보여진 후, hydration 이전"라고 가정하고 답변 드리겠습니다. 말씀하신것처럼 CSR에서 외부 서버 요청을 하는 경우, 결과를 받아서 다시 그려줄때까지 시간차가 발생하는데요. 이때 작성된 코드에 따라서 다르지만 깜빡거림이 발생할 수 있습니다. 일반적인 경우라면 깜빡임 보다는 최초 html에서 없던 컴포넌트가 데이터 요청 결과를 받은 후 갑자기 그려지는 현상일 것 같아요. 최초 html에서 없던 컴포넌트가 갑자기 생겨나면 SEO에 안좋은 영향을 줄 수 있기도 하고, 사용자 경험이 좋지 않기 때문에 대부분 해당 위치에 컴포넌트와 동일한 크기의 스켈레톤 (로딩 상태 컴포넌트)를 보여주기도 합니다. (다만, 화면이 깜빡이는 것은 최초 html이 렌더된 뒤 DOM을 버리고 다시 DOM을 만들어서 그려주는 현상인것 같은데 꼭 이럴 필요가 있는지 확인해보시면 좋을 것 같네요) SSR의 경우는 클라이언트로 html을 내려주기 전에 필요한 정보를 요청한 뒤 취합해서 내려주기 때문에 화면 깜빡임이 없는 것 같네요. 질문으로 돌아가보자면, "화면 깜빡거림 없이 즉시 사용자 정보 UI를 화면에 나타내려면" 중 "화면 깜빡거림"을 "컴포넌트만 깜빡거림" 수준까지 격리를 할 수 있다면 스켈레톤을 보여주는 것도 방법인것 같아요. "화면 깜빡거림"이라면 SSR도 나쁘지 않을것 같습니다. 하지만, SSR의 경우 사용자 검증 로직이 돌때까지 클라이언트(사용자)가 흰색 화면을 보게될 시간이 더 길어지겠죠. "완전한 렌더링 이후" === "서버사이드에서 내려주는 최초 html이 보여진 후, hydration 이전"이라면 종현님이 답변해주신 useLayoutEffect를 사용한다고해도 동일하게 화면이 깜빡거릴 것 같습니다. 만약 "완전한 렌더링 이후"의 의미가 "hydration을 거친 후 리액트 렌더 사이클을 한번 거친 html"이라면 useLayoutEffect를 사용해주시면 화면 깜빡거림이 사라질 것 같긴합니다. 간단한 예시를 code sandbox로 만들어보았습니다. https://codesandbox.io/p/sandbox/loving-water-67oc1z?file=%2Fpages%2Findex.tsx 해당 샌드박스의 프리뷰 주소를 새로운 브라우저 탭에서 크롬 데브 툴과 함께 3G 속도로 확인해보시면 될 것 같습니다 :)

외 1개 답변 보러 가기

3. next js getStaticProps rehydration 질문 있습니다.

SSG, ISR, SSR 이론은 다 알고 있습니다. 그런데 개발도중 NEXT.JS 고질병인 client, server 충돌경고를 종종 겪는중 의문이 생겼습니다. 페이지를 미리 그리는데 왜 에러가 나지 하는 생각에 질문 드립니다. 1. 만약 SSG로 만든 페이지가 HTML로 만들어져서 화면에 미리 렌더링이 되는데, 그럼 미리 렌더링된 이후(제어권이 리액트로 넘어감) CSR은 화면을 로드할때 사전에 만든 HTML을 로딩하나요? 아니면 해당 페이지를 새로 그리나요? 2. 만약 새로 페이지를 그린다면 기존 pre render된 html 파일 내용과 csr이 그린 데이터가 다르면 next.js가 match 경고를 출력하나요? 2.1 next js에서 server, client 내용이 다르면 match경고를 발생시키는 이유는 무엇인가요? 3. csr이 미리 만들어진 html을 사용하여 페이지를 그린다면 이유가 뭔가요?


답변

안녕하세요! 리액트에서 rehydration에 대한 개념을 잘 정리한 글이 있어서 공유합니다 - https://www.joshwcomeau.com/react/the-perils-of-rehydration/#rehydration--render-7 간단하게 설명을 드리자면, rehydration은 서버에서 받은 html과 react가 최초 생성한 react node들이 맞는지 확인하는 거라고 합니다. (이때 어떤 상태에 따라서 다시 node를 그리거나 하는 행위는 하지 않는다고 해요) 그래서 서버에서 받은 html과 rehydration 후 생성된 react의 node들이 다를 경우 mismatch 에러를 내뱉는다고 합니다. mismatch 에러를 내뱉는 이유는 의도하지 않은 경우, 대부분 버그일 확률이 높다고 간주하기 때문이에요. 대부분의 해결법은 useEffect에 해당 로직을 옮기는 걸 추천하는데요. 이유는 최초 마운트 후 (rehydration 이후) 로직에 따라서 새로운 node들이 그려지기 때문에 mismatch 에러를 피할 수 있기 때문이에요. next.js만 발생하는 문제는 아니고 리액트를 지원하는 모든 서버 렌더링 프레임워크에서 발생할 수 있는 문제입니다. - https://beta.reactjs.org/reference/react-dom/client/hydrateRoot#usage

이 질문 바로 가기

4. 현업에서도 nextjs로 데이터 서빙하는데 사용하시나요?

nextjs가 backend 프레임웍으로 분류되어 있더라고요. 그럼 api서버(DB연결)의 역할을 하면서 프론트 파일 서빙하는 것 까지 nextjs 하나로 충분한가요? 그리고 실제로 현업에서도 nextjs를 baas 로 사용을 많이 하시는지 궁금합니다!


답변

말씀하신 부분은 가능합니다. Next.js는 내부적으로 express를 띄우기 때문에 말씀하신 DB 연결을 비롯한 API 서버역할도 가능하고 동시에 뷰서버 및 각종 파일 서빙이 가능한 프레임워크입니다. 그렇지만 백엔드 프레임워크이라고만 설명할 수 있을지는 잘 모르겠습니다. 어디에서 backend 프레임워크라고 분류되어 있는지는 모르겠으나 Next.js 홈페이지만 가도 떡하니 The React Framework for the Web라 적혀있고 풀스택 웹앱을 가능하게 한다고 설명하고 있습니다. 실제로도 CSR, SSG, SSR 모두 가능하게 할 수 있으니까요! 현업에서 Next.js를 BaaS로 쓰냐는 질문에 대해서는 큰 회사에 큰 서비스라면 대표적으로 저희 회사에선 메인 서비스의 API 서버용으로 Next.js를 고려하지 않습니다. 내부 백오피스나 BFF 용도로 Next.js를 사용하고 있긴 하지만 그마저도 React와 같이 즉, 프론트엔드와 같이 사용하지 백엔드 단독으로 사용하고 있진 않습니다. 규모가 작은 스타트업에선 관리할 코드를 줄이거나 언어 등을 맞추기 위해 쓸 순 있을 것 같습니다. 그러나 그 경우에도 다른 Node.js용 프레임워크를 우선 고려하지 Next.js를 차용하는 경우가 많을까요..? 제 생각은 굳이..? 싶네요..ㅎㅎ

외 2개 답변 보러 가기

5. SSR과 CSR에 대한 질문

제가 지금 강좌를 보면서 햇갈리는 것이 있습니다. 아래는 제가 SSR에 대해 배운 내용입니다. 1. 클라이언트가 페이지 요청을 Front Server에 한다 2. Front Server는 해당 페이지의 데이터를 Backend Server에 요청한다 3. Backend Server는 필요한 데이터를 DataBase에 요청한다 4. Database가 Backend Server에 Data를 발송한다 5. Backend Server는 Front Sever에 Data를 발송한다. 6. Front Server는 HTML파일과 같은 것과 Data를 섞어서 클라이언트에 발송한다 그러면 이것을 정리하면 페이지 하나를 받아올때, Client -> front -> back -> db -> back -> front -> client 위와 같은 방법으로 받아오는 것 같은데, 제가 더 알아보고 싶어서 인터넷에 검색을 했더니 SSR 방식은 client에게 js가 없는 페이지를 먼저 띄우고 데이터를 불러오는 방식이라고 합니다 그렇다면 Client -> front -> Client -> Back -> DB -> Back -> Client가 맞는 것 아닌가요?


답변

음, 질문자님이 언급한 플로우도 (Client -> front -> Client -> Back -> DB -> Back -> Client) 충분히 있을 수 있을 것 같아요. 그전에 1~6번의 플로우가 일반적인 SSR의 흐름이라고 생각하시면 될 것 같습니다. 이 페이지를 예로 들어볼까요? (https://careerly.co.kr/qnas/773?fa=qna-list) 현재 이 페이지는 Next.js 프레임워크의 SSR 기능을 사용하고 있습니다. 크롬 데브 툴(f12)로 네트워크 탭을 열고 새로고침을 하시면, 페이지가 어떤 요청을 보내고 어떤 데이터를 받는지 볼 수 있습니다. 제일 처음 요청하는 "773?fa=qna-list" (아님 그냥 773) 요청을 눌러서 preview 창을 보시면 이 페이지를 렌더하기 위한 질문자님의 질문 데이터가 이미 HTML에 포함 된 것을 보실 수 있습니다. 이 HTML은 frontend server에서 내려준 것입니다. 이 흐름을 1~6번에 맞춰서 보면: 1. 클라이언트가 페이지 요청을 Front Server에 한다 - 크롬이 frontend server로 "773?fa=qna-list" 요청을 한다 2. Frontend Server는 해당 페이지에 필요한 데이터를 Backend Server에 요청한다 - frontend server는 qna/773에 맞는 데이터를 backend server로 요청한다 (이건 크롬 데브 툴 네트워크 창에 뜨지 않습니다. frontend server 컴퓨터에서 backend로 요청하는 것이니, frontend server 로그에 남겠죠) 3. Backend Server는 필요한 데이터를 DataBase에 요청한다 - qna/773에 대한 정보를 backend가 DB로 요청한다 (질문자님의 질문 내용이겠죠) 4. Database가 Backend Server에 Data를 발송한다 - qna/773에 대한 정보를 DB에서 backend로 보내준다 5. Backend Server는 Front Sever에 Data를 발송한다. - qna/773에 대한 정보를 backend에서 frontend server로 보내준다 6. Frontend Server는 HTML파일과 같은 것과 Data를 섞어서 클라이언트에 발송한다 - 전달받은 qna/773에 대한 정보를 사용해서 preview에 나왔던 HTML 페이지를 클라이언트 (크롬)으로 보내준다 - 이게 "js가 없는 페이지"가 되겠죠? -------- 이제 추가로 질문 내용 하단에 있는 "궁금해요" 버튼을 한번 볼까요? "궁금해요" 버튼을 누르면 상태 값이 바뀌면 UI가 업데이트 되는 것을 볼 수 있습니다. 이거를 네트워크 탭에서 보시면, "liked"라는 이름의 PUT 요청이 날라가는 것을 볼 수 있습니다. 여기서 headers 탭을 눌러서 보시면 요청한 서버의 주소를 볼 수 있습니다. 이 주소를 "773?fa=qna-list" 요청의 header와 비교해보시면 주소가 다른 것을 볼 수 있죠? "773?fa=qna-list"는 frontend server로 요청을 한 것이고, "liked"는 backend server로 요청을 한 것입니다. (아까 보았던 "773?fa=qna-list" preview에서도 "궁금해요" 버튼은 있었지만 눌러도 아무런 요청이 날라가지 않습니다. hydration 과정을 아직 거치지 않은 "js가 없는 페이지"이기 때문이죠) -------- 결론은 1번~6번의 플로우는 일반적인 SSR 페이지 (서버에서 데이터를 조회할 필요가 있는 페이지)를 요청할 때 거치는 플로우를 설명하고 있고 질문자님의 플로우도 페이지의 구성에 따라 가능한 플로우일 수 있다 입니다. 예를 들면, frontend server에서 SSR로 내려주는 페이지가 최초 렌더 시에 backend로 데이터를 요청할 필요가 없는 페이지이고 이후 client에서 hydration 과정을 거친 후 js로 인해 backend로 데이터를 요청하게끔 되어있다면 질문자님이 언급하신 Client -> front -> Client -> Back -> DB -> Back -> Client 플로우가 성립할 것 같긴하네요. 하지만 이 경우는 backend로 데이터를 조회하는 로직이 CSR로 처리된다고 생각하시면 될 것 같습니다.

이 질문 바로 가기

6. next.js hydrate가 무엇인가요? (Did not expect server HTML to contain the text node)

안녕하세요 next.js로 개발하고 있는 1년차 뉴비 프론트엔드 개발자입니다. 페이지에서 간헐적으로 문구가 안보이는 이슈가 있어 콘솔을 확인해보니 'Did not expect server HTML to contain the text node' <- 이런 에러가 발생하고 있었습니다. 검색하다가 hydrate하는 과정에서 mismatch가 생겨서 발생한 에러라는 영어 댓글을 보긴 했는데 hydrate에 대한 설명을 찾아봐도 너무 어렵더라구요.. 혹시 간단하게라도 hydrate에 대해 설명해주실 수 있으신가요? 또 'Did not expect server HTML to contain the text node'에러는 왜 발생했는지 궁금합니다..


답변

해당 문구가 서버에서 받는 html에 잘 찍혀서 오나요? (undefined는 아닌지 궁금합니다) 아니면 클라이언트로 넘어왔을 때 문구를 콘솔에 찍으면 잘 나오나요? 간헐적인 이슈라면 서버나 클라이언트에서 undefined를 찍는 것 같습니다. 먼저 확인해보셔야 할 것 같아요!

이 질문 바로 가기

지금 가입하면 모든 질문의 답변을 볼 수 있어요!

현직자들의 명쾌한 답변을 얻을 수 있어요.

또는

이미 회원이신가요?

키워드로 질문 모아보기

실무, 커리어 고민이 있다면

새로운 질문 올리기

지금 가입하면 모든 질문의 답변을 볼 수 있어요!