본문으로 건너뛰기

Next.js

무료2020-12-03#Front-End#Solution#SSG#Nextjs#Next SSR#Next SSG#React预渲染

완벽한 정적 렌더링/서버 사이드 렌더링 지원으로 Next.js 는 React 생태계에서 독보적인 존재입니다

一.Next.js 소개

The React Framework for Production

프로덕션용 React 프레임워크 (당연한 말). 정적 렌더링/서버 사이드 렌더링 혼용, TypeScript 지원, 번들 최적화, 라우트별 프리로드 등 많은 기능을 출고 시부터 제공합니다:

Next.js gives you the best developer experience with all the features you need for production: hybrid static & server rendering, TypeScript support, smart bundling, route pre-fetching, and more. No config needed.

그 중에서도 완벽한 정적 렌더링/서버 사이드 렌더링 지원으로 Next.js 는 React 생태계에서 독보적인 존재입니다

二。핵심 기능

Next.js 가 하는 일이 하나뿐이라고 한다면, 그것은 프리렌더링 (Pre-rendering) 입니다:

By default, Next.js pre-renders every page. This means that Next.js generates HTML for each page in advance, instead of having it all done by client-side JavaScript.

구체적으로 프리렌더링에는 두 가지 방식이 있습니다:

  • SSG(Static Site Generation): Static Generation 이라고도 하며, 컴파일 시점에 정적 HTML 생성

  • SSR(Server-Side Rendering): Server Rendering 이라고도 하며, 사용자 요청이 도착했을 때 동적으로 HTML 생성

SSR 과 비교하여 Next.js 가 더 권장하는 것은 SSG 입니다. 성능상의 이점이 더 크기 때문입니다 (정적 콘텐츠는 CDN 에 호스팅할 수 있어 성능 향상이 극적입니다). 따라서 SSG 를 우선적으로 고려하고, SSG 로 충족할 수 없는 경우 (컴파일 시점에 정적 생성할 수 없는 개인화된 콘텐츠 등) 에만 SSR, CSR 을 고려하는 것이 좋습니다

P.S.CSR, SSR 등 기타 렌더링 모드는 前端渲染模式的探索 참조

핵심 프리렌더링 기능을 중심으로 일련의 관련 지원이 파생되었습니다:

  • 라우팅 (파일 규약, API): 다중 페이지의 기초
  • 페이지 레벨 프리렌더링, 코드 분할: 당연한 흐름
  • 증분 정적 생성: 대량 페이지용 컴파일 시점 프리렌더링 (즉 정적 생성) 전략
  • 라우트별 프리로드:锦上添花
  • 국제화 (라우팅과 연동):锦上添花
  • Serverless 함수 통합:锦上添花
  • 자동 polyfill, 커스텀head태그:덤

또한 일반적인 시나리오 지원도 제공합니다:

  • 출고 시 사용 가능 (설정 불필요)
  • TypeScript
  • CSS module, Sass
  • Fast Refresh(신뢰성 높은 Hot Reload 지원)
  • 사용자 실제 데이터 수집 및 분석 (페이지 로딩 성능, 경험 점수 등)
  • 기본 최적화 적용Image컴포넌트

三。라우팅 지원

Next.js 는 두 가지 라우팅 지원을 제공합니다: 정적 라우트와 동적 라우트

정적 라우트

정적 라우트는 파일 규약으로 정의됩니다.pages디렉토리 아래의js파일은 모두 라우트로 간주됩니다 (각 정적 라우트는 하나의 페이지 파일에 해당). 예:

pages/index.js → /
pages/blog/index.js → /blog
pages/blog/first-post.js → /blog/first-post
pages/dashboard/settings/username.js → /dashboard/settings/username

동적 라우트

마찬가지로 동적 라우트도pages디렉토리 아래에 파일을 생성하지만 파일명이 조금 다릅니다:

pages/blog/[slug].js → /blog/:slug (/blog/hello-world)
pages/[username]/settings.js → /:username/settings (/foo/settings)
pages/post/[...all].js → /post/* (/post/2020/id/title)

경로에서 변화하는 파라미터는getStaticPaths로 채웁니다:

// pages/posts/[id].js
export async function getStaticPaths() {
  return {
    // 반드시 paths 여야 하며 값은 배열이어야 함
    paths: [{
      // 각 항목은 이 형식이어야 함
      params: {
        // id 를 포함해야 함
        id: 'ssg-ssr'
      }
    },{
      params: {
        id: 'pre-rendering'
      }
    }],
    fallback: false
  }
}

이어서getStaticProps에 전달하여 파라미터별로 데이터를 가져오고 페이지를 렌더링합니다:

// pages/posts/[id].js
export async function getStaticProps({ params }) {
  // 라우트 파라미터에 따라 해당 데이터 가져오기
  const postData = await getPostData(params.id)
  return {
    props: {
      postData
    }
  }
}

// 페이지 렌더링
export default function Post({ postData }) {
  return (
    <Layout>
      <Head>
        <title>{postData.title}</title>
      </Head>
      <article>
        <h1 className={utilStyles.headingXl}>{postData.title}</h1>
        <div className={utilStyles.lightText}>
          <Date dateString={postData.date} />
        </div>
        <div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} />
      </article>
    </Layout>
  )
}

먼저 팩토리 page(예:pages/[라우트 파라미터 1]/[라우트 파라미터 2].js) 를 생성하고, 다음으로getStaticPaths가 라우트 파라미터를 채우고, getStaticProps({ params })가 파라미터에 따라 다른 데이터를 요청하고, 마지막으로 데이터가 페이지 컴포넌트로 들어가 프리렌더링을 시작한다고 이해할 수 있습니다:

四.SSG 지원

가장 간단하면서 동시에 성능도 최적의 프리렌더링 방식은 정적 생성 (SSG) 으로, 컴포넌트 렌더링 작업을 완전히 컴파일 시점으로 앞당깁니다:

  1. (컴파일 시점) 데이터 가져오기
  2. (컴파일 시점) 컴포넌트 렌더링, HTML 생성

생성된 HTML 정적 리소스를 웹 서버나 CDN 에 호스팅하면 React 엔지니어링의 장점과 Web 의 극한 성능을 모두 확보할 수 있습니다

먼저 데이터 가져오기 문제를 해결해야 합니다. Next.js 의 접근 방식은 페이지가 의존하는 데이터를 집중적으로 관리하는 것입니다:

// pages/index.js
export default function Home(props) { ... }

// 정적 데이터 가져오기
export async function getStaticProps() {
  // Get external data from the file system, API, DB, etc.
  const data = ...

  // The value of the `props` key will be
  //  passed to the `Home` component
  return {
    props: ...
  }
}

여기서**getStaticProps는 서버 측에서만 실행됩니다 (클라이언트 번들에 들어가지 않습니다)**. 반환된 정적 데이터는 페이지 컴포넌트 (위 예시의Home) 에 전달됩니다. 즉, getStaticProps를 통해 페이지가 의존하는 모든 데이터를 미리 준비해야 하며, 데이터가 준비된 후에야 컴포넌트 렌더링이 시작되고 HTML 이 생성됩니다

P.S.주의: 페이지만이getStaticProps를 통해 데이터 의존을 선언할 수 있으며 일반 컴포넌트는 허용되지 않습니다. 따라서 페이지 전체가 의존하는 모든 데이터를 한 곳에 모아야 합니다

HTML 을 생성하여 렌더링하는 부분은 React 가 제공하는 SSR API 를 사용하여 완료할 수 있습니다

이로써 의존하는 데이터를 미리 가져올 수 있는 페이지라면 이론상 모두 정적 HTML 로 컴파일할 수 있지만, 2 가지 문제도随之에 발생합니다:

  • 데이터가 변할 수 있어 이미 생성된 정적 페이지를 업데이트해야 함

  • 데이터량이"영원히"컴파일할 수 없을 정도로 많을 수 있음

이커머스 페이지를 예로 들면, 방대한 상품 데이터를 모두 정적 페이지로 컴파일하는 것은 거의 불가능합니다 (아마 1 세기 정도 걸릴 것입니다). 모두 생성한다 해도 상품 정보는 수시로 업데이트되므로 정적 페이지를 다시 생성해야 합니다:

If your app has a very large number of static pages that depend on data (think: a very large e-commerce site). You want to pre-render all product pages, but then your builds would take forever.

따라서증분 정적 재생성(Incremental Static Regeneration) 이 탄생했습니다

ISR 지원

컴파일 시점에 열거할 수 없는 대량의 페이지나 업데이트가 필요한 시나리오에 대해 Next.js 는 런타임에서 재생성을 허용합니다 (런타임 정적화에 해당):

Incremental Static Regeneration allows you to update existing pages by re-rendering them in the background as traffic comes in.

예:

export async function getStaticProps() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  return {
    props: {
      posts,
    },
    // 유효 기간 설정, ISR 활성화
    revalidate: 1, // 초 단위
  }
}

revalidate: 1은 런타임 (사용자 요청이 도착했을 때) 에 정적 HTML 을 다시 생성하려고 시도하며, 1 초에 최대 한 번 다시 생성함을 의미합니다

런타임에서 정적 생성에는 시간이 걸립니다 (사용자 요청이 HTML 을 기다림). 이 과정에서 3 가지 선택이 있습니다:

  • fallback: false: 폴백 없음, 아직 생성되지 않은 정적 페이지에ヒット하는 라우트는 직접 404

  • fallback: true: 폴백 있음, 아직 생성되지 않은 정적 페이지에ヒット하는 라우트는 먼저 폴백 페이지 반환 (이때props는 비어 있으며 일반적으로 loading 표시). 정적 HTML 을 생성하는 동시에 폴백 페이지가 CSR 에서 사용할 JSON 도 생성되며, 완료 후 브라우저가 데이터를 가져와 (클라이언트 측에서props채움), 완전한 페이지 렌더링

  • fallback: 'blocking': 폴백 없음,かつ 사용자 요청이 새 페이지의 정적 생성이 완료될 때까지 계속 대기 (실제로는 SSR 이며 렌더링 프로세스는 블로킹이지만 완료 후 결과 HTML 을 유지)

즉, 라우팅 (getStaticPaths) 과 연동하여 아직 생성되지 않은 페이지에 대해 폴백을 수행합니다. 예:

// pages/index.js
import { useRouter } from 'next/router'

function Post({ post }) {
  const router = useRouter()

  // 폴백 페이지 렌더링
  if (router.isFallback) {
    return <div>Loading...</div>
  }

  // Render post...
}

export async function getStaticPaths() {
  return {
    paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
    // (페이지 레벨) 폴백 전략.true 는 아직 생성되지 않은 페이지에遭遇하면 폴백 페이지를 제공하고 생성 완료 후 클라이언트가 자동으로 업데이트됨을 의미
    fallback: true,
  }
}

P.S.자세한 내용은 Incremental Static RegenerationThe fallback key 참조

그러나모든 시나리오가 컴파일 시점에 정적 생성을 편안하게 수행할 수 있는 것은 아닙니다. 전형적으로 컴포넌트가 의존하는 데이터가 동적인 경우 컴파일 시점에 미리 데이터를 가져올 수 없으므로 정적 생성은 불가능해집니다

五.SSR 지원

컴파일 시점에 정적 페이지를 생성할 수 없는 시나리오에서는 SSR 을 고려할 수밖에 없습니다:

SSG 의getStaticProps와 달리 Next.js 는 SSR 전용getServerSideProps(context)를 제공합니다:

// pages/index.js
export async function getServerSideProps(context) {
  const res = await fetch(`https://...`)
  const data = await res.json()

  if (!data) {
    return {
      notFound: true,
    }
  }

  return {
    props: {}, // 페이지 컴포넌트에 props 로 전달됨
  }
}

마찬가지로 데이터 가져오기에 사용되지만getStaticProps와의 가장 큰 차이는요청이 올 때마다 실행되므로 요청 컨텍스트 파라미터 (context) 를 가져올 수 있다는 점입니다

P.S.자세한 내용은 getServerSideProps (Server-side Rendering) 참조

六。정리

프리렌더링에서 데이터를 가져오는 방법을 중심으로 Next.js 는 독특한 라우팅 지원과 정교한 SSG, SSR 지원을 탐색했습니다.不仅如此, Next.js 는 두 가지를 모두 얻을 수 있는 혼용 지원도 제공합니다. 서로 다른 렌더링 모드를 결합하면 얼마나 강력해지는지는 다음 편에서 설명하겠습니다

참고 자료

댓글

아직 댓글이 없습니다

댓글 작성