2020-8-28 NextJS 튜토리얼 정리

NextJS

특징

  • page-based 라우팅 기반으로 동적라우팅을 지원
  • pre-rendering, static-generation, server-side-rendering을 지원

setup

npx create-next-app nextjs-blog --use-npm --example "https://github.com/vercel/next-learn-starter/tree/master/learn-starter"
  • 위의 설정으로 입력하면 nextjs의 기본 뼈대가 생성된다. pages, public, components디렉토리가 생성되는데, pages에 있는 index.js를 찾아서 동적으로 라우팅을 생성하고 만든다.

Pages & Routing

  • pages/posts/first-post.js는 /posts/first-post로 라우팅이 생성되는데, nextjs는 pages에 있는 파일이름을 기반으로 동적 라우팅을 생성한다.

  • export default function FirstPost() {
      return <h1> First Post</h1>
    }
    
    • 컴포넌트의 이름은 아무거나 할 수 있지만 반드시 export default로 export를 해야한다.
    • 컴포넌트 작성시 HTML대신 JSX와 리액트 컴포는트를 사용
  • 일반적으로 HTML에서는 a태그를 이용해서 linking을 하지만 nextjs에서는 Link컴포넌트를 사용한다. Link컴포넌트 사이에 a태그를 감싼다.
  • <a href=’…’> 대신 <Link href=…><a>text</a>형태로 작성해라.
  • Link컴포넌트는 두 페이지사이에서 client-side navigation을 할 수 있도록 해준다.
    • client-side navigation은 빠르게 페이지를 이동? 렌더링할 수 있는 기술이다.
  • nextjs는 코드를 자동적으로 쪼개기때문에 page에 필요한것들만 렌더링한다. 그렇기 때문에 처음에 페이지를 완전히 렌더링하지 않고, 일부 페이지에서 에러가 발생하더라도 어플리케이션은 여전히 동작한다.
  • Link컴포넌트는 브라우저의 viewport에서 보여진다. nextjs가 코드들을 prefetches하기 때문에.

Assets, Metadata, CSS

  • next는 기본적으로 css와 scss를 지원한다.
  • next는 Top-level-directory인 public 디렉토리 아래에서 정적파일들을 가져올 수 있다. 컴포넌트 안에서 src=”/path”에서 public기준으로 경로를 설정하면 가져올 수 있다.
<img src="/vercel.svg" alt="Vercel Logo" className="logo" />
  • public디렉토리에서 robots.txt파일을 사용하여 구글 검색엔진에 최적화를 할 수 있다.

  • <Head>는 <head> 대신에 사용되고 next/head모듈에서 가져올 수 있다.

  • export default function FirstPost() {
      return (
        <>
          <Head>
              <title>First Post</title>
          </Head>
        </>
      )
    }
    
    • Head를 사용하고 title을 정의하면 html에서 알아서 설정해준다.
    • html을 커스텀하고 싶으면 pages/_document.js를 생성해서 속성들을 추가해주면 된다. Custom document를 찾아보면 된다.

CSS styling

  • pages/index.js파일에 <style jsx>코드가 존재한다. 여기서 styled-jsx를 호출하고 nextjs는 styled-jsx 뿐만아니라 styled-components, emotion을 지원한다.

  • components디렉토리를 만들고. 하위에 layout.js와 layout.module.css를 추가.

    • import styles from 'layout.module.css';
      function Layout({children}) {
          return <div className={styles.container}>{children}</div>
      }
      export default Layout;
      
      .container {
          max-width: 36rem;
          padding: 0 1rem;
          margin: 3rem auto 6rem;
      }
      
    • className에 styles.className을 하면 적용된다. HTML을 로드하면 자동으로 unique한 classNames를 생성한다. layout_container_…와 같은 형태로 생성한다.

    • css 코드가 렌더링되는것도 code spliting으로 인해 최소한의 코드만 로드된다.

global styling

  • global css를 적용하기 위해 pages아래에 _app.js파일을 생성한다.

    • // _app.js
      import '../styles/global.css';
      
      export default function App({Component, pageProps}) {
          return <Component {...pageProps} />
      }
      
      
    • App컴포넌트는 top-level component로서 모든 페이지에서 사용된다. App컴포넌트를 이용하여 state를 유지할 수 있다.

    • global css파일들을 _app.js에서만 import를 할 수 있고 다른곳에서는 못한다. global css파일이 _app.js밖에서 import하지 못하기 때문이다.

    • styles디렉토리를 생성하고 하위에 global.css파일을 작성한다. 파일이름은 상관없다.

    • import ‘../styles/global.css’ 코드를 통해 App컴포넌트가 로드될때 css파일이 로드된다.

PostCSS Config

  • nextjs는 postcss를 이용하여 css를 컴파일한다.
  • postcss.config.js를 탑레벨에서 설정하면된다. tailwind를 사용할때 좋다.
  • tailwind를 사용하기 위해 postcss-flexbugs-fixes, postcss-preset-env 모듈을 추가해야한다.
  • postcss.config.js와 tailwind.config.js 파일로 설정

pre-rendering & data fetch

pre-rendering

  • next는 모든페이지를 pre-rendering을 한다. 클라이언트쪽에서 모든것을 JS가 하는것이 아니라 서버측에서 pre-rendering을 한다. 이렇게 하면 SEO최적화와 성능이 잘나온다.
  • 각각의 HTML페이지는 최소한의 필요한 JS코드를 가지고 있다. 페이지가 브라우저에 의해 로드될때, JS코드가 실행되고 완전 반응형으로 페이지를 만든다. 이때 필요해서 JS코드를 최소한만 들고있다.
  • 순수 리액트앱의 경우 pre-rendering을 지원하지 않는다.
  • nextjs는 static generation, server-side-rendering이라는 2가지 pre-rendering이 존재한다.
    • static generation : 빌드할때 html을 생성한다. pre-rendering된 html은 요청이 들어오면 재사용한다. npm run dev를 통해 실행하는것도 static generation방식
    • server-side-rendering : 매번요청이 들어올때마다 html을 만든다.
    • nextjs는 각각의 페이지에 대해 static generation방식과 server-side rendering방식을 사용할 수 있게한다.
  • 한번 빌드하고 CDN에서 사용할 수 있기때문에 static generation방식을 권장한다. 이 방식이 server render방식보다 빠르다.

  • 사용자의 요청전에 페이지를 미리 pre-rendering을 해야되면 static generation방식을 사용하고, 사용자 요청전에 pre-rendering을 할 필요가 없고 요청 뒤에 data를 업데이트 해야되면 serverside rendering을 써라.

  • static generation은 외부 데이터를 fetching하지 않아도 만들고 사용할 수 있고 몇몇 페이지들은 처음 한번은 데이터를 fetching해야되는 경우가 있다. 이 경우도 할 수 있다. 이런 경우, 빌드를 할때 미리 외부데이터를 fetching하면된다.

  • export async function getStaticProps(context) {
      return {
        props: {}, // will be passed to the page component as props
      }
    }
    
    • async getStaticProps를 페이지에 export하면 next는 빌드할때, pre-rendering을 한다. 그리고 props가 리턴된다.
    • context는 params, preview, previewData의 필드를 가지는 객체이다.
      • params : 동적 라우터를 만들때 사용하는 라우터 파라미터를 가지고 있다. getStaticPath를 참조.
      • preview : boolean으로 preview를 허용할지 말지.
      • previewData : setPreviewData로 previewData를 설정할 수 있다.
    • getStaticProps는 props와 revalidate라는 필드를 가지고 있다.
      • props : page컴포넌트에 의해 전달된다. 직렬화된 객체여야 한다.
      • revalidate : optional필드로 몇초후에 페이지를 다시생성할지 결정한다.

static generation

export default function Home({ allPostsData }) {
  return (
    <Layout home>
      <Head>
        <title>{siteTitle}</title>
      </Head>
      <section className={utilStyles.headingMd}>
        <p>[Your Self Introduction]</p>
        <p>
          (This is a sample website - youll be building a site like this in{' '}
          <a href="https://nextjs.org/learn">our Next.js tutorial</a>.)
        </p>
      </section>
    </Layout>
  )
}

export async function getStaticProps() {
    const allPostsData = getSortedPostsData();
    return {
        props: {
            allPostsData
        }
    }
}

  • getStaticProps라는 함수에서는 props를 리턴한다. 이때 props의 필드중에 allPostsData라는 필드를 정의해서 데이터를 넣고, Home컴포넌트에서 allPostsData를 전달한다. 이때 전달받은 allPostData는 setup이 끝난 데이터다.
  • npm run dev로 실행하는 개발환경에서는 getStaticProps가 매 요청마다 동작한다. 반면 production환경에서는 getStaticProps가 빌드타임에 한번만 실행된다.
  • **리액트에서 페이지를 만들때 데이터가 필요하기 때문에 getStaticProps는 페이지 환경에서만 export가 된다. **
  • static generation방식은 사용자의 요청이 있기전에 pre-rendering을 하지 않으면 좋은 방법은 아니다.

server-side rendering

  • 사용자의 데이터가 build time이 아닌 request time에 필요할 경우 static generation방식이 아닌 server-side rendering을 해야한다.

  • server-side rendering 사용하기 위해서는 static generation에서 사용했던 getStaticProps대신 getServerSideProps라는 함수를 export하면 된다.

  • export async function getServerSideProps(context) {
      return {
        props: {
          // props for your component
        }
      }
    }
    
    • getServerSideProps는 request time에서 호출이되고 context라는 파라미터가 request안에있는 특별한 파라미터로 포함되어있다.
    • server-side rendering은 요청이 들어올때마다 계산을 해야되서, getStaticProps보다 느리다. 그리고 추가적인 설명이 없으면 결과를 CDN에 캐싱할 수 없다.

Dynamic Routes

  • Nextjs에서는 외부데이터에 의존해서 page path를 생성할 수 있다. 이를 dynamic url라고 한다.

  • /post/<id>로 접근하기 위해서는 pages/posts/[id]로 생성해야한다. nextjs에서 []는 다이나믹 라우트를 나타낸다.

    • ex) pages/posts/[id].js
  • getStaticPaths를 호출해서, pages에서 동적라우트를 생성한다. id로 가능한 값들의 리스트를 리턴해야한다.

  • import Layout from '../../components/layout'
    
    export default function Post() {
      return <Layout>...</Layout>
    }
    
    export async function getStaticPaths() {
      // Return a list of possible value for id
    }
    
    export async function getStaticProps({ params }) {
      // Fetch necessary data for the blog post using params.id
    }
    
    • 최종적으로 getStaticProps는 params에 id를 가지고 있기때문에 getStaticProps를 호출해야한다.

Static Generate방식으로 어떻게 Dynamic Routes가 생성되는가

  • 만약 /posts/<id>를 호출하게 되면, /pages/post/[id].js파일이 생성된다.
    • 렌더링 되기 위해 리액트 반드시필요
    • getStaticPaths() 함수를 정의한다. 이때 되도록이면 Object의 리스트 형태로 리턴해야한다.
    • getStaticProps()를 정의한다. 여기서 필요한 정보들을 fetch해야되기 때문

API Routes

  • pages/api디렉토리 하위에서 api 라우팅을 경의할 수 있다.

  • /api/hello 요청을 받기 위해서는 pages/api/hello.js를 생성하고 내부적으로 req, res를 받는 함수를 구현

    • export default (req, res) => {
        res.status(200).json({ text: 'Hello' })
      }
      
  • getStaticProps, getStaticPaths에서 api를 fetch하면 안된다. getStaticProps와 getStaticPaths의 경우 서버사이드에서만 동작하기 때문에 클라이언트쪽에서 동작하지 않는다.

느낀점

  • 리액트를 아주살짝만 해봤기때문에 NextJS가 ReactJS 프레임워크라고 했을때 뭐가 좋은지에 대해서는 잘 몰랐다. 근근히 쓰면서 느끼는건 그래도 안에 내장되있는 기능들과. 확실히 체계가 잡혀있다? 예를들면 pages/route/index.js파일을 만들면, /route에 대해서 요청하면 index.js를 찾아서 동적으로 라우터를 생성하고. 렌더링해주는것만으로도 이미 편하다. 이번에 프로젝트하면서 잘 배워서 다음번 프로젝트할때는 프론트를 좀 더 자유자재로 다루고 싶다.
Written on August 28, 2020