Next.js 공식 Docs 흝기

Felix Yi·2020년 3월 20일
36

필요한 부분만 스윽. 봐도 전체를 다 알기는 어렵다.

추가 공부 필요

Getting Started

npm install next react react-dom
"scripts": {
  "dev": "next",
  "build": "next build", // 빌드 for production 
  "start": "next start" // run production server
}

Basic Features

Pages

기본

파일라우팅 : 주소매핑

js, ts, tsx 다 됨

/pages/index.js : /
/pages/Banana.ts : /Banana
/pages/sub/Lanana.tsx : /sub/Lanana
/pages/dyna/[dynamic].js: /dyna/동적Path받기 // 동적으로 path 받을 수 있음

기본, 모든 페이지를 렌더. CSR 이 모든 자바스크립트를 가져야 실행되므로 그보다 빠름.

종류는 다음 두 가지. 이건 요청 때마다 새로운 데이터가 매번 필요하나 아니냐에 따라 결정.

  • 정적생성은 한 번 생성 후 계속 사용.
  • SSR은 요청 때마다 생성

정적생성

데이터 없는 정적생성은 그냥 생성.
데이터 있는 정적생성은 getStaticProps, getStaticPaths 등 사용.

처음에 필요한 자료를 빌드 때 다 불러와서 넣어놓고 그것만 사용.

SSR

getServerSideProps 하면. 매 요청 당 실행됨.

Data fetching

  • getStaticProps (Static Generation): Fetch data at build time.
  • getStaticPaths (Static Generation): Specify dynamic routes to pre-render based on data.
  • getServerSideProps (Server-side Rendering): Fetch data on each request.

getStaticProps

export async function getStaticProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  }
}

파일구조: url 입력
/pages/dyna/[dynamic].js: /dyna/동적인값
담기는 형식
context.dynamic : '동적인값

  • param : 위 설명
  • preview: 나중에
  • previewData: 나중에

getStaticPaths (Static Generation)

빌드 타임 때 정적으로 렌더링할 경로 설정.

여기서 정의하지 않은 하위 경로는 접근해도 화면이 안 뜸.

동적라우팅 할 때, 라우팅 되는 경우의 수를 하나하나 집어넣을 것.

/pages/dyna/[dynamic].js: /dyna/동적인값

// This function gets called at build time
export async function getStaticPaths() {
  return {
    //빌드 타임 때 아래 정의한  /dyna/1,  /dyna/2, ... /dyna/동적인값 경로만 pre렌더링.
    paths: [
      { params: { dynamic: 1 } },
      { params: { dynmic: 2 } }
      ......
      { params: { dynmic: 동적인값 } }
    ],
    // 만들어지지 않은 것도 추후 요청이 들어오면 만들어 줄 지 여부.
    fallback: true,
  }
}

getServerSideProps (Server-side Rendering)

export async function getServerSideProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  }
}

context 하위 키.

  • params: 이전에 나온 내용과 동일
  • req: The HTTP request object.
  • res: The HTTP response object.
  • query: The query string.
  • preview: 나중에
  • previewData: 나중에

Fetching data on the client side

빈번하게 자료 업데이트가 필요하면 pre-render 하지 않아도 됨, 클라이언트 사이드에서 해.

  • 일부 정적 pre-render 그러나 자료 없이 보여주고, 로딩 상태 보여주기
  • 그 다음에 클라측에서 자료 fetch 후 준비되면 보여주기.

대시보드의 경우 Private 하기 땜시 잘 통함. SEO 필요 없어서.

SWR

SWR 이라는 데이터 fetching 훅 사용 강추.

import useSWR from 'swr'

function Profile() {
  const { data, error } = useSWR('/api/user', fetch)

  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>
  return <div>hello {data.name}!</div>
}

Built-in CSS Support

글로벌 스타일 시트 추가

pages/_app.js. 요게 기본 app 설정을 override 하는 커스텀 app 페이지다. 여기다가 import 하면 전역으로 다 import 가 적용됨. 프로덕션 빌드 때는 하나의 .css 파일로 미니파이드됨.

여기 임포트 한 건 다른곳에서 넣지 말 것. 충돌남.

styles.css:

 body {
  font-family: 'SF Pro Text', 'SF Pro Icons', 'Helvetica Neue', 'Helvetica',
    'Arial', sans-serif;
  padding: 20px 20px 60px;
  max-width: 680px;
  margin: 0 auto;
}
 import '../styles.css'

// This default export is required in a new `pages/_app.js` file.
export default function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

컴포넌트 레벨 CSS 추가

CSS Modules using the [name].module.css file naming convention.

로컬 스콥 CSS 적용될 유닉클래스 이름 만들어 주기 때문에 어느 컴포넌트에서나 충돌 걱정 없이 로드할 수 있음.

Regular stylesheets and global CSS files are still supported.

components/Button.module.css

/*
You do not need to worry about .error {} colliding with any other `.css` or
`.module.css` files!
*/
.error {
  color: white;
  background-color: red;
}
import styles from './Button.module.css'

export function Button() {
  return (
    <button
      type="button"
      // Note how the "error" class is accessed as a property on the imported
      // `styles` object.
      className={styles.error}
    >
      Destroy
    </button>
  )
}

CSS-in-JS

CSS-in-JS 솔루션 사용 가능함.

function HiThere() {
  return <p style={{ color: 'red' }}>hi there</p>
}

export default HiThere

그리고 넥스트 팀이 번들링 한 scoped CSS 위해 styled-css 란 걸 이용가능.

function HelloWorld() {
  return (
    <div>
      Hello world
      <p>scoped!</p>
      <style jsx>{`
        p {
          color: blue;
        }
        div {
          background: red;
        }
        @media (max-width: 600px) {
          div {
            background: blue;
          }
        }
      `}</style>
      <style global jsx>{`
        body {
          background: black;
        }
      `}</style>
    </div>
  )
}

export default HelloWorld

Sass Support

.scss and .sass .module.scss or .module.sass 지원.

npm install sass

Less and Stylus Support

플로그인 설치
@zeit/next-less
@zeit/next-stylus

Static File Serving

public 폴더에 넣으면 / 주소로 접근 가능.

public/my-image.png

function MyImage() {
  return <img src="/my-image.png" alt="my image" />
}

export default MyImage

TypeScript

투입된 곳에서 안 씀 건너뛰

Routing

Introduction

인덱스 라우팅

  • 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/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)

패이지 끼리 링킹

<Link> 를 사용하면 클라이언트 사이드 라우팅이 됨.

  • href -pages 디렉토리 내부에 페이지 이름. 예 /blog/[slug].
  • as - 브라우저에서 보여질 주소. 예 /blog/hello-world.
import Link from 'next/link'

function Home() {
  return (
    <ul>
      <li>
        <Link href="/blog/[slug]" as="/blog/hello-world">
          <a>To Hello World Blog post</a>
        </Link>
      </li>
    </ul>
  )
}

export default Home

라우터 주입

To access the router object in a React component you can use useRouter or withRouter.

Dynamic Routes

pages/post/[pid].js

import { useRouter } from 'next/router'

const Post = () => {
  const router = useRouter()
  const { pid } = router.query

  return <p>Post: {pid}</p>
}

export default Post

파일 경로가 pages/post/[pid].js 일 때

/post/abc: { "pid": "abc" }
/post/abc?foo=bar : { "foo": "bar", "pid": "abc" }

근데 다이나믹 파일 명(pid)을 쿼리스트링에 써버리면 override 되면서 쿼리파라미터만 남음
/post/abc?pid=123: { "pid": "abc" }

모든 라우트 캐치

삼점으로 모든 경로 캐치 가능.

/docs/모든url 캐치하려면
pages/docs/[...slug].js

/post/a: { "slug": ["a"] }
/post/a/b: { "slug": ["a", "b"] }

결과를 보면 사실상 깊이에 상관없이 모든 route 명을 꺼내서 배열로 만드는 기능이라고 봐야...

우선순위

사전 정의된 라우트 > 동적 라우트 > 캐치올 라우트

  • pages/post/create.js - Will match /post/create
  • pages/post/[pid].js - Will match /post/1, /post/abc, etc. But not /post/create 입력 시 create.js 가 우선 매칭.
  • pages/post/[...slug].js - Will match /post/1/2, /post/a/b/c, etc. But not /post/create, /post/abc 가 우선 매칭.

끙. hydrate 까지 보기는 ㅠㅠ 나중에 필요할 때 보자.

Pages that are statically optimized by Automatic Static Optimization will be hydrated without their route parameters provided, i.e query will be an empty object ({}).

After hydration, Next.js will trigger an update to your application to provide the route parameters in the query object.

Imperatively

<Link> 없이 클라이언트 사이드에서 직접 라우팅 할 수 있음.

import Router from 'next/router'

function ReadMore() {
  return (
    <div>
      Click <span onClick={() => Router.push('/about')}>here</span> to read more
    </div>
  )
}

export default ReadMore

Shallow Routing

getInitialProps 가 9.3 이상부터는 매번 실행되는 것과 한 번 실행되는 걸로 분화가 되었으니 이건 getInitilaProps 를 사용하는 경우에만 유효한 이야기

getInitialProps 실행 없이 URL 바꿀 수 있는게 얕은 라우팅.

라우터 객체를 통해서 상태 손실 없이 pathname query 을 받을 수 있다.

To enable shallow routing, set the shallow option to true. Consider the following example:

import { useEffect } from 'react'
import { useRouter } from 'next/router'

// Current URL is '/'
function Page() {
  const router = useRouter()

  useEffect(() => {
    // Always do navigations after the first render
    router.push('/?counter=10', null, { shallow: true })
  }, [])

  useEffect(() => {
    // The counter changed!
  }, [router.query.counter])
}

export default Page

페이지에 라우터 객체 만들기 싫으면 직접 사용해도 됨.

import Router from 'next/router'
// Inside your page
Router.push('/?counter=10', null, { shallow: true })

브라우저 주소는 /?counter=10로 변하지만 라우트의 상태만 변할 뿐이고 페이지는 대체되지 않는다.

자꾸 맥락이 SSR, 그것도 매번 실행되는 데이터 조작인 getInitialProps 라는 걸 잊게된다. 이 맥락에서 문서를 봐야 이해가 된다.

URL 변경을 componentDidUpdate 로 감시할 수도 있다.

componentDidUpdate(prevProps) {
  const { pathname, query } = this.props.router
  // verify props have changed to avoid an infinite loop
  if (query.counter !== prevProps.router.query.counter) {
    // fetch data based on the new query
  }
}

주의

얕은 라우팅은 같은 페이지 URL 내에서 만 작동한다.

Router.push('/?counter=10', '/about?counter=10', { shallow: true })

아무리 쉘로우:true 라고 해줘도, 파라미터가 바뀐 게 아니고, 페이지 자체가 바뀌었으므로 현재 페이지를 unload 하고 새 페이지를 load 하는 과정에서 getInitialPrpos 부분이 실행된다. 세 페이지니까.

API Routes

Introduction

넥스트 서버에서 API 기능을 제공할 수 있게 해주는 솔루션.

pages/api 안에 넣어두면 url 로 접근할 때는 바로 호스트 루트가 api 폴더로 매칭됨.

파일구조: url
pages/api/*: http(s)://호스트/api/*
pages/api/사용자목록: http(s)://호스트/api/사용자목록

구조는 이럼

pages/api/user.js

export default (req, res) => {
  res.statusCode = 200
  res.setHeader('Content-Type', 'application/json')
  res.end(JSON.stringify({ name: 'John Doe' }))
}

요청 핸들러 함수를 export default 처리.

API 경로에서 다른 HTTP 메소드를 처리하고 싶으면, req.method 사용

export default (req, res) => {
  if (req.method === 'POST') {
    // Process a POST request
  } else {
    // Handle any other HTTP method
  }
}

API 라우팅은 CORS 헤더를 설정 안함, 즉 기본적으노 같은 오리진만 가능하다. [마이크로 CORS]https://nextjs.org/docs/api-routes/api-middlewares#micro-support)로 이거 설정 가능함.

Dynamic API Routes

동적 경로 라우팅과 비슷하게 API 라우팅도 동적으로 가능.

pages/api/post/[pid].js

[pid] 부분을 쿼리 객체로 뽑아서 사용 가능.

export default (req, res) => {
  const {
    query: { pid },
  } = req

  res.end(`Post: ${pid}`)
}

요청 주소 => 응답 결과
/api/post/abc => Post: abc

Catch All API Routes

일반 라우팅과 비슷하게.. API 도 하위의 모든 요청을 하나의 핸들러 감수에서 처리하게 할 수 있음.

페이지구조: 대응 주소
pages/api/post/[...slug]:
/api/post/a
/api/post/a/b
/api/post/a/b/c

slug 대신 param 이라고 쓸 수 있음

요청주소: 캐치올객체 결과
/api/post/a: { "slug": ["a"] }
/api/post/a/b: { "slug": ["a", "b"] }

구현은 요렇게 됨.

export default (req, res) => {
  const {
    query: { slug },
  } = req

  res.end(`Post: ${slug.join(', ')}`)
}

주의

사전 정의된 API 라우트 > 동적 API 라우트 > 캐치올 API 라우트

  • pages/api/post/create.js : /post/create
  • pages/post/[pid].js : /post/1, /post/abc, etc.
    그러나 /post/create 입력 시 create.js 가 우선 매칭.
  • pages/post/[...slug].js : /post/1/2, /post/a/b/c, etc.
    그러나 /post/create 입력 시 /post/abc 가 우선 매칭.

API Middlewares

req 해석하는, 빌트인 된 미들웨어.

req.cookies - 요청에 쿠키 보내졌는지 담는 객체. 기본 {}
req.query - 쿼리스트링 포함하는 객체. 기본 {}
req.body - 컨텐트 타입이별로 해석된 body를 담는 객체 body 없으면 null

설정 변경

모든 API 라우트는 기본 설정을 변경하기 위해서 config 객체를 export 할 수 있다.

export const config = {
  api: {
    bodyParser: {
      sizeLimit: '1mb',
    },
  },
}

api 객체는 API 라우트에 가능한 모든 설정을 포함함.

bodyParser 는 body parsing 활성화함, Stream으로 처리하고 싶으면 비활성화 가능

export const config = {
  api: {
    bodyParser: false,
  },
}

bodyParser.sizeLimit 는 해석된 body 에 담길 최대 사이즈. byte 단위.

export const config = {
  api: {
    bodyParser: {
      sizeLimit: '500kb',
    },
  },
}

Connect/Express middleware support

적합한 미들웨어를 connect 연결할 수 있다.

혹은 아래처럼 Promise 감싸고 그걸 await 로 기다리게 하는 req 핸들러를 만들면 된다.

API 엔드포인트 위해서 CORS 설정 필요할 경우 cors 패키지를 이용할 수 있다

import Cors from 'cors'

// Initializing the cors middleware
const cors = Cors({
  methods: ['GET', 'HEAD'],
})

// Helper method to wait for a middleware to execute before continuing
// And to throw an error when an error happens in a middleware
function runMiddleware(req, res, fn) {
  return new Promise((resolve, reject) => {
    fn(req, res, result => {
      if (result instanceof Error) {
        return reject(result)
      }

      return resolve(result)
    })
  })
}

// 요청 핸들러는
async function handler(req, res) {
  // 미들웨어 실행하고 기다린다.
  await runMiddleware(req, res, cors)

  // 이제 처리
  res.json({ message: 'Hello Everyone!' })
}

export default handler

Response Helpers

export default (req, res) => {
  res.status(200).json({ name: 'Next.js' })
}

헬퍼들

  • res.status(code) - HTTP 상태 코드 설정.
  • res.json(json) - JSON 응답 보내기.
  • res.send(body) - HTTP 응답 보내기. body 는 문자열 or 객체 or 버버 가능

Deployment

건너뜀.

Advanced Features

Preview Mode

이 부분은 9.3 이상에서만 유효함

이 부분 내용 왜 이렇게 길지? 이게 필요한가? 음? 일단 보자. 이거 지금 내가 투입될 작업에 필요 없는 거 같은데.. 음??? 아리까리하네.

getStaticProps 와 getStaticPaths 사용해서 사전렌더링을 서버 빌드 타임 때 (Static Generation) 하는 걸 앞서 봤다.

화면 표시 책임은 React SSR 이 책임지고, 자료 관리는 headless CMS 에서 받아온다면. SSR 은 좋다.

근데 만약 사용자가 draft 된 글을 지금 당장 보고 싶을 때는, 이미 정적으로 생성된 자료려 빌드된 게 아닌, 요청시에 draft 된 자료를 받아와서 렌더링을 하길 원한다.

이 때는 넥스트의 정적 사이트 생성을 우회하고 싶을 거다.

그런 기능이 있다. Preview Mode 라고.

post/12 은 SSR 빌드시간에 만들어 진 거. 근데 지금 빌드시에 만들어 지지 않았지만 headless cms 서버에는 들어있는 자료인 draft 상태인 15번 글을 보고 싶으면. post/15 요청 시 글 보여주기 컴포넌트에 새로운 자료를 받아와서 다시 생성해서 내려 받기를 원할 거다.

getStaticProps 는 빌드 타임때만 실행되므로, 요청시에 새로 렌더링 되는 걸 못함. 그래서 이런 특수한 때만 다시 렌더링 하도록 우회하는 게 preview 모드다.

흐름은 이렇다.

미리보기 요청 -> 우회설정 -> 글보기 페이지 리다이렉트 -> 우회설정때 받아온 자료를 가지고 렌더링 -> 받아보기

Step 1. Create and access a preview API route

미리보기 API 라우트 만든다. 예. pages/api/preview.js

응답 객체에서 setPreviewData 를 호출한다. setPreviewData 의 인자는 객체여야 한다. 그건 getStaticProps 에서 사용될 수 있다.

export default (req, res) => {
  res.setPreviewData({})
  res.end('Preview mode enabled')
}

res.setPreviewData는 preview 모드를 켜는 브라우저 쿠키를 설정한다. 이 쿠키를 포함하는 모든 넥스트 JS 요청은 preview 모드로 간주된다. 동시에 정적 생성페이지 행위가 변한다.

브라우저 개발자 도구 보면, 요청에 __prerender_bypass , __next_preview_data 쿠키가 설정되 있는 걸 보게 될 거다.

Headless CMS 에 안전하게 접근하기

시크릿 토큰이랑 커스텀 프리뷰 URL 필요.

https://<your-site>/api/preview?secret=<token>&slug=<path>
  • <your-site> 배포 도메인.
  • <token> 생성된 시크릿 키.
  • <path> 미리보기 원하는 페이지. /posts/foo 라면, &slug=/posts/foo.

흐름
preview 요청하면, 시크릿 키 체크하고, res.setPreviewData 호출해서 preview Mode 켜는 쿠키 설정. 이후 자료 요청 & 렌더링해서 보여줄 페이지 경로로 리다이렉트

  export default async (req, res) => {
  // Check the secret and next parameters
  // 클라이언트에 노출되지 않고, 나와 Headless CMS 만 아는 시크릿 키
  if (req.query.secret !== 'MY_SECRET_TOKEN' || !req.query.slug) {
    return res.status(401).json({ message: 'Invalid token' })
  }

  // Fetch the headless CMS to check if the provided `slug` exists
  // getPostBySlug would implement the required fetching logic to the headless CMS
    // 15번 draft 자료를 headlessCMS 에 요청할 수도 있음
  const post = await getPostBySlug(req.query.slug)

  // If the slug doesn't exist prevent preview mode from being enabled
  if (!post) {
    return res.status(401).json({ message: 'Invalid slug' })
  }

  // Enable Preview Mode by setting the cookies
  res.setPreviewData({})

  // Redirect to the path from the fetched post
  // 직접 req.query.slug 이동하지 않는 건 리다이렉트 취약성이 발생할까봐.
  res.writeHead(307, { Location: post.slug })
  res.end()
}

307 쓰는 이유 : https://perfectacle.github.io/2017/10/16/http-status-code-307-vs-308/ 요거 함 봐야겠네.

Step 2. Update getStaticProps

res.setPreviewData 쿠기 설정이 되면, getStaticProps 는 빌드타임뿐 아니라 요청 시에도 호출된다. 즉, 우회가 됨.

구제적으로 context 를 통해 호출을 할 수 있다.

  • context.preview will be true.
  • context.previewDatasetPreviewData 에 전달된 인자가 담겨있다.

예를 들어서, 세션 데이터가 필요할 때면, res.setPreviewData({}) 에서 {]에다가 세션 정보를 pass 할 수도 있다.

getStaticPaths 쓰면, context.params도 사용 가능.

export async function getStaticProps(context) {
  // If context.preview is true, append "/preview" to the API endpoint
  // to request draft data instead of published data. This will vary
  // based on which headless CMS you're using.
  const res = await fetch(`https://.../${context.preview ? 'preview' : ''}`)
  // ...
}

미리보기 자료 fetch
context.preview context.previewData 에 따라 getStaticProps 는 다른 자료를 가져올 수 있다.

아래처럼 preview 여부에 따라서 cms에 자료를 요청하는 주소를 바꿔서 보낼 수도 있다.

export async function getStaticProps(context) {
  // If context.preview is true, append "/preview" to the API endpoint
  // to request draft data instead of published data. This will vary
  // based on which headless CMS you're using.
  const res = await fetch(`https://cms주소자료요청주소/${context.preview ? 'preview' : ''}`)
  // ...
}

res 로 SSR 된 페이지가 내려오면 그것이 preview 를 담고 있는 페이지.

More Examples

Take a look at the following examples to learn more:

DatoCMS Example (Demo)

More Details

Clear the preview mode cookies

쿠기 끝 날짜가 없어서 브라우저 꺼야 끝난다. clearPreviewData로 강제 끝낼 수 있다.

export default (req, res) => {
  // Clears the preview mode cookies.
  // This function accepts no arguments.
  res.clearPreviewData()
  // ...
}

Specify the preview mode duration

setPreviewData 는 두번째 매개변수로 옵션 객체를 받음 :

maxAge: Specifies the number (in seconds) for the preview session to last for.
setPreviewData(data, {
  maxAge: 60 * 60, // The preview mode cookies expire in 1 hour
})

previewData size limits

setPreviewDatagetStaticProps에 객체를 넘겨줄 수 있지만 어찌됐건 쿠키에 저장이 되기 때문에 2KB 최대다.

Unique per next build
previewData를 암호화하는데 사용되는 bypass cookie value 와 private key 는 매 next build 마다 바뀐다. 추측을 불가능하게 하기 위해서.

Dynamic Import

Next.js supports ES2020 dynamic import(). They also work with SSR.

관리 가능한 chunk 로 코드를 split 하는 또다른 방법이다.

기본 사용

모듈 ../components/hello 은 페이지에 의해서 동적 로딩된다. export default 로 내보내진 컴포넌트를 가져온다.

import dynamic from 'next/dynamic'

const DynamicComponent = dynamic(() => import('../components/hello'))

function Home() {
  return (
    <div>
      <Header />
      <DynamicComponent />
      <p>HOME PAGE is here!</p>
    </div>
  )
}

export default Home

With named exports

export default 가 아니라 이름으로 내보내진 컴포넌트를 가져오려면 then 을 쓴다. import 가 promise 를 리턴해서 가능하다.

export function Hello() {
  return <p>Hello!</p>
}
import dynamic from 'next/dynamic'

const DynamicComponent = dynamic(() =>
  import('../components/hello').then(mod => mod.Hello)
)

function Home() {
  return (
    <div>
      <Header />
      <DynamicComponent />
      <p>HOME PAGE is here!</p>
    </div>
  )
}

export default Home

With custom loading component

다이나믹 컴포넌트 로딩 중에 보여질 로딩 컴포넌트를 옵션으로 넣을 수 있다.

import dynamic from 'next/dynamic'

const DynamicComponentWithCustomLoading = dynamic(
  () => import('../components/hello'),
  { loading: () => <p>...</p> }
)

function Home() {
  return (
    <div>
      <Header />
      <DynamicComponentWithCustomLoading />
      <p>HOME PAGE is here!</p>
    </div>
  )
}

export default Home

With no SSR. SSR 제외하고 다이나믹 로드

클라에서만 사용되는 라이브러리는 SSR 때 로그 안하고 싶을 거다.

import dynamic from 'next/dynamic'

const DynamicComponentWithNoSSR = dynamic(
  () => import('../components/hello3'),
  { ssr: false }
)

function Home() {
  return (
    <div>
      <Header />
      <DynamicComponentWithNoSSR />
      <p>HOME PAGE is here!</p>
    </div>
  )
}

export default Home

Automatic Static Optimization

넥스트는 페이지가 블록킹 데이터 요청을 가지고 있지 않으면 페이지가 (사전렌더링될 수 있는)정적이라고 판단한다. 페이지에서 getInitialProps가 없으면 그렇다고 판단한다.

이게 주는 이득은, SSR 계산이 없다는 거다. 그래서 즉시 사용자에게 streamed 될 수 있다. 사용자는 ultra fast 로딩이다.

작동법

getInitialProps 있을 때. 페이지 요구되면 기본 작동한다(SSR)

getInitialProps 없으면, 페이지를 정적 HTML 으로 사전렌더링 해서 정적 최적화를한다. 이 단계에서 query 를 게종하지 않으면 라우터 query 객체는 비어지게 된다. hydration 뒤에 클라이언트 사이드에서 query가 조작된다.

next build 는 .html 파일을 정적 최적화 페이지에 내보낸다.

.next/server/static/${BUILD_ID}/about.html

getInitialProps 있으면 자바스크립트가 된다.

.next/server/static/${BUILD_ID}/about.js

주의사항

  • 만약 custom App 에 getInitialProps 썼으면 모든 페이지가 비활성화 된다.
  • 만약 custom Document 에 getInitialProps 썼다면, SSR 되기 전에 ctx.req 가 정의되어 있는지 확인해라. ctx.req는 사전 렌더링된 페이지에서 undefined 가 될 거다.

Static HTML Export

next export 는 HTML 로 내보내기 해준다. node.js 서버 없이 독립실행 할 수 있는.

내보내진 앱은 dynamic routes, prefetching, preloading and dynamic imports 등의 거의 모든 기능을 지원한다.

next export 는 prerendering all pages to HTML 하는데 exportPathMap 로 매핑이 된 경로만 html 로 렌더링한다.

페이지에 getInitialProps 없으면 next export 필요 없다. 자동 정적 최적화 덕분에.

사용법

"scripts": {
  "build": "next build && next export"
}
npm run build

out 디렉토리에 생성된다.

배포

건너뜀

주의

  • export 시기 때, 페이지에 getInitialProps 가 실행된다. 작동하는 서버가 없기 때문에 context의 req 와 res 는 비어진다.

  • 정적 export 때 HTML 동적 렌더가 가능하지 않다, next export 안 쓰면 정적 생성과 SSR 이 하이브리드 될 수 있다. pages section 참조.

AMP Support

건너뛰기. 모바일 전용 페이지 사용할 일 없다.

Customizing Babel Config

Next.js includes the next/babel. But if you want to extend the default Babel configs, it's also possible.

.babelrc file:

{
  "presets": ["next/babel"],
  "plugins": []
}

next/babal 프리셋은 아래의 프리셋을 다 포함하기 때문에 추가되면 안된다.

preset-env
preset-react
preset-typescript
plugin-proposal-class-properties
plugin-proposal-object-rest-spread
plugin-transform-runtime
styled-jsx

새로 추가하는 대신에, next/babel 하위에서 추가 설정을 할 수 있다.

{
  "presets": [
    [
      "next/babel",
      {
        "preset-env": {},
        "transform-runtime": {},
        "styled-jsx": {},
        "class-properties": {}
      }
    ]
  ],
  "plugins": []
}

The modules option on "preset-env" should be kept to false, otherwise webpack code splitting is disabled.

Customizing PostCSS Config

기본 행위

  1. AUtoprefixer 로 ie11 등의 지원을 위한 vendor prefixes 를 추가
  2. 파이어폭스 크로스 브라우저 버그를 수정
  3. IE 11 에 적합하게 새로운 CSS 기능을 컴파일
    all Property
    Break Properties
    font-variant Property
    Gap Properties
    Grid Layout
    Media Query Ranges

Next 기본 CSS 처리 행위에서 CSS 변수는 IE11을 위해 컴파일 되지 않는다.

CSS variables are not compiled because it is not possible to safely do so. If you must use variables, consider using something like Sass variables which are compiled away by Sass.

이런 과정을 커스텀 할 수 있는데.. 자세한 건 그 때 필요하면 하기로 함.

  • 타켓 브라우저 커스터마이징
  • 플러그인 커스터마이팅

Custom Server

커스텀 서버 돌릴 수 있다. 근데 서버리스 함수와 자동 정적 최적화 같은 기능이 빠져버림.

// server.js

const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')

const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare().then(() => {
  createServer((req, res) => {
    // Be sure to pass `true` as the second argument to `url.parse`.
    // This tells it to parse the query portion of the URL.
    const parsedUrl = parse(req.url, true)
    const { pathname, query } = parsedUrl

    if (pathname === '/a') {
      app.render(req, res, '/b', query)
    } else if (pathname === '/b') {
      app.render(req, res, '/a', query)
    } else {
      handle(req, res, parsedUrl)
    }
  }).listen(3000, err => {
    if (err) throw err
    console.log('> Ready on http://localhost:3000')
  })
})

server.js 는 바벨 통과 안함. 따라서 현재 node 버전에서 지원되는 문법만 쓸 것.

"scripts": {
  "dev": "node server.js",
  "build": "next build",
  "start": "NODE_ENV=production node server.js"
}

넥스트 어플리케이션과 연결하기 위해서 server.js 에서 다음 구문이 있다.

const next = require('next')
const app = next({})

next import는 아래 함수를 수신한다

  • dev: Boolean - Whether or not to launch Next.js in dev mode. Defaults to false
  • dir: String - Location of the Next.js project. Defaults to '.'
  • quiet: Boolean - Hide error messages containing server information. Defaults to false
  • conf: object - The same object you would use in next.config.js. Defaults to {}

요래 해서 받는 app은 Next.js 가 요청을 핸들할 수 있게 해줌.

파일 시스템 라우팅 비활성화

If your project uses a custom server, this behavior may result in the same content being served from multiple paths, which can present problems with SEO and UX.

To disable this behavior and prevent routing based on files in pages, open next.config.js and disable the useFileSystemPublicRoutes config:

module.exports = {
  useFileSystemPublicRoutes: false,
}

Note that useFileSystemPublicRoutes simply disables filename routes from SSR; client-side routing may still access those paths. When using this option, you should guard against navigation to routes you do not want programmatically.

You may also wish to configure the client-side Router to disallow client-side redirects to filename routes; for that refer to Router.beforePopState.

Custom App

넥스트에서 App은 페이지 초기화 하는 컴포넌트. 이걸 사용하면 페이지 초기화를 오버라이드할 수 있음.

  • 페이지 변경 시 레이아웃 유지
  • 페이지 돌아다녀도 상태 유지
  • componentDidCatch로 Custom error 핸들링
  • pages 에 추가적인 자료 주입
  • Add global CSS

./pages/_app.js

// import App from 'next/app'

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

// Only uncomment this method if you have blocking data requirements for
// every single page in your application. This disables the ability to
// perform automatic static optimization, causing every page in your app to
// be server-side rendered.
//
// MyApp.getInitialProps = async (appContext) => {
//   // calls page's `getInitialProps` and fills `appProps.pageProps`
//   const appProps = await App.getInitialProps(appContext);
//
//   return { ...appProps }
// }

export default MyApp

Component prop 은 현재 활성된 페이지다. 라우트가 변경되면 Component 는 활성화된 페이지로 바뀐다. 또한, Component 에 보낸 props는 모두 해당 페이지의 props 가 된다.

pageProps는 페이지 를 위해 사전적재된 초기 프롭스 객체다. 페이지가 getInitialProps 를 쓰지 않으면 비어있다.

Custom Document

현재 프로젝트에서 이거 사용 안 한다. 스킵

넥스트 페이지가 서라운딩 도큐먼트 마크업 디피니션을 스킵하게 해줌.

getInitialProps 써서 비동기 서버렌더링 데이터 사용 가능.

./pages/_document.js

import Document, { Html, Head, Main, NextScript } from 'next/document'

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx)
    return { ...initialProps }
  }

  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

export default MyDocument

<Html>, <Head />, <Main /> and <NextScript /> are required for the page to be properly rendered.

ctx 객체는 getInitialProps

renderPage: Function - a callback that executes the actual React rendering logic (synchronously). It's useful to decorate this function in order to support server-rendering wrappers like Aphrodite's renderStatic

주의

  • Document 는 오직 서버에서만 렌더링된다. 이벤트 헨들러 onClick 같은 건 작동 안함.
  • <Main /> 바깥의 리액트 컴포넌트는 브라우저에 의해서 초기화 안 됨. 애플리케이션 로직을 거기다가 넣지 말기. 모든 페이지 간 공유 컴포넌트가 있다면(메뉴나 툴바), App 컴포넌트에 넣는 걸 추천.
  • Document getInitialProps 은 클라이언트 사이드 트랜지션에는 호출 안 되고 정적 최적화도 안 된다.
  • next export자동 정적 최적화로 내보내진 정적 페이지는 getInitialPropsctx.req / ctx.resundefined 될 거다. 꼭 확인 바람.

renderPage 커스터마이징

renderPage커스터마이징은 서버사이드 렌더링을 적절하게 작동하기 위해서 css-in-js 라이브러리를 감싸야 할 필요하 있을 때만 되어야 함.

import Document from 'next/document'

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const originalRenderPage = ctx.renderPage

    ctx.renderPage = () =>
      originalRenderPage({
        // useful for wrapping the whole react tree
        enhanceApp: App => App,
        // useful for wrapping in a per-page basis
        enhanceComponent: Component => Component,
      })

    // Run the parent `getInitialProps`, it now includes the custom `renderPage`
    const initialProps = await Document.getInitialProps(ctx)

    return initialProps
  }
}

export default MyDocument

실제 예 styled components 스타일이 적용전에 렌더가 되는 문제 해결법

Custom Error Page

404

빈번하게 404 페이지가 접근된다. 그럼 Next.js 서버 부하 늘린다.

그래서 정적 404 를 제공한다.

빌드 타임때 정적 생성되는 pages/404.js 파일을 생성할 수 있다.

pages/404.js

export default function Custom404() {
  return <h1>404 - Page Not Found</h1>
}

500

넥스트는 기본 404페이지 스타일로 500 오류 페이지도 제공한다. 이것은 정적 최적화기 안 됨. 왜냐면 서버 사이드에서 제출을 허용하기 때문이다. 그래서 404랑 분리가 되어있다.

Customizing The Error Page

pages/_error.js

function Error({ statusCode }) {
  return (
    <p>
      {statusCode
        ? `An error ${statusCode} occurred on server`
        : 'An error occurred on client'}
    </p>
  )
}

Error.getInitialProps = ({ res, err }) => {
  const statusCode = res ? res.statusCode : err ? err.statusCode : 404
  return { statusCode }
}

export default Error

pages/_error.js is only used in production. In development you’ll get an error with the call stack to know where the error originated from.

Reusing the built-in error page

If you want to render the built-in error page you can by importing the Error component:

import Error from 'next/error'
import fetch from 'isomorphic-unfetch'

const Page = ({ errorCode, stars }) => {
  if (errorCode) {
    return <Error statusCode={errorCode} />
  }

  return <div>Next stars: {stars}</div>
}

Page.getInitialProps = async () => {
  const res = await fetch('https://api.github.com/repos/zeit/next.js')
  const errorCode = res.statusCode > 200 ? res.statusCode : false
  const json = await res.json()

  return { errorCode, stars: json.stargazers_count }
}

export default Page

The Error component also takes title as a property if you want to pass in a text message along with a statusCode.

src Directory

루트/pages 대신 루트/src/pages 로 사용할 수도 있다.

  • 근데 루트/pages 가 있으면 루트/src/pages 무시됨
  • next.config.jstsconfig.json 같은 설정 파일은 루트 디렉토리에 있어야 함, src로 옮겨도 작동 안 함. Same goes for the public directory

Upgrade Guide

패스

FAQ

패스

profile
다른 누구와도 같은 시장 육체 노동자

0개의 댓글