# Next.js
###### tags: `tech sharing`
리액트로 웹 어플리케이션을 만들 때 고려해야할 사항들에는 다음과 같은 것들이 있다.
- 코드가 웹팩 같은 번들러로 번들링 되고 바벨 같은 컴파일러로 변형되어야한다.
- 코드 스플리팅 같은 배포 최적화를 해야한다.
- SEO를 위해서 어떤 페이지들은 pre-rendering 하기를 원하거나 서버 사이드 렌더링이나 클라이언트 사이드 렌더링을 원할 수도 있다.
- 리액트 앱을 data store에 연결하기 위해서 서버 사이드 코드를 작성해야 할 수 있다.
Next.js가 위의 문제들을 해결한다. 또한 리액트 어플리케이션을 만드는 것을 쉽게 해준다.
Next.js의 내장 기능들에는 다음과 같은 것들이 있다.
- 페이지 기반의 직관적인 라우팅
- pre-rendering과 static generation, server side rendering이 페이지 기반으로 동작한다.
- 더 빠른 페이지 로딩을 위한 자동적인 코드 스플리팅
- 최적화된 prefetching이 되는 client-side routing
- 기본적으로 Sass를 지원하고 CSS-in-JS도 가능하다.
- Fast refresh 지원
- 개발중 파일을 수정했을 때 자동으로 실행중인 브라우저에 반영이 된다.
- serverless function을 가진 API endpoint를 만드는 API routes
- 확장가능
## next.js app 만들기
```
npx create-next-app nextjs-blog --use-npm --example "https://github.com/vercel/next-learn-starter/tree/master/learn-starter"
```
## navigating
Next.js에서 `pages` 디렉터리에서 export 된 리액트 컴포넌트가 페이지가 된다.
페이지는 file명에 기반해서 routing이 된다.
예를들어, `pages/posts/first-post.js` 파일은 `/posts/first-post` 경로와 관련이 있다.
Next.js에서 `<a>` tag를 사용할 때 `next/link`로 `<a>` tag를 감싼다.
```jsx
import Link from 'next/link'
export default function FirstPost() {
return (
<>
<h1>First Post</h1>
<h2>
<Link href="/">
<a>Back to home</a>
</Link>
</h2>
</>
)
}
```
Link를 사용하면 브라우저에 의해서 네비게이션이 되는 것이 아니라 자바스크립트에 의해서 네비게이션(client side navigation)이 된다. Next.js의 배포 빌드에서 `Link` 컴포넌트가 브라우저의 viewport에 나타나면 Next.js가 자동적으로 link 된 페이지를 백그라운드에서 prefetch한다. 따라서 페이지를 클릭했을 때 페이지 이동이 빠르다.
## Assets
Next.js에서 `public` 디렉터리 안의 파일들이 static assets로 취급된다.
```jsx=
<img src="/images/profile.jpg" alt="Your Name" />
```
위와 같이 이미지를 추가할 수 있다. 이렇게 하면 수동적으로 다뤄야 하는 문제가 있다.
- 다양한 스크린 사이즈에 반응형으로 만들어야한다.
- 이미지를 외부 툴이나 라이브러리로 최적화 해야한다.
- 이미지가 viewport 안에 들어왔을 때만 로딩해야한다.
이를 해결하기 위해 Nex.js는 `Image` 컴포넌트를 제공한다.
`next/image`는 `<img>` element의 확장이다. Next.js에서 이미지 최적화를 빌드 타임에 하지 않고 이미지가 필요할 때 최적화를 한다. 정적인 사이트에 비교했을 때 빌드 타임이 증가하지 않는다.
이미지는 자동으로 lazy loading 된다.
## pre-rendering과 data fetching
Next.js에서 기본적으로 모든 페이지를 pre-rendering 한다.
Next.js의 pre-rendering은 두 가지가 있다. Next.js에서 pre-rendering 방법을 고를 수 있다. 또한 어떤 것에서는 static을 사용하고 어떤 것에서는 서버사이드 렌더링을 하는 '하이브리드' 방법을 사용할 수도 있다.
### Static generation
static generation은 build time에 HTML을 만드는 방법이다. 요청이 오면 미리 만들어져있는 HTML이 사용된다.
데이터 fetching이 없는 페이지는 자동으로 production 빌드시에 static 하게 만들어진다. 하지만 데이터 fetching이 있는 페이지도 static 하게 만들 수 있다.
#### `getStaticProps를` 사용해서 데이터와 함께 static generation
Next.js에서 페이지 컴포넌트를 export 할 때 `getStaticProps`라고 불리는 async 함수를 함께 export 할 수 있다.
- `getStaticProps`가 production build time에 동작한다.
- 함수 안에서 외부 데이터를 fetch 해서 page에 props로 보낼 수 있다.
```jsx
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`는 페이지 안에서만 export 될 수 있다. 왜냐하면 페이지가 렌더링 되기 이전에 모든 필요한 데이터를 가지고 있어야 되기 때문이다.
### Server-side rendering
server-side rendering은 요청을 할 때마다 HTML을 만드는 방법이다.
#### `getServerSideProps`를 사용한 SSR
`getServerSideProps`는 request time에 호출되기 대문에 `context` 파라미터에 request의 특정한 파라미터가 포함된다. request time에 데이터가 fetch되기를 바랄 때 사용한다. `getStaticProps`에 비해서 응답 속도가 더 느리다.
### Client-side rendering
만약 데이터를 pre-render 할 필요가 없다면 client-side rendering을 사용해도 된다
1. 페이지의 먼저 외부 데이터를 필요로 하지 않는 부분을 pre-render 한다.
2. 페이지가 로딩될 때 나머지 부분을 자바스크립트를 이용해서 외부 데이터를 fetch 하고 렌더링한다.
대시보드 같은 경우 이런 방법을 사용해도 좋다.
#### SWR
Next.js를 만든 팀에서 `SWR`이라는 데이터 fetching 리액트 훅을 만들었다. 만약 client side에서 fetching을 해야하는 경우에 `SWR`을 추천한다. 캐싱이나, revalidation, focus tracking, interval refetching 같은 기능을 제공한다.
```jsx
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>
}
```
### 언제 무엇을 사용해야 하는가?
static generation을 사용할 수 있으면 static generation을 권장한다. 왜냐하면 페이지가 한 번 만들어지고 CDN에 저장되면 request가 왔을 때 속도가 매우 빨라지기 때문이다.
만약 유저가 request를 보내기 이전에 미리 페이지를 rendering 할 수 있다면 static generation을 선택하는 것이 좋다.
반면에 유저의 request가 있기 이전에 미리 렌더링을 할 수 없다면, 즉, 자주 데이터가 업데이트 되고 page의 content가 요청마다 바뀌어야 한다면 server side rendering을 사용하는 것이 좋다. 그러면 더 느리지만 항상 최신의 상태를 유지할 수 있다. 아니면 그냥 pre-rendering을 하면서 client-side 자바스크립트가 데이터를 업데이트 시키도록 할 수 있다.
## Dynamic routes
page의 path가 외부 데이터에 의존하고 있는 경우를 처리할 때를 말한다.
1. 먼저 `[id].js`라는 페이지를 만든다. 대괄호로 시작하는 페이지는 dynamic routes가 된다.
2. 페이지를 작성한다.
3. `getStaticPaths` async 함수를 export 할 때 id로 가능한 value들의 리스트를 반환해주어야한다.
```jsx
import Layout from '../../components/layout'
export default function Post() {
return <Layout>...</Layout>
}
export async function getStaticPaths() {
// Return a list of possible value for id
}
```
4. `getStaticProps`를 사용해서 id에 해당하는 데이터를 fetch한다. `params`에 id 값이 포함되어 있다.
```jsx
import Layout from '../../components/layout'
export default function Post() {
return (
<Layout>
{postData.title}
<br />
{postData.id}
<br />
{postData.date}
</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
}
```

예제 코드
```jsx=
// pages/posts/[id].js
import Layout from '../../components/layout'
import { getAllPostIds, getPostData } from '../../lib/posts'
export default function Post() {
return <Layout>...</Layout>
}
export async function getStaticProps({ params }) {
const postData = getPostData(params.id)
return {
props: {
postData
}
}
}
export async function getStaticPaths() {
const paths = getAllPostIds()
return {
paths,
fallback: false
// false이면 getStaticPaths에서 반환되지 않은 path는 404 페이지를 띄운다.
// true이면 그 path로 이전에 request가 되어서 만들어졌던 페이지를 띄운다.
//
}
}
// lib/posts.js
export function getAllPostIds() {
const fileNames = fs.readdirSync(postsDirectory)
// Returns an array that looks like this:
// [
// {
// params: {
// id: 'ssg-ssr'
// }
// },
// {
// params: {
// id: 'pre-rendering'
// }
// }
// ]
return fileNames.map(fileName => {
return {
params: {
id: fileName.replace(/\.md$/, '')
}
}
})
}
export function getPostData(id) {
const fullPath = path.join(postsDirectory, `${id}.md`)
const fileContents = fs.readFileSync(fullPath, 'utf8')
// Use gray-matter to parse the post metadata section
const matterResult = matter(fileContents)
// Combine the data with the id
return {
id,
...matterResult.data
}
}
```
만약 하위의 모든 path를 catch 하고 싶다면 `...`을 사용하면 된다.
예를 들어, `pages/posts/[...id].js`는 `/posts/a/b`, `/posts/a/b/c` 모두 catch 한다.