[Nextjs] 데이터 가져오기 기초(SSR, SSG, CSR)
SSR, SSG, CSR에 따라 Nextjs로 데이터를 방법에 대해 하나씩 알아보자. 기존의 react는 CSR방식으로 useEffect를 사용했다면, react의 프레임워크인 Nextjs에서는 getStaticProps
, getStaticPaths, getServerSideProps
활용하여 데이터를 가져오는 방법에 대해 하나씩 알아보자.
Nextjs의 각각의 페이지는 .js, .jsx, ,tx, ,tsx,파일인 리액트 컴포넌트이고 각각의 페이지는 파일 이름과 관련이 있다. 예를 들면 pages/about.js를 아래와 같이 만들었다고 하자.
function About() {
return <div>About</div>
}
export default About
그러면 아래의 리액트 컴포넌트는 /about 로 브라우저 접근이 가능하게 된다.
Nextjs 동적 라우팅 (Dynamic Routes)
Nextjs는 동적 라우팅도 제공된다. 만약 우리가 pages/posts/[id].js라는 파일을 만들면 우리는 posts/1, posts/2와 같이 접근이 가능하다.
https://nextjs.org/docs/routing/dynamic-routes
pre-rendering이란
Nextjs의 기본은 pre-renders이다. pre-render란 페이지에서 js를 우선하는게 아니라, HTML을 미리 만드는 것을 말한다. 따라서 더 좋은 성능과 SEO를 기대할 수 있다. 이때 생성된 HTML은 최소한의 javascript 코드와 연결된다. 그 후에 브라우저가 로딩될 때 남은 javascript가 페이지와 상호작용하면서 페이지가 render 된다. 이러한 개념은 hydration이라는 개념이라고 부르기도 한다.
pre-rendering 하는 두 가지 방법 (SSG와 SSR)
Nextjs는 위의 pre-rendering하는 방법으로 Static Site Generation과 Server-side Rendering이 있다. 이 둘의 차이는 HTML을 생성 시에 차이점을 보인다.
- Static Generation (Recommended): The HTML is generated at build time and will be reused on each request.
- Server-side Rendering: The HTML is generated on each request.
정리하면, SSG는 빌드 시에 HTML을 만들고 각각의 요청 시 재사용한다. 그리고 SSR을 각각의 요청 시에 html을 만든다. 이러한 두 가지 방법을 Nextjs에서 제공을 한다. 기본적으로 SSG가 SSR보다 높은 성능을 가지기 때문에 SSG를 사용하는 것이 좋다. 하지만 상황에 따라 SSR을 사용하는 것이 좋을 때가 있다. CSR의 경우는 SSG나 SSR 내부에서 필요시 포함시켜 사용을 하면 된다. 조금 더 디테일하게 알아보자.
Static Generation 만들기(SSG)
SSG에서 HTML은 next build 명렁어를 칠 때 발생한다. 그 후에는 CDN으로 캐시 되어지고 매 요청마다 HTML을 재사용한다. Nextjs에서는 데이터의 유무에 따라 정적으로 페이지를 만드는 것이 가능하다. 각각의 상황에 따른 Nextjs 페이지를 만들어보자.
단순한 SSG
데이터를 불어오는 것 없이 아래와 같이 Static Generation을 만들 수 있다. 사실 단순하게 컴포넌트를 만들면 SSG로 동작한다는 것을 이해하자.
function About() {
return <div>About</div>
}
export default About
위의 경우 Nextjs는 빌드 시 각각의 페이지에서 HTML을 만든다.
데이터 가져오는 SSG
react에서는 useEffect통해 데이터를 가지고 온다. 그러나 여기서는 useEffect를 사용하면 SSG로 작동하지 않는다. Nextjs에서 SSG를 하려면, Nextjs에서 제공하는 getStaticProps나 getStaticPaths를 사용한다. 아래의 예를 보고 조금더 이해도를 높여보자.
CASE 1. 우리가 만드는 페이지가 외부의 데이터에 의존하는 경우
예를 들어 블로그의 글들을 가져온다고 해보자.
// TODO: Need to fetch `posts` (by calling some API endpoint)
// before this page can be pre-rendered.
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
)
}
export default Blog
위는 기본적인 Blog 컴포넌트이다. Nextjs에서 pre-render로 데이터를 가져오기 위해서는 아래와 같이 파일 내부에서 getStaticProps라는 async 함수를 export 해주는 것이 필요하다. 이 함수는 빌드 시 실행되어 데이터를 가져오고 컴포넌트의 props로 들어가게 된다.
function Blog({ posts }) {
// Render posts...
}
// This function gets called at build time
export async function getStaticProps() {
// Call an external API endpoint to get posts
const res = await fetch('https://.../posts')
const posts = await res.json()
// By returning { props: { posts } }, the Blog component
// will receive `posts` as a prop at build time
return {
props: {
posts,
},
}
}
export default Blog
정리하면, getStaticProps에서 return 되는 데이터가 Blog의 props로 들어간다고 생각하면 된다. getStaticProps에 대한 추가적인 내용은 아래의 url에서 참고하자.
https://nextjs.org/docs/basic-features/data-fetching/get-static-props
CASE 2. 우리가 만드는 페이지의 path가 외부의 데이터에 의존하는 경우
블로그에서 동적 라우터(Dynamic Route)를 사용해서 하나의 글을 보여주기 위해서 'pages/posts/[id].js'라는 파일을 만들었다고 하자. 이것은 'posts/1'에 접근을 했다면, 'id : 1'인 블로그 게시글을 볼 수 있다.
To learn more about dynamic routing, check the Dynamic Routing documentation.
이때, 빌드 시 pre-render 되길 원하는 'id'값이 외부의 데이터에 의존하는 경우를 생각해보자. db에서 id가 1인 블로그를 추가한다면, 빌드 시 posts/1을 pre-render 해야 한다. 그리고 id2를 추가로 더하려면 posts/2도 추가로 pre-render 해야 한다. 이러한 예시가 바로 pre-render 된 page paths가 외부 데이터에 의존하는 경우다. 이것을 구현하기 위해서는 동적 페이지에서 ( ex. pages/posts/[id].js ) getStaticPaths인 async 함수를 export 해야 한다. getStaticPaths 함수는 빌드 시에 호출되며, 함수에 pre-render 되길 원하는 paths들 명시하면 된다.
// This function gets called at build time
export async function getStaticPaths() {
// Call an external API endpoint to get posts
const res = await fetch('https://.../posts')
const posts = await res.json()
// Get the paths we want to pre-render based on posts
const paths = posts.map((post) => ({
params: { id: post.id },
}))
// We'll pre-render only these paths at build time.
// { fallback: false } means other routes should 404.
return { paths, fallback: false }
}
추가로 id 값에 대한 게시글의 데이터를 가지고 오기 위해 getStaticProps도 export 해야 한다.
function Post({ post }) {
// Render post...
}
export async function getStaticPaths() {
// ...
}
// This also gets called at build time
export async function getStaticProps({ params }) {
// params contains the post `id`.
// If the route is like /posts/1, then params.id is 1
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
// Pass post data to the page via props
return { props: { post } }
}
getStaticPaths에 관한 추가 정보는 아래의 url에서 확인 가능하다.
https://nextjs.org/docs/basic-features/data-fetching/get-static-paths
Static Generation 사용 시기
Static Generation은 페이지를 빌드하면 CDN으로 제공되므로, 매 요청 시 페이지를 랜더 하는 서버보다 빠르다. 따라서 가능하면 언제나 Static Generation을 사용하는 것이 좋다.
- Marketing pages
- Blog posts and portfolios
- E-commerce product listings
- Help and documentation
위의 예가 SSG를 사용하면 좋은 페이지이다. 그리고 "요청 이전에 pre-render를 할 수 있나?"의 질문에 "할 수 있다"면 SSG를 사용하자. 할 수 없다면, 보통은 요청마다 데이터가 변화하거나 자주 업데이트되는 내용일 것이다. 이러한 경우는 CSR이나 SSR을 사용하면 된다.
- Use Static Generation with Client-side Rendering:전체 화면을 SSG로 구성하고 특정 부분을 CSR로 구성할 수 있다. CSR로 구성된 부분은 pre-rendering을 하지 않고 client-side의 자바스크립트로 화면을 render 한다. 이 부분을 참고 하자. Data Fetching documentation.
- Use Server-Side Rendering: SSG와 다르게 SSR은 속도는 느리지만 pre-render 된 페이지는 요청마다 최신화가 되어 있다. 아래에서 추가적으로 더 알아보자.
Server-side Rendering 구현하기
기본적으로 Server-side Rendering은 SSR이나 Dynamic Rendering이라고도 부른다. SSR을 사용하면 매 요청마다 HTML 페이지가 생성된다. 이러한 SSR을 사용하기 위해서는 매 요청마다 서버에 의해 호출되는 getServerSideProps 함수를 export 하는 것이 필요하다.
예를 들면, 외부 API를 통해 자주 데이터가 변경되어야 하는 페이지에 pre-render가 되어야 한다고 생각해보자. 이럴 경우는 아래와 같이 getServerSideProps를 아래와 같이 적으면 된다.
function Page({ data }) {
// Render data...
}
// This gets called on every request
export async function getServerSideProps() {
// Fetch data from external API
const res = await fetch(`https://.../data`)
const data = await res.json()
// Pass data to the page via props
return { props: { data } }
}
export default Page
As you can see, getServerSideProps is similar to getStaticProps, but the difference is that getServerSideProps is run on every request instead of on build time.
To learn more about how getServerSideProps works, check out our Data Fetching documentation
위의 코드를 보면, getServerSideProps는 getStaticProps와 비슷한 것을 알 수 있다. 하지만 돌아가는 방식을 보면 getServerSideProps는 빌드 시에 요청하는 것이 아닌 매 요청마다 함수가 작동한다.
SSG와 SSR 정리
우리는 위에서 Nextjs에서 사용하는 pre-render 방식인 SSG와 SSR에 대해 알아보았다. SSG(Static site Generation)의 경우에는 빌드 시에 HTML이 생성되고, 매 요청마다 재사용을 한다. getStaticProps와 getStaticPaths를 통해 만들기가 가능하며, 요청 이전에 미리 렌더링 하는 페이지에 적합하다. 추가적인 데이터가 필요한 경우 CSR(Client-side Rendering)을 통해 가져오기(useEffect)가 가능하다.
SSR(Server-side Rendering)의 경우는 매 요청마다 HTML을 생성한다. SSR을 만들기 위해서는 getServerSideProps를 통해 만들기가 가능하다. SSG보다 SSR이 느리기 때문에 필요할 때만 사용하는 것이 좋다.