해당 글은 도리가 작성했습니다. 🐠
프론트엔드 팀에서는 Next.js를 사용하기로 함에 따라 다양한 Rendering 방식을 알아야 할 필요성을 느꼈고, 따라서 공식문서를 통해 알아보았습니다.
기본적으로 Next.js는 모든 페이지를 pre-rendering 합니다.
이 둘의 가장 큰 차이점으로는 페이지의 HTML이 생성되는 방식에서 차이가 있습니다.
Static Generation
: HTML은 build-time에 생성되며 모든 요청에서 미리 생성된 HTML을 재사용함. (Next.js에서 권장하는 방법)Server-side Rendering
: HTML은 각각의 요청마다 생성함. 즉, 재사용은 하지 않음.Next.js는 각 페이지에 사용하고자 하는 pre-rendering 방식을 선택할 수 있게 해주기에, 더 나아가 대부분의 페이지에서는 Static Generation
을 사용하고 다른 페이지에서는 Server-side Rendering
을 이용하여 Hybrid 하게 만들 수도 있습니다.
Next.js에서는 성능상의 이유로
Server-side Rendering
방식보다Static Generation
방식을 사용하는 것을 추천합니다.
=> 성능 향상을 위한 추가 구성없이 CDN에서 캐싱을 하기 때문
(이를 통해 미리 생성해 놓은 HTML을 다시 재사용 가능)
페이지에서 server-side rendering을 사용하는 경우 각 요청마다 페이지 HTML이 생성됩니다. 페이지에 대해 server-side rendering을 사용하려면 getServerSideProps
라는 비동기 함수를 내보내야 하며, 이 함수는 모든 요청 시 서버에서 호출됩니다.
예를 들어 페이지에서 자주 업데이트되는 데이터(외부 API에서 가져옴)를 사전 렌더링해야 한다고 가정하면, 이 데이터를 가져와 페이지로 전달하는 getServerSideProps
를 다음과 같이 작성할 수 있습니다.
export default 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 } }
}
server-side rendering method인 getStaticProps
와 getServerSideProps
대신 hook을 이용하는 방법으로 다음과 같이 작성할 수 있습니다.
import React, { useState, useEffect } from 'react'
export function Page() {
const [data, setData] = useState(null)
useEffect(() => {
const fetchData = async () => {
const response = await fetch('https://api.example.com/data')
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const result = await response.json()
setData(result)
}
fetchData().catch((e) => {
// handle the error as needed
console.error('An error occurred while fetching the data: ', e)
})
}, [])
return <p>{data ? `Your data: ${data}` : 'Loading...'}</p>
}
위의 방법은 기존의 React Hooks를 사용하는 형태로, 더 나은 성능이나 캐싱, 업데이트 등을 위해서는 data-fetching library를 사용하는 것이 좋습니다.
data fetching library를 통해 클라이언트에서 데이터를 가져오는 것으로 Next.js에서 권장하는 방법입니다.
import useSWR from 'swr'
const fetcher = async (url) => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
return result
};
export function Page() {
const { data, error, isLoading } = useSWR(
'https://api.example.com/data',
fetcher
)
if (error) return <p>Failed to load.</p>
if (isLoading) return <p>Loading...</p>
return <p>Your Data: {data}</p>
}
import { useQuery } from 'react-query'
const fetchData = async () => {
const response = await fetch('https://api.example.com/data')
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const result = await response.json()
return result
}
export function Page() {
const { data, error, isLoading } = useQuery('data', fetchData)
if (error) return <p>Failed to load.</p>
if (isLoading) return <p>Loading...</p>
return <p>{data ? `Your data: ${data}` : 'No data available'}</p>
}
Static Generation을 사용한다면, 페이지의 HTML은 build-time(빌드 시점)에 생성됩니다. 즉, 페이지의 HTML이 next build
를 실행할 때 생성된다는 의미입니다.
생성된 HTML은 각각의 모든 요청(request)에서 재사용될 것이고, CDN에 의해 캐싱될 수도 있습니다. 생성되는 페이지에서는 데이터 포함 여부에 따라 방식이 달라집니다.
function About() {
return <div>About</div>
}
export default About
이 페이지는 pre-rendering 하기 위해 외부 데이터를 가져올 필요가 없습니다. 이와 같은 경우 build-time 동안 페이지당 단일 HTML 파일을 생성합니다.
일부 페이지에서 pre-rendering을 위해 외부 데이터를 가져와야 할 때 두 가지 시나리오가 있습니다.
getStaticProps
getStaticPaths
getStaticProps
와 함께 사용예시로, blog page는 CMS(Content Management System)로부터 blog post 리스트를 가져와야 할 때
// TODO: Need to fetch `posts` (by calling some API endpoint)
// before this page can be pre-rendered.
export default function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
)
}
Next.js는 이 데이터를 pre-rendering 하기 위해 동일한 파일에서 getStaticProps
함수를 비동기 방식으로 export
하여 사용합니다. 이 함수는 build-time 시 호출되며, pre-rendering 시 가져온 데이터를 page의 contents로 전달할 수 있습니다.
export default 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,
},
}
}
Next.js는 Dynamic Routing을 통해 페이지를 생성합니다. 예를 들어, pages/posts/[id.js]
라는 파일을 만들어 id
를 기반으로 하나의 블로그 게시물을 보여줄 때 posts/1
에 접속하면 id:1
의 블로그 게시물을 보여줄 수 있는 겁니다.
하지만 id 값은 pre-rendering 시 외부 데이터에 따라 달라질 수 있습니다. 이를 처리하기 위해 동적 페이지(pages/posts/[id].js
)로 부터 getStaticPaths
함수를 비동기 방식으로 사용하는데, 이 함수는 build-time에 실행되며 pre-rendering을 위해 원하는 path를 지정할 수 있습니다.
// 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 }
}
또한 page/posts/[id.js]
에서도 getStaticProps
를 export
하면 이 id
로 게시물에 대한 데이터를 가져와 페이지를 pre-rendering 하는 데 사용할 수 있습니다.
export default 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 } }
}
Next.js 공식 문서에서는 Static Generation(with or without data)을 사용하는 것을 가능한 권장합니다. build-time에 생성된 HTML을 CDN 캐시를 통해 제공하기 때문에, 각 요청에 따라 Server-side rendering 하므로 더 빠른 성능을 보여줄 수 있기 때문입니다.
예를 들어 이와 같은 페이지에서 사용됩니다.
💡 스스로에게 "사용자의 요청에 앞서 이 페이지를 pre-rendering해도 되는가?"를 물어봤을 때, 대답이 "예"라면 Static Generation을 사용하면 됩니다.
반면, 사용자가 요청하는 데이터를 보여주어야 하는 경우에는 적합하지 않습니다. 페이지에 따라 데이터가 자주 업데이트 되는 경우 매 순간 페이지의 content가 변경될 수 있기 때문입니다.
content
를 채울 수 있습니다.보시다시피 getServerSideProps
는 getStaticProps
와 유사하지만 getServerSideProps
는 빌드 시간이 아닌 모든 요청에 대해 실행된다는 점이 다릅니다.
💡 서버에서 pre-rendering 하는 것까지가 Next.js의 특징이고,
pre-rendering을 동적으로 페이지를 생성하느냐, 정적으로 페이지를 생성하느냐의 차이가 SSR과 SSG의 차이라고 생각하면 됩니다.