미주알고주알

Next.js의 서버 사이드 렌더링 기술 3가지 본문

Next.js

Next.js의 서버 사이드 렌더링 기술 3가지

미주알고주알 2023. 2. 16. 22:25

 

Next.js에서는 SSR(Server-Side Rendering), SSG(Static-Side Generation), ISR(Incremental Static Regeneration) 세 가지 서버 사이드 렌더링 기술을 제공한다.

 

Next.js는 React의 프레임워크인 만큼 기본적으로 CSR(Client-Side Rendering) 기반으로 브라우저에 웹페이지를 표시한다. 여기서 CSR, 클라이언트 사이트 렌더링이란 서버에서 HTML을 제공하면 클라이언트에서 Javascript를 사용하여 페이지를 동적으로 렌더링하는 기술을 의미한다. 즉, 클라이언트에서는 서버로부터 HTML, CSS 및 Javascript를 다운로드하고, 이후에 Javascript를 사용하여 브라우저에서 동적으로 페이지를 렌더링한다. 

 

하지만 때에 따라 클라이언트에 도달하기 이전에 서버에서 먼저 HTML, CSS 및 Javascript를 생성하고 클라이언트는 그 데이터를 그대로 다운받아 실행하게 함으로써 초기 로딩 시간은 줄인 채 검색 엔진 최적화(SEO)를 구현하고 싶을 때 서버 사이트 렌더링의 방법을 선택할 수 있다.

 

Next.js를 통해 CSR과 SSR을 조합해 최상의 사용자 경험과 SEO 최적화를 제공할 수 있게 된다.

 

다시 Next.js의 서버 사이드 렌더링 기술 3가지 얘기로 돌아가서... Next.js에서 말하는 데이터 요청 방법을 보면,

 

https://nextjs.org/docs/basic-features/data-fetching/overview

Data fetching in Next.js allows you to render your content in different ways, depending on your application's use case. These include pre-rendering with Server-side Rendering or Static Generation, and updating or creating content at runtime with Incremental Static Regeneration.

여기서 pre-rendering은 쉽게 말해, SSR이다. 초기 로드 시 서버에서 미리 렌더된(pre-rendered) HTML 마크업이 브라우저에 그려진 후, Javascript가 로드되어 리액트 컴포넌트가 초기화되고 앱이 인터랙티브해 질 수 있다는 것. 이는 초기 로드 시 앱 자체가 랜더되지 않고 Javascript가 로드된 후에야 리액트 컴포넌트가 초기화되는 CSR과는 반대되는 개념이다. 나도 이해하기 복잡해서 계속 같은 단어를 반복해서 설명하는 듯 하지만 이렇게 해서라도 결국에 이해하면 되는 거니까 그냥 계속 적을란다 😵😵😵

 

1. SSR(Server Side Rendering)

서버 사이드 렌더링은 서버 측에서 페이지의 HTML 마크업을 생성하는 방법이다. 즉, 클라이언트 요청이 있을 때마다 서버에서 페이지를 렌더링하여 HTML을 생성하고 이를 클라이언트를 전송한다. SSR을 사용하면 페이지의 초기 로딩 속도를 높일 수 있으며, SEO 최적화에도 유리하다. 그러나 서버에서 페이지를 렌더링하는 데 시간이 더 걸리고, 클라이언트의 매 요청마다 응답하기 때문에 서버 부하가 증가할 수 있다.

// SSR

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
  // 👉🏻 리턴하는 데이터를 페이지의 props로 전달한다.
  return { props: { data } }
}

export default Page

// CSR

function Page2() {
  const [data, setData] = useState({})
	
  useEffect(() => {
    (async() => {
      const res = await fetch(`https://.../data`)
      const json = await res.json();
      setData(json)
    })()  
    }, [])
    
  return ... 
}

 

2. SSG(Static Site Generation)

정적 사이트 생성은 서버 측에서 미리 페이지를 렌더링하여 HTML 파일을 생성하는 방법이다. 이 방법을 사용하면 클라이언트 요청 시 서버에서 페이지를 매번 생성할 필요가 없으므로 서버 부하가 적고, 페이지 로딩 속도가 빠르다. 이 방법은 페이지가 자주 변경되지 않는 경우, 예를 들어어 블록 게시물과 같은 경우에 적합하다.

// posts will be populated at build time by getStaticProps()
// 👉🏻 getStaticProps에 의해 빌드 타임에 생성된 props(posts)를 받아 해당 JSX을 렌더링한다. 
function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li>{post.title}</li>
      ))}
    </ul>
  )
}

// This function gets called at build time on server-side.
// It won't be called on client-side, so you can even do
// direct database queries.
// 👉🏻 해당 함수는 빌드 타임에 서버에서 실행되기 때문에 db 요청을 직접적으로 작성할 수 있다.
export async function getStaticProps() {
  // Call an external API endpoint to get posts.
  // You can use any data fetching library
  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
// pages/posts/[id].js

// Generates `/posts/1` and `/posts/2`
// 👉🏻 slug와 같은 query 요청을 포함한 SSG에 대해 그 url 경로를 미리 그려놓을 수 있다. 
export async function getStaticPaths() {
  return {
    paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
    fallback: false, // can also be true or 'blocking'
  }
}

// `getStaticPaths` requires using `getStaticProps`
// 👉🏻 `getStaticProps`와 `getStaticPaths`은 👩‍❤️‍👩 세트!
export async function getStaticProps(context) {
  return {
    // Passed to the page component as props
    props: { post: {} },
  }
}

export default function Post({ post }) {
  // Render post...
}
export async function getStaticPaths() {
  // When this is true (in preview environments) don't
  // prerender any static pages
  // (faster builds, but slower initial page load)
  if (process.env.SKIP_BUILD_STATIC_GENERATION) {
    return {
      paths: [],
      fallback: 'blocking',
    }
  }

  // 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 prerender based on posts
  // In production environments, prerender all pages
  // (slower builds, but faster initial page load)
  const paths = posts.map((post) => ({
    params: { id: post.id },
  }))

  // { fallback: false } means other routes should 404
  return { paths, fallback: false }
}

 

3. ISR(Incremental Static Regeneration)

증분 정적 생성은 SSG와 비슷하지만, 런타임에 일부 페이지를 동적으로 렌더링하여 서버측에서 캐싱하고 일정 시간이 지난 후에 갱신하는 방법이다. 이 방법을 사용하면 정적 사이트 생성의 이점을 유지하면서도 동적으로 데이터를 업데이트할 수 있다. 이 방법은 뉴스 사이트, 전자상거래 사이트와 같이 데이터가 자주 갱신되는 경우에 적합하다.

function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// revalidation is enabled and a new request comes in
// 👉🏻 SSG의 연장선으로 빌드 타임에 서버에서 실행되지만 revalidation을 추가적으로 요청할 경우 일정 기간 간격으로 새로 실행해 데이터를 갱신할 수 있다.
// 👉🏻 SWR의 revalidate를 생각하면 쉽게 이해가 될지도?!
export async function getStaticProps() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  return {
    props: {
      posts,
    },
    // Next.js will attempt to re-generate the page:
    // - When a request comes in
    // - At most once every 10 seconds
    revalidate: 10, // In seconds
    // 👉 revalidation 설정은 위와 같이.
  }
}

// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// the path has not been generated.
// 👉 경로 역시 빌드 타임 이후에도 일정 시간 간격으로 갱신될 수 있음.
export async function getStaticPaths() {
  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: blocking } will server-render pages
  // on-demand if the path doesn't exist.
  return { paths, fallback: 'blocking' }
}

export default Blog

 

Next.js에서는 이러한 서버 사이드 렌더링 방법을 사용하여 더 나은 성능과 검색 엔진 최적화를 제공하며, `getSeverSideProps`, `getStaticProps`, `getStaticPaths`, `revalidate`와 같은 API를 제공하여 각각 SSR, SSG, ISR에 대한 구현을 간편하게 할 수 있도록 지원하고 있다.