SSR: Server Side Rendering
작동방식 : 사용자가 페이지를 요청하면, 서버에서 HTML을 생성하여 브라우저에 전달함. 브라우저는 이 HTML을 렌더링하여 페이지를 표시함
서버 측에서 페이지 생성후 클라이언트에 전달 초기 요청시에 HTML과 데이터가 완전 렌더링되어 전달
장점: 페이지 로딩 시간이 빠름, 자바스크립트 필요없음, SEO최적화가 좋음, 보안이 뛰어남, 실기간 데이터를 사용, 사용자별 필요한 데이터를 사용함
단점: 초기 로딩 이후 페이지 이동 속도 느림, 서버의 과부하가(overhead) 걸릴 수 있음, CDN에 캐시가 안됨
SSR에서 데이터 패칭은 서버 컴포넌트에서 데이터 통신을 하는 것을 말합니다. 서버에서 이루어지기 때문에 사용자 정의 인터렉션 훅(useState) 같은 것을 사용할 수 없습니다.
CSR : Client Side Rendering
작동방식 : 클라이언트에서 페이지를 생성하고, 필요한 데이터를 요청하여 동적으로 페이지 렌더링한다.
HTML 파일이 서버로 보내진다, 초기 요청시에 HTML만 전송하고, JS코드를 통해 페이지 완성
유저의 요청이 있을 때, HTML 파일을 브라우저로 보내주고, 그 다음 client는 API server에 data를 요청한다.
장점 : 빠른 초기 로딩 시간, 동적 페이지 구성, 초기 서버 부하 감소, 캐싱 활용
단점 : 초기 로딩시간이 SSR보다 느리다, SEO 추가 보안 작업 필요
CSR에서 데이터 통신은 useEffect()를 사용하는 고전적인 방식으로 사용할 수 있습니다. 클라이언트 기반에서 발생하는 데이터 패치이기 때문에 useState 같은 인터렉션 훅을 사용할 수 있습니다.// next 14 부터 이용시 최상단에 적어줘야 인식이 됨 "use client";
Layout
// layout.tsx // Next.js는 각 라우트 경로마다 layout.tsx 파일을 지정하여 공통된 레이아웃을 처리해줄 수 있습니다 export const metadata = { title: "Next.js Starter", description: "Generated by Next.js", }; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="en"> <body> <Navigation /> {children} </body> </html> ); }
not-found
// 페이지를 찾을 수 없을 때 보이는 not-found 페이지는 Next.js 프레임워크에서 ‘not-found.tsx’ 파일로 제어할 수 있다. 가장 최상위에 있는 경로에 생성해야 한다 const NotFound = () => { return ( <> <h1>NotFound Component</h1> </> ); }; export default NotFound;
loading
// 컴포넌트의 로딩 상태 관리 const Blog = async () => { await new Promise((resolve) => setTimeout(resolve, 5000)); return ( <> <h1>Blog Component</h1> </> ); }; export default Blog;
error
// 컴포넌트에서 에러가 발생했을 때 사용자에게 보여주는 페이지 const Blog = async () => { await new Promise((resolve) => setTimeout(resolve, 1000)); throw new Error("This is an error"); return ( <> <h1>Blog Component</h1> </> ); }; export default Blog;
Request Memoization
웹 서버로 페이지 요청이 들어오면 페이지에 필요한 데이터들을 fetch하게 되는데, 이때 동일한 endpoint로의 API fetch를 여러 컴포넌트에서 수행할 필요가 있다면 Request Memoization이 동작한다. (React가 fetch 함수를 확장해놓았기 때문에 별도 설정은 필요 없다.)
상위 컴포넌트에서 API fetch 결과를 prop drilling 하는것 대신, 각 컴포넌트에서 fetch를 수행하도록 구현해도 실제 API 요청은 최초 1회만 전송되고 나머지는 응답값을 재사용한다.
Request Memoization은 서버에서 호출되는 GET 메서드에만 적용되므로, POST나 DELETE API 또는 클라이언트에서 호출되는 API에는 적용되지 않습니다. 그리고 한 번의 서버 렌더링 동안만 유효하기 때문에 따로 revalidate 할 필요가 없을 뿐 아니라 할 수도 없습니다.Data Cache
우리가 일반적으로 생각할 수 있는 API 캐싱
fetch('https://...', { next: { revalidate: 3600 } })
Next.js가 확장해놓은 fetch 함수에 next.revalidate 옵션을 넘기면 Data Cache가 동작한다. 성공적으로 데이터를 가져왔다면 그 응답값을 저장해두었다가 동일한 경로로 fetch 함수를 실행할 때 실제 API 호출은 건너뛰고 저장해놓은 응답값을 반환한다.
하나의 요청 동안만 유효한 Request Memoization과 다르게 Data Cache는 일정 시간 동안에 웹 서버로 들어오는 모든 요청에 대해 동작한다. 만약 next.revalidate를 1초로 설정했다면, 1초에 1000명의 사용자가 접속해도 실제 API 요청은 1회 전송된다
Data Cache를 설명하는 위 이미지에서 한 가지 짚고 싶은 부분은 revalidate 시간이 지나더라도 첫 요청은 캐싱된 값을 (STALE 상태여도) 반환한다. 반환 후 백그라운드에서 API를 호출해서 값을 업데이트하는데, 개발자 의도와 다르게 동작할 수 있기 때문에 캐시를 적용할 때 주의가 필요하다.
router.refresh로는 Data Cache가 revalidate되지 않고, revalidatePath를 사용해야 한다. (이때는 즉시 revalidate 되기 때문에, 다음 첫 요청에도 새로운 값을 반환합니다.)
Full Route Cache
웹 서버의 성능을 눈에 띄게 향상시키려면 Full Route Cache를 적용해야 합니다. 서버 렌더링 과정에서 웹 서버의 리소스(특히 CPU)를 대부분 사용하게 되는데, Full Route Cache는 서버 렌더링 결과를 재사용함으로써 이를 줄일 수 있다.
Full Route Cache를 적용하려면 페이지를 Static 렌더링 되도록 구성해야 한다. 다시 말해 Dynamic Function을 사용하지 않아야 하는데, 그렇지 않으면 그림과 같이 Full Route Cache 단계가 SKIP 된다.
참조