Next + Suspense + SSR
nextjs 에서 ssr, isr 방식을 올바르게 처리하기 위한 방식과 react 18에서 소개된 suspense 등의 기능 사용 방식을 정리합니다.
Q1. 서버에서 전달받는 html에 왜 CSR 로 요청한 데이터도 모두 들어가있는지?✅
Q2. 서버에서 받은 html 중, 왜 div hidden으로 해서 ssr에서 호출된 데이터가 있는지✅
Q3. react 코드 내 > getServerSideProps 에서 server or client에서 실행된 상황인지 판단 가능 여부 확인✅
Q4. server에서 1번, 클라이언트에서 1번 api 호출하는 건 어떻게 막아야 할지? 방법이 있는지?✅
Q5. 서버 실행 함수( getServerSideProps …) 사용 시, 불필요한 api 호출 막는 방법 정리✅
Q6. 서버에서 렌더링되며 api도 호출하는 컴포넌트가 한 페이지에 2개 이상일 때, html 생성 및 클라이언트로 전달 시점✅
Q7. 서버에서 호출한 api error 처리?✅
Q8. Cache✅
Q9. 유저 인증 관리 - SSR, SSG, ISR 일 때✅
Q10. react-query suspense:true 일 때,api fetch 무한✅
230116 상황 정리
SEO 에 잡히는 데이터 확인 기준 - 첫 경로로 입장 시, 네트워크로 서버에서 전달받은 html
dynamic
CSR로 클라이언트에서 렌더링될 컴포넌트에 대해서만 dynamic으로 감싼다
ssr: false로만 사용한다.
lazy loading, suspense 역할
getStaticProps
SSG, ISR 사용 시 사용하는 함수로, 빌드될 때 한 번만 실행된다.
SSG, ISR에서 호출해야하는 api 들은(SEO 하고자 하는 데이터) 해당 함수에서 fetch 한다.
Suspense
api를 호출하는 컴포넌트에 대해서는 항상 감싼다.
Q1. 서버에서 전달받는 html에 왜 CSR 로 요청한 데이터도 모두 들어가있는지? ✅
A. 정확하지 않은 방식으로 next/dynamic 을 사용하고 있었다. → CSR 컴포넌트는 html에 포함되지 않음.

// Error const LeftBar = dynamic(() => import('components/Suspense/LeftBar/LeftBar'), { suspense: true, ssr: false }); // Right const LeftBar = dynamic(() => import('components/Suspense/LeftBar/LeftBar'), { ssr: false }); ... <Suspense fallback={
위처럼 작성 시 html 파일에는 ssr 로 서버 쪽에서 렌더된 컴포넌트의 데이터 내용만 추가된다.
또한, 해당 컴포넌트는 클라이언트 사이드에서만 실행, 렌더되는 컴포넌트로서 동작한다. (즉, 로그 출력해도 터미널에서 확인되지 않음)
next/dynamic 자체가 React.lazy와 Suspense의 extension 이기 때문에 클라이언트 사이드에서 api 요청이 완료되지 않았을 때 Suspense의 fallback 컴포넌트가 정상 출력된다.

화면 기록 2023-01-16 오후 2.34.33.mov
Q2. 서버에서 받은 html 중, 왜 div hidden으로 해서 ssr에서 호출된 데이터가 있는지 ✅
A. 서버 측에서 필요한 데이터 수집이 완료되면 인라인 script 에 포함된다.
Suspense로 감싸진 컴포넌트는 필요한 데이터가 수집되지 않으면 fallback ui 가 출력된다.
이후, 필요한 데이터가 수집된다면 해당 컴포넌트는 인라인 script 태그와 함께 스트리밍 된다.


실제 코드에서 아래처럼 suspense > fallback ui를 실제 데이터 html 코드로 바꾸는 부분은 아래와 같습니다.

Q3. react 코드 내 >getServerSideProps에서 server or client에서 실행된 상황인지 판단 가능 여부 확인 ✅
A.getServerSideProps인자 값 ctx.req.url 로 확인 가능

deploy 하여getServerSideProps의 인자값 contex 콘솔 확인하여 확인하였습니다.
navigate를 통해 페이지에 입장한 경우

해당 페이지('/suspense')에 처음 입장한 경우

Q4. server에서 1번, 클라이언트에서 1번 api 호출하는 건 어떻게 막아야 할지? 방법이 있는지? ✅
A. SSR일 때, getServerSideProps 를 쓰지 않으려고 했는데, 사용하여 react-query의 캐싱을 사용하여 위 문제를 해결할 수 있다.
Q5. 서버 실행 함수( getServerSideProps …) 사용 시, 불필요한 api 호출 막는 방법 정리 ✅
상황
getServerSideProps, getStaticProps는 모두 해당 함수가 작성된 컴포넌트가 실행될 때마다 실행된다.
해당 컴포넌트(페이지)에 처음 입성한 경우
navigate를 통해 해당 컴포넌트(페이지)에 입장한 경우
shallow = true


참고 :https://dev.to/arianhamdi/react-query-v4-ssr-in-next-js-2ojj
서버 실행 함수의 props를 확인하여 커스텀 로직 함수 작성
추가해야하는 이유
shallow 프로퍼티는 /pages/1 → /pages/2 로의 이동에 대해서는 서버 실행 함수가 호출되지 않게 하지만, /home → /pages 로의 이동은 감지하지 못하여 서버 실행 함수가 호출된다.
참고
Q6. 서버에서 렌더링되며 api도 호출하는 컴포넌트가 한 페이지에 2개 이상일 때, html 생성 및 클라이언트로 전달 시점? ✅
A. 서버에서 렌더링되는 데이터 호출이 모두 완료되어야 클라이언트에 html이 전달된다.
Q7. 서버에서 호출한 api error 처리? ✅
getServerSideProps안에서prefetchQuery대신fetchQuery를 사용한다.
이유 :prefetchQuery는 데이터, 에러를 반환하지 않는다.
404 에러 처리 방법
참고 :https://dev.to/arianhamdi/react-query-v4-ssr-in-next-js-2ojj#404
Q8. Cache ✅
nextjs 공식 cache 처리 문서https://nextjs.org/docs/going-to-production#caching

getServerSideProps, getInitialProps 를 쓸 때는 next start 했을 때 next.config.js에 생성된 기본 Cache-control 헤더 값을 사용한다.
getServerSideProps 를 사용하며 다른 캐시 행동 양식을 정하고 싶을 때는 해당 함수 안에서 setHeader 함수를 사용하여 설정할 수 있다.
// This value is considered fresh for ten seconds (s-maxage=10). // If a request is repeated within the next 10 seconds, the previously // cached value will still be fresh. If the request is repeated before 59 seconds, // the cached value will be stale but still render (stale-while-revalidate=59). // // In the background, a revalidation request will be made to populate the cache // with a fresh value. If you refresh the page, you will see the new value. export async function getServerSideProps({ req, res }) { res.setHeader( 'Cache-Control', 'public, s-maxage=10, stale-while-revalidate=59' ) return { props: {}, } }
next/image 캐싱 문서https://nextjs.org/docs/api-reference/next/image#caching-behavior
→ 결과입니다.
react-query에서 제공하는 캐싱 전략과 next에서 디폴트로 제공하는 캐싱 전략을 사용한다. (추가로 변경, 사용할 일은 없을 것 같다)
Q9. 유저 인증 관리 - SSR, SSG, ISR 일 때 ✅
공식문서에서 유저 인증 Authentication 관련하여 2가지 방식을 제안합니다.
데이터 fetching 전략에 따라 선택하여 사용하기를 안내하고 있습니다.
1.Authenticating Statically Generated Pages
client side에서 user data를 fetching할 때 사용 가능한 패턴
= 페이지에 서버 실행 함수 (getServerSideProps등) 이 없는 경우
장점 : TTI (Time to Interactive) 시점이 빠르다.
원인 : 페이지가 static한 페이지이기 때문에 페이지는 global CDN에서 생성되며 next/link 를 사용해 preloading 되기 때문에
2.Authenticating Server-Rendered Pages*****
server side에서 유저 data api fetch 하여 인증 관리하는 방식
장점
유저 정보가 없을 때 리다이렉트 시키기 전에 보여지지 않아야 하는 컨텐츠가 잠시 보이는 등, 깜빡거리는 현상을 방지해줍니다.
원인 : 유저 인증 체크가 렌더링 이전 과정인 getServerSideProps에서 일어나기 때문에 렌더링 자체를 방지합니다.
위와 같은 이유로 TTFB 시간이 빠릅니다.
아래는 session을 사용할 때의 예시입니다.
import withSession from '../lib/session' export const getServerSideProps = withSession(async function ({ req, res }) { const { user } = req.session if (!user) { return { redirect: { destination: '/login', permanent: false, }, } } return { props: { user }, } })
Next/Image 최적화 관련 내용 및 과금 정책
Next/Image 는 새로운 source 이미지에 대한 요청이 있을때 새로운 최적화된 이미지를 생성후 edge network 라고 하는 cdn 으로 배포함 과금 정책이 5000장의 이미지 이후 부터는 1000장당 50불 과금됨 .. 무조건 아무 이미지에 다 최적화를 사용하면 안될듯 함
Q10. react-query suspense:true 일 때,api fetch 무한 ✅
[상황 발생 조건] - CSR로 로드되는 컴포넌트
getServerSideProps 등의 서버 실행 함수에서 데이터를 미리 fetch 하지 않는 경우 +
컴포넌트 내의 react-query > suspense: true 옵션을 건 경우
dynamic으로 불러진 상황 + ssr:false
dynamic으로 불러지지 않은 상황
suspense로 잘 감싸진 상황
정상 동작 (CSR로 동작함)
정상 동작 (CSR로 동작함)
suspense로 잘 감싸지지 않은 상황
에러(1)
무한 fetch 에러(2)
[에러 설명]
에러(1)
원인 : suspense:true 로 설정되어 데이터가 불러오기 전에 출력해야할 fallback ui가 없어 발생한 오류
에러(2)
상황 : 앱 자체가 계속 다시 rerender 되면서 query 가 무한으로 불린다.
react-query 에 enabled:false 를 준 상황에서는 아예 query 가 호출되지 않는다.
refetchonMount: false + enabled: false 일 때는 아예 마운트되어도 실행되지 않도록 함.