[Next.js] 공식문서 : Create Your First App

👾·2023년 6월 26일
0

Next.js

목록 보기
3/4
post-thumbnail

Create a Next.js App

처음부터 React로 완전한 웹 애플리케이션을 만들기 위해선 고려해야할 중요한 세부사항이 많다:

  • 코드는 웹팩과 같은 번들러를 사용하여 번들링 되어야하고, 바벨과 같은 컴파일러를 사용하여 변환되어야 한다.
  • 코드 스플리팅과 같은 production 최적화를 수행해야 한다.
  • 성능과 SEO를 위해 일부 페이지를 정적으로 pre-rendering하길 원할 수 있다. 또한 서버-사이드 렌더링과 클라이언트-사이드 렌더링을 사용하길 원할 수 있다.
  • React 앱을 데이터 저장소에 연결하기 위해 일부 서버-사이드 코드를 작성해야할 수 있다.

프레임워크는 이러한 문제들을 해결할 수 있다. 그러나 그러한 프레임워크는 올바른 수준의 추상화를 가져야한다 - 그렇지 않으면 그다지 유용하지 않을 것이다. 또한 좋은 "개발자 경험"이 있어야 당신과 당신의 팀이 코드를 작성하는 동안 놀라운 경험을 만날 수 있다.

Next.js: The React Framework

React 프레임워크인 Next.js에 들어가보자. Next.js는 위의 모든 문제에 대한 해결책을 제공한다. 그러나 더 중요한 것은, 당신과 당신의 팀이 React 애플리케이션을 만드는것을 성공시키는 것이다.

Next.js는 최고의 개발자 경험과 다음과 같은 많은 빌트인 기능들을 제공한다:

  • 직관적인 페이지 기반 라우팅 시스템 (dynamic route 지원)
  • 페이지 별 Pre-rendering, static generation (SSG) 및 서버사이드 렌더링 (SSR) 지원
  • 더 빠른 페이지 로드를 위한 자동 코드 분할
  • 최적화된 prefetching을 이용한 클라이언트 사이드 렌더링
  • 빌트인 CSS 및 Sass 지원, 모든 CSS-in-JS 라이브러리 지원
  • Fast Refresh를 지원하는 개발 환경
  • Serverless Function으로 API 엔드포인트 개발을 위한 API routes
  • 완전한 확장가능성

Next.js는 세계에서 가장 큰 브랜드를 포함하여, 수만개의 production 관련 웹사이트 및 웹 애플리케이션에서 사용된다.

About This Tutorial

이 코스는 Next.js를 어떻게 시작하는지 설명한다.

이 튜토리얼에서, 매우 간단한 블로그 앱을 만들어보면서 Next.js 기초를 배울 수 있다. 다음은 최종 결과물 예시이다: source

| 이 튜토리얼은 JavaScript와 React에 대한 기초 지식이 있다고 가정한다. React 코드를 한번도 작성해본 적이 없다면, 먼저 공식 React 튜토리얼을 확인해라.

Setup

먼저, 개발환경이 준비되었는지 확인해라.

  • 설치된 Node.js가 없다면, 여기서 설치해라. 노드 버전 10.13 이상이 필요하다.
  • 튜토리얼에서는 당신의 고유한 코드 에디터와 터미널 앱을 사용할 것이다.

| 윈도우를 사용한다면, 윈도우용 Git을 다운로드하고 함께 제공되는 Git Bash를 사용하는 것이 좋다. 이 자습서에서는 UNIX 관련 명령을 지원한다. Windows Subsytem for Linux (WSL)을 사용해도 된다.

Create a Next.js app

Next.js 앱을 만들기 위해서, 터미널을 열고, 앱을 만들고자 하는 디렉토리 안으로 cd하고, 다음 명령어를 실행해라:

npx create-next-app@latest nextjs-blog --use-npm --example "https://github.com/vercel/next-learn/tree/master/basics/learn-starter"

| 내부적으로, 이 명령은 Next.js 앱의 부트스트랩인 [create-next-app](https://nextjs.org/docs/api-reference/create-next-app)이라 불리는 도구를 사용한다. 이는 --example 플래그를 통해 이 템플릿을 사용한다.
작동하지 않으면 이 페이지를 확인해라.

Run the development server

이제 nextjs-blog라는 새 디렉토리를 가진다. 안으로 cd하자:

cd nextjs-blog

이제, 다음 명령어를 실행해라:

npm run dev

이렇게 하면 포트 3000에서 Next.js 앱의 "개발 서버" (추후 설명 예정)가 시작된다.

잘 실행되는지 살펴보자. http://localhost:3000을 브라우저에서 열어봐라.

Welcome to Next.js

http://localhost:3000에 접근하면 다음과 같은 페이지를 확인할 수 있다. 이는 Next.js에 관한 몇가지 유용한 정보들을 보여주는 시작 템플릿 페이지이다.

이제 이 페이지를 수정해보자!

Editing the Page

시작 페이지를 수정해보자.

  • Next.js 개발 서버가 계속 실행중인지 확인해라.
  • 텍스트 에디터를 이용하여 pages/index.js를 열어라.
  • <h1> 태그 아래에 "Welcome to"라 적혀있는 텍스트를 찾아 "Learn"으로 변경해라.
  • 파일을 저장해라.

파일을 저장하는 즉시, 브라우저는 새 텍스트로 페이지를 자동으로 업데이트한다:

Next.js 개발 서버는 Fast Refresh가 활성화되어있다. 파일을 변경하면 Next.js는 변경사항을 브라우저에 거의 즉시 적용한다. 더 이상 새로고침이 필요하지 않다! 이는 앱을 빠르게 반복하는데 효과적이다.

| 개발 서버를 계속 유지해야한다. 하지만 재시작하고 싶다면, 서버를 중단하기 위해 Ctrl + c를 입력해라.

Navigate Between Pages

지금까지, 우리가 만든 Next.js 앱은 오직 한 페이지만 가진다. 웹사이트들과 웹 애플리케이션들은 일반적으로 많은 다른 페이지들을 가진다.

우리의 애플리케이션에 페이지를 추가하는 방법에 대해 알아보자.

What You'll Learn in This Lesson

  • 통합 파일 시스템 라우팅을 이용한 새 페이지 만들기
  • [Link](https://nextjs.org/docs/api-reference/next/link) 컴포넌트를 이용한 페이지들 간 클라이언트-사이드 네비게이션을 활성화하는 방법
  • 코드 스플리팅과 prefetching에 대한 기본 제공 기능에 대해 배우기

Pages in Next.js

Next.js에서, 페이지는 pages 디렉토리에서 export한 리액트 컴포넌트이다.

페이지들은 파일 이름을 기반으로 라우트와 연결된다. 예를들어, 개발중에서:

  • pages/index.js/ 경로와 연결된다.
  • pages/posts/first-post.js/posts/first-post 경로와 연결된다.

이미 pages/index.js 파일은 있으므로, 어떻게 동작하는지 확인하기 위해 pages/posts/first-post.js 파일을 만들어보자.

Create a New Page

pages 아래 posts 디렉토리를 만들자.

다음 내용을 포함한 first-post.js 파일을 posts 디렉토리 안에 만들자.

export default function FirstPost(){
  return <h1>Frist Post</h1>;
}

컴포넌트는 아무 이름을 가질 수 있지만, 반드시 default export로 export 해야한다.

이제, 개발 서버가 실행중인지 확인하고 http://localhost:3000/posts/first-post 에 접속해보자. 다음 페이지를 확인할 수 있다:

이것이 Next.js에서 다른 페이지를 만들 수 있는 방법이다.

단순히 pages 디렉토리 안에 JS 파일을 생성하면, 파일의 경로가 URL의 path가 된다.

어떻게 보면, HTML이나 PHP 파일로 웹사이트를 만드는 것과 유사하다. HTMl을 작성하는 대신 JSX를 작성하고 React Component를 사용한다.

이제 새로 추가한 페이지에 링크를 추가하여 홈페이지로 이동할 수 있게 해보자.

웹사이트에서 페이지를 연결할때, HTML <a> 태그를 사용한다.

Next.js에서는 [next/link](https://nextjs.org/docs/api-reference/next/link)Link 컴포넌트를 사용하여 애플리케이션의 페이지들을 연결할 수 있다. <Link>는 클라이언트-사이드 네비게이션을 수행하고, 네비게이션 동작을 더 잘 제어할 수 있도록 props를 허용한다.

먼저, pages/index.js를 열고, next/link에서 Link 컴포넌트를 import하기 위해 다음을 상단에 추가해라:

import Link from 'next/link';

그런 다음 아래와 같이 생긴 h1 태그를 찾아라:

<h1 className="titile">
  Welcome to <a href="https://nextjs.org">Next.js!</a>
</h1>

아래와 같이 바꿔라:

<h1 className="title">
  Read <Link href="/posts/first-post">this page!</Link>
</h1>

다음으로, pages/posts/first-post.js를 열고, 내용을 다음과 같이 바꿔라:

import Link from 'next/link';

export default function FirstPost() {
  return (
    <>
      <h1>First Post</h1>
      <h2>
        <Link href="/">Back to home</Link>
      </h2>
    </>
  );
}

보다시피 Link 컴포넌트는 <a> 태그와 유사하게 사용하지만, <a href="..."> 대신 <Link href="...">를 사용한다.

| Note: Next.js 12.2 이전에는, Link 컴포넌트를 <a> 태그로 감싸는것이 필수였지만 12.2 버전 이상부터는 필수가 아니다.

잘 동작하는지 살펴보자. 이제 각 페이지를 연결하여 앞뒤로 이동할 수 있다.

Client-Side Navigation

Link 컴포넌트는 같은 Next.js 앱의 두 페이지 간 클라이언트-사이드 네비게이션을 가능하게 한다.

클라이언트-사이드 네비게이션은 페이지 전환이 브라우저에서 일어나는 기본 네비게이션보다 더 빠른, JavaScript를 이용하여 일어남을 의미한다.

확인할 수 있는 간단한 방법은 다음과 같다:

  • 개발자 도구를 사용하여 <html>background CSS 프로퍼티를 yellow로 변경해라.
  • 두 페이지 간 앞뒤로 이동하기 위해 링크를 클릭해라.
  • 페이지 전환 사이에 노란색 배경이 지속됨을 확인할 수 있다.

이는 브라우저가 전체 페이지를 로드하지 않고, 클라이언트-사이드 네비게이션이 작동하고 있음을 나타낸다.

<Link href="..."> 대신 <a href="...">를 사용하면, 링크 클릭 시 브라우저가 전체 새로고침을 실행하기 때문에 배경색이 초기화된다.

Code splitting and prefetching

Next.js는 자동으로 코드 스플리팅을 수행하므로, 각 페이지는 그 페이지에 필요한 항목만 로드한다. 즉, 홈페이지가 렌더링될때, 다른 페이지에 대한 코드는 처음에 제공되지 않는다.

이렇게 하면 수백개의 페이지가 있어도 홈페이지가 빠르게 로드된다.

요청한 페이지에 대한 코드만 로딩하는 것은, 페이지가 격리됨을 의미하기도 한다. 특정 페이지에서 오류가 발생해도, 애플리케이션의 나머지 부분은 계속 동작한다.

게다가, Next.js의 production 빌드에서, 브라우저의 뷰포트에 Link 컴포넌트가 나타날때마다, Next.js는 백그라운드에서 자동으로 링크된 페이지의 코드를 prefetch한다. 링크를 클릭할때마다, 목적지 페이지에 대한 코드는 이미 백그라운드에 로드되어 있고, 페이지 전환은 거의 즉각적으로 이루어진다!

Summary

Next.js는 코드 스플리팅, 클라이언트-사이드 네비게이션, prefetching (production에서)를 통해 애플리케이션을 자동으로 최적화하여 최상의 성능을 제공한다.

pages 아래에 파일로 경로를 만들고 빌트인 Link 컴포넌트를 사용한다. 라우팅 라이브러리가 필요하지 않다.

next/link API 레퍼런스와 routing documentation의 일반적인 라우팅에서 Link 컴포넌트에 대해 더 알아볼 수 있다.

Assets, Metadata, and CSS

우리가 추가한 두번째 페이지는 스타일링이 없다. 이 페이지를 꾸미기 위해 CSS를 추가해보자.

Next.js는 CSS와 Sass를 자체적으로 지원한다. 이 강의에서는 CSS를 사용할 것이다.

이 강의는 이미지와 <title> 태그와 같은 페이지 메타데이터 등의 정적 asset들을 Next.js가 어떻게 다루는지도 알아볼 것이다.

What You'll Learn in This Lesson

  • Next.js에 정적 파일(이미지 등)을 추가하는 방법
  • 각 페이지의 <head> 내부 내용을 커스터마이즈 하는 방법
  • CSS Module을 이용한 스타일된 재사용가능한 리액트 컴포넌트를 만드는 방법
  • pages/_app.js에 전역 CSS를 추가하는 방법
  • Next.js 스타일링에 대한 유용한 몇가지 팁들

Prerequisites

  • 기본 CSS 지식. 이 강의는 Next.js 앱에 어떻게 CSS를 추가하는지에 대해 알아보지만, CSS 기본을 알려주진 않는다.

| Next.js 스타일링에 대한 자세한 문서를 확인하고 싶다면, CSS documentation을 봐라.

Assets

Next.js는 이미지와 같은 정적 asset들을 최상위 public 디렉토리 아래에 제공할 수 있다. public 내부의 파일들은 pages와 비슷하게 애플리케이션의 루트에서 참조할 수 있다.

public 디렉토리는 또한 robots.txt, Google Site Verification 및 다른 모든 정적 asset에 유용하다. 자세한 내용은 Static File Serving에 대한 문서를 참고해라.

Download Your Profile Picture

먼저, 프로필 사진을 검색해보자.

  • .jpg 포맷으로 프로필 사진을 다운로드해라. (또는 이 파일을 사용해라)
  • public 디렉토리 안에 images 디렉토리를 생성해라.
  • public/images 디렉토리 안에 profile.jpg 사진을 저장해라.
  • 이미지 사이즈는 약 400px x 400px이다
  • public 디렉토리 안에 사용하지 않는 SVG 로고 파일을 제거해도 된다.

Unoptimized Image

일반 HTMl을 사용하면, 다음과 같이 프로필 사진을 추가해야 한다.

<img src="/images/profile.jpg" alt="Your Name"/>

그러나, 이는 다음을 수동으로 처리해야 함을 의미한다:

  • 이미지가 다양한 화면 사이즈에서 반응형으로 동작하는지 확인
  • thrid-party 도구나 라이브러리에서 이미지 최적화
  • 뷰포트에 들어갈때만 이미지 로딩

등등. 대신, Next.js는 이들을 처리하기 위해 바로 사용가능한 Image 컴포넌트를 제공한다.

Image Component and Image Optimization

next/image는 모던 웹을 위해 진화된 HTML <img> 요소의 익스텐션이다.

Next.js는 기본적으로 이미지 최적화도 지원한다. 이를 통해, 브라우저에서 지원하는 경우 WebP와 같은 최신 포맷으로 이미지 크기 조정, 최적화 및 이미지 제공이 가능하다. 이렇게 하면 더 작은 뷰포트를 가진 디바이스에 큰 이미지가 전달되는 것을 막을 수 있다. 또한 Next.js가 향후 이미지 형식을 자동으로 채택하고 해당 형식을 지원하는 브라우저에 제공할 수 있다.

자동 이미지 최적화는 모든 이미지 소스에서 동작한다. 이미지가 CMS와 같은 외부 데이터 소스에서 호스팅되는 경우에도, 여전히 최적화할 수 있다.

Using the Image Component

빌드 타임에 이미지를 최적화하는 대신, Next.js는 유저 요청시에 수요에 맞게 이미지를 최적화한다. 정적 사이트 생성기 및 정적 전용 솔루션과 달리, 10개의 이미지를 전달하던 1천만개의 이미지를 전달하든 빌드 시간이 늘어나지 않는다.

이미지는 기본적으로 lazy load된다. 이는 뷰포트 외부의 이미지에 의해 페이지 속도가 저하되지 않음을 의미한다. 이미지는 뷰포트로 스크롤되면서 로드된다.

이미지는 항상 Google이 검색 랭킹에 사용할 Core Web VitalCumulative Layout Shift를 피하는 방식으로 렌더링된다.

다음은 next/image를 이용하여 프로필 사진을 표시하는 예제이다. heightwidth props는 소스 이미지와 종횡비가 동일한 원하는 렌더링 사이즈여야 한다.

| Note: 추후 "Polishing Layout"에서 이 컴포넌트를 사용할 것이므로 아직 복사할 필요가 없다.

import Image from 'next/image';

const YourComponent = () => (
  <Image
    src="/images/profile.jpg" // Route of the image file
    height={144} // Desired size with correct aspect ratio
    width={144} // Desired size with correct aspect ratio
    alt="Your Name"
  />
);

| 자동 이미지 최적화애 대해 자세히 알아보려면, 문서를 확인해라.
Image 컴포넌트에 대해 자세히 알아보려면, next/image의 API 레퍼런스를 확인해라.

Metadata

<title> HTML 태그와 같은, 페이지의 메타데이터를 수정하려면 어떻게 해야할까?

<title><head> HTML 태그의 일부분이므로, Next.js 페이지에서 <head>를 수정하는 방법에 대해서 알아보자.

pages/index.js를 에디터에서 열고 다음 줄을 찾아라:

<Head>
  <title>Create Next App</title>
  <link rel="icon" href="/favicon.ico" />
</Head>

소문자 <head> 대신 <Head>가 사용되는 것에 유의해라. <Head>는 Next.js에 내장된 React 컴포넌트이다. 이것으로 페이지의 <head>를 수정할 수 있다.

next/head 모듈에서 Head 컴포넌트를 import할 수 있다.

Adding Head to first-post.js

우리는 /posts/first-post 라우트에 <title>을 추가하지 않았다. 추가해보자.

pages/posts/first-post.js 파일을 열고 파일의 시작부분에 next/head에서 Head를 import하는 것을 추가하자.

import Head from 'next/head';

다음으로, export된 FirstPost 컴포넌트가 Head 컴포넌트를 포함하도록 업데이트한다.지금은 title 태그만 추가할 것이다.

export default function FirstPost() {
  return (
    <>
      <Head>
        <title>First Post</title>
      </Head>
      <h1>First Post</h1>
      <h2>
        <Link href="/">← Back to home</Link>
      </h2>
    </>
  );
}

http://localhost:3000/posts/first-post에 접속해보자. 이제 브라우저 탭에 "First Post"가 표시된다. 브라우저의 개발자 도구를 사용하면, <head><title> 태그가 추가된 것을 확인할 수 있다.

| Head 컴포넌트에 대해 자세히 알아보려면, next/head의 API 레퍼런스를 참조해라.
lang 속성을 추가하는 등 <html> 태그를 커스텀하고 싶다면, pages/_document.js 파일을 생성하여 수행할 수 있다. custom Document 문서에서 더 자세히 알아봐라.

Thrid-Party JavaScript

Thrid-party JavaScript는 서드 파티 소스에서 추가된 모든 스크립트를 의미한다. 주로, 애널리틱스, 광고, 고객 지원 위젯과 같이 처음부터 작성할 필요가 없는 새로운 기능을 사이트에 도입하기 위해 서드-파티 스크립트가 포함된다.

Adding Thrid-Party JavaScript

Next.js 페이지에 서드 파티 스크립트를 추가하는 방법에 대해 알아보자.

에디터에 pages/posts/first-post.js를 열고 다음 줄을 찾아라:

<Head>
  <title>First Post</title>
</Head>

메타데이터 외에도, 가능한 빨리 로드하고 실행해야 하는 스크립트들은 일반적으로 페이지의 <head> 안에 추가된다. 일반적인 HTML <script> 요소를 사용하면, 다음과 같이 외부 스크립트가 추가된다:

<Head>
  <title>First Post</title>
  <script src="https://connect.facebook.net/en_US/sdk.js" />
</Head>

이 스크립트는 페이스북 소셜 플러그인과 기타 기능을 도입하는데 일반적으로 사용되는 Facebook SDK가 포함되어 있다. 비록 이 방식은 동작하지만, 이 방식으로 스크립트를 포함하는 것은 동일한 페이지에서 가져온 다른 JavaScript 코드와 관련하여 언제 로드될지 명확하게 알 수 없다. 만약 특정 스크립트가 렌더링을 블로킹하고 페이지 컨텐츠 로딩을 지연시킬 수 있는 경우, 성능에 큰 영향을 미칠 수 있다.

Using the Script Component

next/script는 HTML <script> 요소의 확장이며 추가 스크립트를 가져와 실행할때 최적화한다.

동일한 파일에서, 파일 시작 부분에 next/script에서 Script를 가져와 import하는 것을 추가하자:

import Script from 'next/script';

이제, FirstPost 컴포넌트가 Script 컴포넌트를 포함하도록 수정하자:

export default function FirstPost() {
  return (
    <>
      <Head>
        <title>First Post</title>
      </Head>
      <Script
        src="https://connect.facebook.net/en_US/sdk.js"
        strategy="lazyOnload"
        onLoad={() =>
          console.log(`script loaded correctly, window.FB has been populated`)
        }
      />
      <h1>First Post</h1>
      <h2>
        <Link href="/">← Back to home</Link>
      </h2>
    </>
  );
}

Script 컴포넌트에 몇가지 추가 프로퍼티들이 정의되어 있다:

  • strategy는 서드 파티 스크립트를 로드해야하는 시점을 제어한다. lazyOnload 값은 Next.js가 특정 스크립트를 늦게 브라우저의 idle time (유휴시간)동안 로드하도록 한다.
  • onLoad는 스크립트 로드가 완료된 직후 JavaScript 코드를 실행하는데 사용된다. 예제에서는, 스크립트가 잘 로드되었음을 콘솔에 기록한다.

http://localhost:3000/posts/first-post에 접속해보자. 브라우저의 개발자 도구를 사용하면, Console 패널에 로그된 메세지를 볼 수 있어야 한다. 또한 스크립트가 전역변수를 채웠는지 확인하기 위해 window.FB를 실행할 수 있다.

Note: Facebook SDK는 서드 파티 스크립트를 애플리케이션에 효과적인 방식으로 추가하는 방법을 보여주기 위해 고안된 예제로만 사용되었다. 이제 Next.js에 서드파티 기능을 포함하는 개념을 이해했으므로, FirstPost에서 Script 컴포넌트를 제거해도 된다.

| Script 컴포넌트에 대해 자세히 알아보려면, 문서를 확인해라.

CSS Styling

이제 CSS 스타일링에 대해 이야기해보자.

보다시피, index 페이지 (http://localhost:3000)은 이미 몇가지 스타일을 가지고 있다. 파일 구조를 보면, 두가지 CSS 파일이 있는 styles 폴터를 확인할 수 있다: 전역 스타일시트(global.css)와 CSS module(Home.module.css)

만약 프로젝트에 해당 파일들이 없으면, 시작 코드를 여기서 다운받을 수 있다:

npx create-next-app nextjs-blog --use-npm --example "https://github.com/vercel/next-learn/tree/master/basics/assets-metadata-css-starter"

CSS Modules

CSS 모듈을 사용하면 고유한 클래스 이름을 자동으로 생성하며, 컴포넌트 레벨에서 CSS 스코프를 로컬로 지정할 수 있다. 이를 통해 클래스 이름 충돌에 대한 걱정 없이 다른 파일에서 동일한 CSS 클래스 이름을 사용할 수 있다.

CSS 모듈 외에도, 다음과 같은 다양한 방법으로 Next.js 애플리케이션을 스타일할 수 있다:

  • .css, .scss 파일 import를 가능하게 하는 Sass
  • Tailwind CSS와 같은 PostCSS 라이브러리
  • styled-jsx, styled-components, emotion과 같은 CSS-in-JS 라이브러리

이 강의에서는, Next.js에서 CSS 모듈과 Sass를 사용하는 방법에 대해 설명한다.

Layout Component

먼저, 모든 페이지에서 공유할 Layout 컴포넌트를 만들자.

  • components 최상위 디렉토리를 만들어라.
  • components안에, 다음 내용을 포함하는 layout.js 파일을 만들어라:
export default function Layout({ children }) {
  return <div>{children}</div>;
}

다음으로, pages/posts/first-post.js를 열고, Layout 컴포넌트 import를 추가하고, 가장 바깥 컴포넌트로 만들자:

import Head from 'next/head';
import Link from 'next/link';
import Layout from '../../components/layout';

export default function FirstPost() {
  return (
    <Layout>
      <Head>
        <title>First Post</title>
      </Head>
      <h1>First Post</h1>
      <h2>
        <Link href="/">← Back to home</Link>
      </h2>
    </Layout>
  );
}

Adding CSS

이제, Layout 컴포넌트에 몇가지 스타일을 추가해보자. 이를 위해, React 컴포넌트에서 CSS 파일을 import할 수 있는 CSS Modules을 사용할 것이다.

다음 내용을 포함하는 components/layout.module.css 파일을 만들자:

.container {
  max-width: 36rem;
  padding: 0 1rem;
  margin: 3rem auto 6rem;
}

| Important: CSS Module을 사용하려면, CSS 파일 이름은 반드시 .module.css로 끝나야 한다.

components/layout.js 안에서 container 클래스를 사용하려면, 다음이 필요하다:

  • CSS 파일을 import하고 styles 같은 이름을 지정하자
  • className으로 styles.container를 사용하자

components/layout.js를 열고 다음과 같이 변경하자:

import styles from './layout.module.css';

export default function Layout({ children }) {
  return <div className={styles.container}>{children}</div>;
}

http://localhost:3000/posts/first-post로 들어가면, 이제 텍스트가 중앙 컨테이너 안에 있음을 확인할 수 있다.

Automatically Generates Unique Class Names

이제, 브라우저의 개발자 도구에서 HTML을 보면, Layout 컴포넌트에 의해 렌더링된 <div>layout_container__...과 같은 클래스 이름을 가지고 있음을 확인할 수 있다:

이것이 CSS Module이 하는 일이다: 고유한 클래스 이름을 자동으로 생성한다. CSS 모듈을 사용하는 한, 클래스 이름 충돌을 걱정할 필요가 없다.

게다가, Next.js의 코드 스플리팅은 CSS Module에서도 잘 작동한다. 각 페이지에 최소한의 CSS가 로드되도록 한다. 그 결과 번들 사이즈는 더 작아진다.

CSS Module은 빌드 타임에 JavaScript 번들에서 추출되고 Next.js에 의해 자동으로 로드되는 .css 파일을 생성한다.

Global Styles

CSS Module은 컴포넌트 레벨 스타일에 유용하다. 하지만 모든 페이지에서 로드하고 싶은 CSS가 있는 경우, Next.js는 이 역시 지원한다.

전역 CSS를 애플리케이션에 로드하려면, 다음 내용을 포함하는 pages/_app.js 파일을 만들어라:

export default function App({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

_app.js의 default export는 애플리케이션의 모든 페이지를 래핑하는 최상위 수준의 React 컴포넌트이다. 페이지 사이를 이동할 때 state를 유지하거나, 여기서 하려는 것 처럼 전역 스타일을 추가할 때 이 컴포넌트를 사용할 수 있다. _app.js 파일에 대해 더 자세히 알아보자.

Restart the Development Server

Important: pages/_app.js을 추가할 때 개발 서버를 다시 시작해야 한다. Ctrl + C를 눌러 서버를 중단하고 다시 실행하자:

npm run dev

Adding Global CSS

Next.js에서, pages/_app.js에서 전역 CSS 파일을 import하여 추가할 수 있다. 다른 어떤 곳에서든 전역 CSS를 import할 수 없다.

전역 CSS를 pages/_app.js 밖에서 import할 수 없는 이유는 전역 CSS가 페이지의 모든 요소에 영향을 미치기 때문이다.

홈페이지에서 /posts/first-post 페이지로 이동하는 경우, 홈페이지의 전역 스타일이 /posts/first-post에 의도치않게 영향을 끼친다.

전역 CSS 파일을 어디에든 배치하고 어느 이름이든 사용할 수 있다. 따라서 다음을 수행해보자:

  • 최상위 styles 디렉토리와 global.css 파일을 생성하자.
  • styles/global.css 안에 다음 CSS를 추가하자. 이 코드는 일부 스타일을 리셋하고 a 태그의 색상을 변경한다:
html,
body {
  padding: 0;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu,
    Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
  line-height: 1.6;
  font-size: 18px;
}

* {
  box-sizing: border-box;
}

a {
  color: #0070f3;
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

img {
  max-width: 100%;
  display: block;
}

마지막으로, 이전에 생성한 pages/_app.js 파일 안에 CSS 파일을 import하자:

// `pages/_app.js`
import '../styles/global.css';

export default function App({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

이제, http://localhost:3000/posts/first-post에 접근하면, 스타일이 적용된 것을 확인할 수 있다. _app.js안에 import한 모든 스타일은 애플리케이션의 모든 페이지에 전역적으로 적용된다.

| 작동하지 않는다면 : pages/_app.js를 업데이트했을 때 개발 서버를 재시작했는지 확인하라.

Polishing Layout

지금까지는, CSS 모듈과 같은 개념을 설명하기 위해 최소한의 React와 CSS 코드만 추가했다. 다음 강의인 data fetching으로 이동하기 전에, 우리의 페이지 스타일링과 코드를 다듬어보자.

Update components/layout.module.css

먼저, components/layout.module.css를 열고 레이아웃과 프로필 사진에 대한 더 세련된 스타일의 내용으로 변경하자:

.container {
  max-width: 36rem;
  padding: 0 1rem;
  margin: 3rem auto 6rem;
}

.header {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.backToHome {
  margin: 3rem 0 0;
}

Create styles/utils.module.css

두번째로, 여러 컴포넌트에서 재사용할 수 있는 CSS 유틸리티 클래스 (텍스트 스타일 용) 집합을 만들어보자.

다음 내용을 포함한 새로운 styles/utils.module.css CSS 파일을 추가하자:

.heading2Xl {
  font-size: 2.5rem;
  line-height: 1.2;
  font-weight: 800;
  letter-spacing: -0.05rem;
  margin: 1rem 0;
}

.headingXl {
  font-size: 2rem;
  line-height: 1.3;
  font-weight: 800;
  letter-spacing: -0.05rem;
  margin: 1rem 0;
}

.headingLg {
  font-size: 1.5rem;
  line-height: 1.4;
  margin: 1rem 0;
}

.headingMd {
  font-size: 1.2rem;
  line-height: 1.5;
}

.borderCircle {
  border-radius: 9999px;
}

.colorInherit {
  color: inherit;
}

.padding1px {
  padding-top: 1px;
}

.list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.listItem {
  margin: 0 0 1.25rem;
}

.lightText {
  color: #666;
}

| 애플리케이션 전체에서 이러한 유틸리티 클래스를 재사용할 수 있으며, global.css 파일에서도 유틸리티 클래스를 사용할 수 있다. 유틸리티 클래스는 메서드(ex) 전역 스타일, CSS 모듈, Sass 등)이 아니라 CSS 선택자를 작성하는 방식을 의미한다. 먼저 utility-first CSS에 대해 자세히 알아보자.

Update components/layout.js

세번째로, components/layout.js를 열고 다음 코드로 내용을 변경하자, Your Name을 실제 이름으로 변경해라:

import Head from 'next/head';
import Image from 'next/image';
import styles from './layout.module.css';
import utilStyles from '../styles/utils.module.css';
import Link from 'next/link';

const name = 'Your Name';
export const siteTitle = 'Next.js Sample Website';

export default function Layout({ children, home }) {
  return (
    <div className={styles.container}>
      <Head>
        <link rel="icon" href="/favicon.ico" />
        <meta
          name="description"
          content="Learn how to build a personal website using Next.js"
        />
        <meta
          property="og:image"
          content={`https://og-image.vercel.app/${encodeURI(
            siteTitle,
          )}.png?theme=light&md=0&fontSize=75px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg`}
        />
        <meta name="og:title" content={siteTitle} />
        <meta name="twitter:card" content="summary_large_image" />
      </Head>
      <header className={styles.header}>
        {home ? (
          <>
            <Image
              priority
              src="/images/profile.jpg"
              className={utilStyles.borderCircle}
              height={144}
              width={144}
              alt=""
            />
            <h1 className={utilStyles.heading2Xl}>{name}</h1>
          </>
        ) : (
          <>
            <Link href="/">
              <Image
                priority
                src="/images/profile.jpg"
                className={utilStyles.borderCircle}
                height={108}
                width={108}
                alt=""
              />
            </Link>
            <h2 className={utilStyles.headingLg}>
              <Link href="/" className={utilStyles.colorInherit}>
                {name}
              </Link>
            </h2>
          </>
        )}
      </header>
      <main>{children}</main>
      {!home && (
        <div className={styles.backToHome}>
          <Link href="/">← Back to home</Link>
        </div>
      )}
    </div>
  );
}

새로운 점들은 다음과 같다:

  • 페이지의 컨텐츠를 설명하는데 사용되는 meta 태그 (og:image 등)
  • 제목과 이미지의 크기를 조절하는 boolean home prop
  • homefalse인 경우 하단에 있는 "Back to home" 링크
  • priority 속성으로 preload된 next/image가 포함된 이미지 추가

Update pages/index.js

마지막으로, 홈페이지를 업데이트하자.

pages/index.js를 열고 다음 내용으로 변경하자:

import Head from 'next/head';
import Layout, { siteTitle } from '../components/layout';
import utilStyles from '../styles/utils.module.css';

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

다음으로 [Your Self Introduction]을 당신의 자기소개로 교체해라. 다음은 저자의 프로필 예시이다:

이것이 끝이다! 이제 우리는 세련된 레이아웃 코드가 생겼고 data fetching 강의로 넘어갈 준비가 되었다.

Styling Tips

유용할만한 몇가지 스타일링 팁들이 있다.

| 그냥 이 섹션을 읽고 넘어가도 된다. 앱을 변경할 필요가 없다!

Using clsx library to toggle classes

clsx는 토글 클래스 이름을 쉽게 설정할 수 있는 간단한 라이브러리이다. npm install clsx 또는 yarn add clsx로 설치할 수 있다.

자세한 내용은 문서를 참조해라. 기본 사용법은 다음과 같다:

  • success 또는 error가 될 수 있는 type을 가지는 Alert 컴포넌트를 만들고 싶다고 가정하자.
  • success인 경우, 텍스트 색상이 초록색이길 원한다. error인 경우 텍스트 색상이 빨간색이길 원한다.

먼저 다음과 같이 CSS module(ex) alert.module.css)을 작성할 수 있다.

.success {
  color: green;
}
.error {
  color: red;
}

다음으로 아래와 같이 clsx를 사용하자:

import styles from './alert.module.css';
import { clsx } from 'clsx';

export default function Alert({ children, type }) {
  return (
    <div
      className={clsx({
        [styles.success]: type === 'success',
        [styles.error]: type === 'error',
      })}
    >
      {children}
    </div>
  );
}

Customizing PostCSS Config

아무런 설정없이, 바로 사용할 수 있는 PostCSS를 사용하여 Next.js는 CSS를 컴파일한다.

PostCSS 설정을 커스터마이징 하기 위해, 최상위 파일인 postcss.config.js를 생성할 수 있다. 이는 Tailwind CSS와 같은 라이브러리를 사용할 때 유용하다.

다음은 Tailwind CSS를 추가하는 단계이다. 먼저, 패키지를 설치하자:

npm install -D tailwindcss autoprefixer postcss

다음으로, postcss.config.js를 생성하자:

// postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};

또한 tailwind.config.js에서 content 옵션을 명시하여 content source를 설정하는것을 추천한다:

// tailwind.config.js
module.exports = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx}',
    './components/**/*.{js,ts,jsx,tsx}',
    // For the best performance and to avoid false positives,
    // be as specific as possible with your content configuration.
  ],
};

| 커스텀 PostCSS 설정에 대해 더 자세히 알고싶다면, PostCSS 문서를 확인해라.

| 쉽게 Tailwind CSS를 시작하려면, 예제를 참고해라.

Using Sass

Next.js를 사용하면 바로 .scss.sass 확장을 사용하여 Sass를 import할 수 있다. CSS 모듈 및 .module.scss 또는 .module.sass 확장을 통해 컴포넌트 레벨 Sass를 사용할 수 있다.

Next.js의 빌트인 Sass 지원을 사용하기 전에, sass를 설치해야 한다:

npm install -D sass

Next.js의 빌트인 CSS 지원 및 CSS 모듈에 대해 자세히 알아보려면 CSS 문서를 참고해라.

Pre-rendering and Data Fetching

우리는 블로그를 만들고 싶지만(원하는 결과물), 지금까진 아무런 블로그 컨텐츠를 추가하지 않았다. 이번 강의에서, 앱으로 외부 블로그 데이터를 가져오는 방법을 배울 것이다. 파일 시스템에 블로그 컨텐츠를 저장하지만, 컨텐츠가 아무곳이나(ex) 데이터베이스나 Headless CMS) 저장되어 있으면 작동할 것이다.

What You'll Learn in This Lesson

이번 강의에서, 다음을 배울것이다:

  • Next.js의 pre-rendering 기능
  • pre-rendering의 두가지 형태 : Static Generation 과 Server-side Rendering
  • 데이터가 있는, 그리고 데이터가 없는 Static Generation
  • getStaticProps와 인덱스 페이지로 외부 블로그 데이터를 import하는데 사용하는 방법
  • getStaticProps에 대한 몇가지 유용한 정보

Pre-rendering

data fetching에 대해 이야기하기 전에, Next.js에서 가장 중요한 개념 중 하나인 pre-rendering에 대해 이야기해보자.

기본적으로, Next.js는 모든 페이지를 사전 렌더링한다. 즉 Next.js는 클라이언트-사이드 JavaScript에서 모든 작업을 수행하는 대신, 각 페이지의 HTML을 미리 생성한다. 사전 렌더링은 성능과 SEO 측면에서 더 좋은 결과를 가져다준다.

생성된 각 HTML은 해당 페이지에서 필요한 최소한의 JavaScript 코드와 연결된다. 페이지가 브라우저에 의해 로드되면, 해당 JavaScript 코드가 실행되고 페이지가 완전히 인터렉티브하게 만든다. (이 프로세스를 hydration이라 부른다.)

Check That Pre-rendering Is Happening

다음 단계를 수행하여 사전 렌더링이 일어나고 있는지 확인할 수 있다:
1. 브라우저의 JavaScript를 비활성화하기 (크롬에서 수행하는 방법)
2. 이 페이지에 엑세스해보기 (이 튜토리얼의 최종 결과)

앱이 JavaScript 없이 렌더링되는 것을 볼 수 있다. 이는 Next.js가 앱을 정적 HTML로 사전 렌더링하여, JavaScript 실행 없이 앱 UI를 볼 수 있게 하기 때문이다.

| Note: localhost에서도 위의 단계를 시도할 수 있지만, JavaScript를 비활성화하면 CSS가 로드되지 않을 것이다.

만약 앱이 일반 React.js 앱이라면 (Next.js 없이), 사전 렌더링이 없으므로, JavaScript를 비활성화하면 앱을 볼 수 없다. 예를들어:

  • 브라우저에서 JavaScript를 활성화하고 이 페이지를 확인해라. 이는 Create React App으로 빌드된 일반 React.js 앱이다.
  • 이제, JavaScript를 비활성화하고 동일한 페이지에 다시 접근해보자.
  • 더이상 앱을 볼 수 없다 - 대신, "이 앱을 실행하려면 JavaScript를 활성화해야한다"라고 표시된다. 이는 앱이 정적 HTML로 사전 렌더링되지 않기 때문이다.

Summary: Pre-rendering vs No Pre-rendering

다음은 간단한 그래픽 요약이다:

이제, Next.js의 두가지 형태의 사전 렌더링에 대해 이야기해보자.

Two Forms of Pre-rendering

Next.js는 두가지 형태의 사전렌더링을 가진다: Static GenerationServer-side Rendering이다. 차이점은 페이지에서 HTML을 생성하는 시점이다.

  • Static Genration빌드 타임에 HTML을 생성하는 사전 렌더링 방법이다. 사전 렌더링된 HTML은 각 요청에서 재사용된다.
  • Server-side Rendering각 요청마다 HTML을 생성하는 사전 렌더링 방법이다.

| 개발 모드에서(npm run dev 또는 yarn dev를 실행할 때), 페이지들은 모든 요청마다 사전 렌더링된다. 이는 Static Generation에도 적용되어 개발하기가 더 쉽다. production으로 전환할 때, Static Generation은 모든 요청에 대해서가 아닌, 빌드 시 단 한번만 발생한다.

Per-page Basis

중요한점은, Next.js에서 각 페이지에서 사용할 사전 렌더링 방식을 선택할 수 있다는 점이다. 대부분의 페이지에서는 Static Generation을 사용하고 나머지 페이지에서는 Server-side Rendering을 사용하는 "하이브리드" Next.js 앱을 만들 수 있다.

When to Use Static Generation v.s. Server-side Rendering

Static Generation을 사용하면, 페이지를 한번 빌드하고 CDN에서 제공할 수 있으므로 모든 요청마다 페이지를 서버에서 렌더링하는 것보다 훨씬 빠르기 때문에, 가능하면 Static Generation ( 데이터 포함 및 제외)을 사용하는것을 추천한다.

다양한 유형의 페이지에서 Static Generation을 사용할 수 있다, 다음을 포함해서:

  • 마케팅 페이지
  • 블로그 포스트
  • 이커머스 상품 리스트
  • 도움말 및 설명서

스스로에게 물어봐라: "유저 요청보다 먼저 이 페이지를 미리 렌더링할 수 있는가?". 만약 대답이 yes라면, Static Generation을 선택해야 한다.

반면에, Static Generation은 유저 요청보다 먼저 페이지를 사전 렌더링할 수 없는 경우 좋은 선택이 아니다. 아마도 페이지에 자주 변하는 데이터가 표시되고, 모든 요청마다 페이지의 컨텐츠가 변경될 것이다.

이러한 경우에는, Server-side 렌더링을 사용할 수 있다. 속도는 느려지지만, 사전 렌더링된 페이지는 항상 최신상태이다. 또는 사전 렌더링을 생략하고 클라이언트측 JavaScript를 사용하여 자주 업데이트 되는 데이터를 채울 수 있다.

We'll Focus on Static Generation

이 강의에서는, Static Generation에 집중할 것이다. 다음 페이지에서, 데이터가 있거나 없는 Static Generation에 대해 이야기해보자.

Static Generation with and without Data

Static Generation은 데이터가 있든 없든 수행할 수 있다.

지금까지, 우리가 만든 모든 페이지들은 외부 데이터를 가져올 필요가 없다. 이 페이지들은 앱이 production으로 빌드될 때 자동으로 정적으로 생성될 것이다.

그러나, 일부 페이지의 경우, 일부 외부 데이터를 처음에 가져오지 않고서는 HTML을 렌더링하지 못할 수 있다. 빌드 시에 파일 시스템에 접근해야하거나, 외부 API를 가져와야 하거나, 데이터베이스를 쿼리해야할 수 있다. Next.js는 이런 경우를 바로 지원한다 - Static Generation with data

Static Generation with Data using getStaticProps

어떻게 동작할까? 음, Next.js에서는, 페이지 컴포넌트를 export할 때, getStaticProps라는 async 함수도 export할 수 있다. 이렇게 하면, 다음과 같다:

  • getStaticProps는 production에서 빌드시에 실행된다. 그리고,...
  • 함수 내에서, 외부 데이터를 가져와 페이지에 props로 전달할 수 있다.
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](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation)는 Next.js에게 다음과 같이 말할 수 있다: "이 페이지는 몇가지 데이터 의존성을 가진다 - 따라서 빌드 시 이 페이지를 사전 렌더링할 때, 이를 먼저 해결해야한다!"

| Note: 개발 모드에서, getStaticProps는 대신 각 요청마다 실행된다.

Let's Use getStaticProps

해보면서 배우는것이 더 쉬우므로, 다음 페이지부터 블로그를 구현하는데 getStaticProps를 사용할 것이다.

Blog Data

Creating a simple blog architecture

이 예제의 블로그 포스트는 애플리케이션 디렉토리의 로컬 마크다운 파일로 저장되므로(외부 데이터 소스에서 가져오지 않음), 파일 시스템에서 데이터를 읽어야 한다.

이 섹션에서, 파일 시스템에서 마크다운 데이터를 읽는 블로그를 만드는 단계를 살펴보자.

Creating the markdown files

먼저, 루트 폴더에 **posts** (pages/posts와 같지 않음) 라는 최상위 디렉토리를 만들자. posts안에, 파일 두개를 만들자: **pre-rendering.md****ssg-ssr.md**.

이제, 다음 코드를 posts/pre-rendering.md에 복사하자:

---
title: 'Two Forms of Pre-rendering'
date: '2020-01-01'
---

Next.js has two forms of pre-rendering: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page.

- **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request.
- **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**.

Importantly, Next.js lets you **choose** which pre-rendering form to use for each page. You can create a "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others.

이제, 다음 코드를 posts/ssg-ssr.md에 복사하자:

---
title: 'When to Use Static Generation v.s. Server-side Rendering'
date: '2020-01-02'
---

We recommend using **Static Generation** (with and without data) whenever possible because your page can be built once and served by CDN, which makes it much faster than having a server render the page on every request.

You can use Static Generation for many types of pages, including:

- Marketing pages
- Blog posts
- E-commerce product listings
- Help and documentation

You should ask yourself: "Can I pre-render this page **ahead** of a user's request?" If the answer is yes, then you should choose Static Generation.

On the other hand, Static Generation is **not** a good idea if you cannot pre-render a page ahead of a user's request. Maybe your page shows frequently updated data, and the page content changes on every request.

In that case, you can use **Server-Side Rendering**. It will be slower, but the pre-rendered page will always be up-to-date. Or you can skip pre-rendering and use client-side JavaScript to populate data.

| 각 마크다운 파일의 최상단에 titledate를 포함하는 메타데이터 섹션이 있음을 알아챘을 것이다. 이를 YAML Front Matter라고 하며, gray-matter라는 라이브러리를 사용하여 파싱할 수 있다.

Installing gray-matter

먼저, 각 마크다운 파일의 메타데이터를 파싱할 수 있는 gray-matter를 설치하자.

npm install gray-matter

Creating the utility function to read the file system

다음으로, 파일 시스템의 데이터를 파싱하기 위한 유틸리티 함수를 만들자. 이 유틸리티 함수를 사용하여, 다음을 수행하고자 한다:

  • 각 마크다운 파일을 파싱하고 title, date, 파일 이름(포스트 URL에서 id처럶 사용될 것)을 얻는다.
  • index 페이지에 데이터를 나열하고, date로 정렬한다.

루트 디렉토리에 최상위 디렉토리 lib를 만든다. 그리고, lib 안에, posts.js 파일을 만들고, 다음 코드를 복붙한다:

import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';

const postsDirectory = path.join(process.cwd(), 'posts');

export function getSortedPostsData() {
  // Get file names under /posts
  const fileNames = fs.readdirSync(postsDirectory);
  const allPostsData = fileNames.map((fileName) => {
    // Remove ".md" from file name to get id
    const id = fileName.replace(/\.md$/, '');

    // Read markdown file as string
    const fullPath = path.join(postsDirectory, fileName);
    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,
    };
  });
  // Sort posts by date
  return allPostsData.sort((a, b) => {
    if (a.date < b.date) {
      return 1;
    } else {
      return -1;
    }
  });
}

| Note:
Next.js를 배우기 위해 위의 코드가 무엇을 하는지 이해할 필요는 없다. 목적은 블로그 예제의 기능을 만드는 것이다. 하지만 더 알고싶다면:

  • fs는 파일 시스템에서 파일을 읽는 Node.js 모듈이다.
  • path는 파일 경로를 조작하는 Node.js 모듈이다.
  • matter는 각 마크다운 파일의 메타데이터를 파싱할 수 있는 라이브러리이다.
  • Next.js에서, lib 폴더에는 pages 폴더와 같이 할당된 이름이 없으므로, 아무 이름이나 지정할 수 있다. lib 또는 utils를 사용하는것이 관습이다.

Fetching the blog data

이제 블로그 데이터가 파싱되었으므로, index 페이지(pages/index.js)에 추가해야한다. 이는 [getStaticProps()](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation)라는 data fetching 메소드를 사용하여 수행할 수 있다. 다음 섹션에서, getStaticProps()를 어떻게 구현하는지 알아보자.

Implement getStaticProps

Pre-rendering in Next.js

Next.js에는 두가지 형태의 사전 렌더링이 있다: Static GenerationServer-side Rendering. 차이점은 페이지에서 HTML을 생성하는 시점이다.

  • Static Genration빌드 타임에 HTML을 생성하는 사전 렌더링 방법이다. 사전 렌더링된 HTML은 각 요청에서 재사용된다.
  • Server-side Rendering각 요청마다 HTML을 생성하는 사전 렌더링 방법이다.

중요한점은, Next.js에서 각 페이지에서 사용할 사전 렌더링 방식을 선택할 수 있다는 점이다. 대부분의 페이지에서는 Static Generation을 사용하고 나머지 페이지에서는 Server-side Rendering을 사용하는 "하이브리드" Next.js 앱을 만들 수 있다.

Using Static Generation (getStaticProps())

이제, getSortedPostsData import를 추가하고 pages/index.jsgetStaticProps 내부에서 이를 호출해야한다.

에디터에서 pages/index.js를 열고 export된 Home 컴포넌트 위에 다음 코드를 추가하자:

import { getSortedPostsData } from '../lib/posts';

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

getStaticProps에서 props 객체 안에 allPostsData를 반환하면, 블로그 포스트들이 Home 컴포넌트에 prop으로 전달된다. 이제 블로그 포스트에 다음과 같이 엑세스할 수 있다:

export default function Home ({ allPostsData }) { ... }

블로그 포스트를 표시하기 위해, 자기소개가 있는 섹션 아래에 데이터가 있는 다른 <section> 태그를 추가하도록 Home 컴포넌트를 수정해보자. ()에서 ({allPostsData})로 prop을 변경하는 것도 잊지마라:

export default function Home({ allPostsData }) {
  return (
    <Layout home>
      {/* Keep the existing code here */}

      {/* Add this <section> tag below the existing <section> tag */}
      <section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
        <h2 className={utilStyles.headingLg}>Blog</h2>
        <ul className={utilStyles.list}>
          {allPostsData.map(({ id, date, title }) => (
            <li className={utilStyles.listItem} key={id}>
              {title}
              <br />
              {id}
              <br />
              {date}
            </li>
          ))}
        </ul>
      </section>
    </Layout>
  );
}

이제 http://localhost:3000에 엑세스하면 블로그 데이터를 확인할 수 있다.

축하한다! 외부 데이터(파일시스템에서)를 성공적으로 가져왔고 이 데이터를 index 페이지에 사전 렌더링했다.

다음 페이지에서 getStaticProps 사용에 대한 몇가지 팁에 대해 이야기해보자.

getStaticProps Details

다음은 getStaticProps에 대해 알아야할 몇가지 필수 정보들이다.

Fetch External API or Query Database

lib/posts.js에서, 파일 시스템에서 데이터를 가져오는 getSortedPostsData를 구현했다. 그러나 외부 API 엔드포인트와 같이 다른 소스에서 데이터를 가져와야할 할수도 있는데, 여전히 이는 잘 작동한다:

export async function getSortedPostsData() {
  // Instead of the file system,
  // fetch post data from an external API endpoint
  const res = await fetch('..');
  return res.json();
}

| Note: Next.js는 클라이언트와 서버 모두에서 fetch() 폴리필을 제공한다. import할 필요가 없다.

데이터베이스를 직접 쿼리할수도 있다:

import someDatabaseSDK from 'someDatabaseSDK'

const databaseClient = someDatabaseSDK.createClient(...)

export async function getSortedPostsData() {
  // Instead of the file system,
  // fetch post data from a database
  return databaseClient.query('SELECT posts...')
}

이는 getStaticProps서버 사이드에서만 실행되기 때문에 가능하다. 클라이언트 사이드에서는 절대 실행되지 않는다. 브라우저용 JS 번들에도 포함되지 않는다. 즉 브라우저로 보내지 않고도 직접 데이터베이스 쿼리와 같은 코드를 작성할 수 있음을 의미한다.

Development vs. Production

  • development(npm run dev 또는 yarn dev)에서, getStaticProps는 모든 요청에서 실행된다.
  • production에서, getStaticProps빌드시에 실행된다. 그러나, 이 동작은 getStaticPaths에 의해 반환된 fallback를 이용하여 향상될 수 있다.

빌드 시 실행되기 때문에, 쿼리 파라미터나 HTTP 헤더와 같이 요청시에만 사용할 수 있는 데이터를 사용할 수 없다.

Only Allowed in a Page

getStaticProps는 오직 page에서만 export될 수 있다. 페이지가 아닌 파일에서는 export할 수 없다.

이 제한의 이유 중 하나는 페이지가 렌더링되기 전에 React는 필요한 데이터를 모두 가지고 있어야 하기 때문이다.

What If I Need to Fetch Data at Request Time?

Static Generation은 빌드시 한번 발생하므로, 자주 업데이트되거나 모든 유저 요청시마다 변경되는 데이터에는 적합하지 않다.

이와 같이, 데이터가 변경될 가능성이 있는 곳에서는, Server-side 렌더링을 사용할 수 있다. 다음 섹션에서 서버 사이드 렌더링에 대해 자세히 알아보자.

Fetching Data at Request Time

빌드 시가 아닌 요청시에 데이터를 가져와야하는 경우, Server-side 렌더링을 시도할 수 있다:

서버 사이드 렌더링을 사용하려면, 페이지에서 getStaticProps 대신 getServerSideProps를 export해야한다.

Using getServerSideProps

여기 getServerSideProps에 대한 시작코드가 있다. 우리의 블로그 예제에는 필요하지 않으므로, 구현하지 않는다.

export async function getServerSideProps(context) {
  return {
    props: {
      // props for your component
    },
  };
}

getServerSideProps는 요청 시 호출되기 때문에, 파라미터(context)는 요청마다의 매개변수가 포함된다.

getServerSideProps는 요청 시 데이터를 가져와야하는 페이지를 사전 렌더링해야하는 경우에만 사용해야한다. 첫번째 바이트까지의 시간 (TTFB)는 서버가 모든 요청에 대해 결과를 계산해야하고, 추가 구성 없이 CDN에서 결과를 캐시할 수 없기 때문에 getStaticProps보다 느리다.

Client-side Rendering

데이터를 사전 렌더링할 필요가 없는 경우, 다음 전략(Client-side Rendering)을 사용할 수도 있다.

  • 외부 데이터가 필요하지 않은 페이지의 일부분을 정적으로 생성(사전렌더링)
  • 페이지가 로드되면, JavaScript를 사용하여 클라이언트에서 외부 데이터를 가져오고 남은 부분을 채우기

이 접근방식은 예를들자면 사용자 대시보드에 적합하다. 대시보드는 비공개의, 유저별 페이지이므로, SEO와 관련이 없으며, 페이지는 사전렌더링될 필요가 없다. 데이터는 자주 변경되므로 요청시마다 데이터를 가져와야한다.

SWR

Next.js 팀은 SWR이라는 데이터 fetch을 위한 React hook을 만들었다. 클라이언트 사이드에서 데이터를 가져오는 경우 이를 적극 권장한다. 이는 캐싱, 재검증, focus tracking, 일정 간격으로 refetch하기 등을 처리한다. 여기서 자세한 내용을 다루진 않지만, 사용예제는 다음과같다:

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>;
}

자세한 내용은 SWR 문서를 확인해라.

다음 강의에서는 dynamic route를 사용하여 각 블로그 포스트에 대한 페이지를 만들 것이다.

| 다시 말하지만, Data Fetching 문서에서 getStaticPropsgetServerSideProps 에 대한 자세한 정보를 얻을 수 있다.

Dynamic Routes

우리는 블로그 데이터로 index 페이지를 채웠지만, 아직 개별 블로그 페이지를 만들지 않았다(원하는 결과). 우리는 이러한 페이지의 URL이 블로그 데이터에 의존하기를 원한다, 즉 동적 라우트을 사용해야한다.

What You'll Learn in This Lesson

이번 강의에서, 다음을 배우게 된다:

  • getStaticPaths를 사용하여 동적 라우트 페이지를 정적으로 생성하는 방법
  • 각 블로그 포스트에 대한 데이터를 가져오기 위해 getStaticProps를 작성하는 방법
  • remark를 이용하여 마크다운을 렌더링하는 방법
  • 날짜 문자열을 예쁘게 출력하는 방법
  • 동적 라우트 페이지를 연결하는 방법
  • 동적 라우트에 대한 몇가지 유용한 정보

Page Path Depends on External Data

이전 강의에서, 페이지 컨텐츠가 외부 데이터에 의존하는 경우를 다뤘다. 우리는 index 페이지를 렌더링하는데 필요한 데이터를 가져오기 위해 getStaticProps를 사용했다.

이 강의에서는, 각 페이지 경로가 외부 데이터에 의존하는 경우에 대해 이야기 할 것이다. Next.js를 사용하면 외부 데이터에 의존하는 경로가 있는 페이지를 정적으로 생성할 수 있다. 이는 Next.js에서 동적 URL이 가능하게 한다.

How to Statically Generate Pages with Dynamic Routes

우리의 경우, 블로그 포스트에 대한 동적 라우트을 만들고 싶다:

  • 우리는 각 포스트가 /posts/<id> 경로를 갖길 원한다. 여기서 <id>는 최상위 posts 디렉토리 아래의 마크다운 파일의 이름이다.
  • ssg-ssr.mdpre-rendering.md 파일이 있으므로, 우리는 경로가 /posts/ssg-ssr/posts/pre-rendering이 되기를 원한다.

Overview of the Steps

다음 단계를 따라서 이를 수행할 수 있다. 아직 이러한 변경을 할 필요는 없다 - 다음 페이지에서 모든 작업을 수행할 것이다.

먼저, pages/posts 아래에 **[id].js**라는 페이지를 만든다. [로 시작하고 ]로 끝나는 페이지는 Next.js에서 동적 라우트이다.

pages/posts/[id].js에서, 포스트 페이지를 렌더링하는 코드를 작성할 것이다 - 우리가 만들었던 다른 페이지들과 마찬가지로.


import Layout from '../../components/layout';

export default function Post() {
  return <Layout>...</Layout>;
}

이제, 새로운 점이 있다: 이 페이지에서 getStaticPaths라는 비동기 함수를 export할 것이다. 이 함수에서, 가능한 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
}

마지막으로, getStaticProps를 다시 구현해야한다 - 이번에는, 주어진 id로 블로그 포스트에 필요한 데이터를 가져온다. getStaticPropsid를 포함하는 (파일 이름이 [id].js이기 때문) params를 받는다.

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
}

다음은 방금 이야기한 내용을 그래픽으로 요약한 것이다:

Implement getStaticPaths

먼저 파일을 세티앻보자:

  • pages/posts 디렉토리 안애 [id].js 파일을 만들어라.
  • 또한 pages/posts 디렉토리 안에 first-post.js를 제거하자 - 더 이상 사용하지 않는다.

그런 다음, 에디터에 pages/posts/[id]를 열고 다음 코드를 붙여넣어라. ...는 나중에 채울 것이다:

import Layout from '../../components/layout';

export default function Post() {
  return <Layout>...</Layout>;
}

다음으로, lib/posts.js를 열고 하단에 다음과 같은 getAllPostIds 함수를 추가해라. 이는 posts 디렉토리에 있는 파일 이름들의 (.md 포함)목록을 반환한다.

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$/, ''),
      },
    };
  });
}

중요: 반환된 리스트는 단순한 문자열 배열이 아니다 - 위의 주석처럼 보이는 객체의 배열이어야 한다. 각 객체는 id 키를 가지는 객체를 포함하는 params 키가 있어야 한다(파일 이름에 [id]를 사용하기 떄문). 그렇지 않으면 getStaticPaths는 실패한다.

마지막으로, getAllPostIds를 import하고 getStaticPaths 내부에서 사용한다. pages/posts/[id].js를 열고 export된 Post 컴포넌트 위에 다음 코드를 복사한다:

import { getAllPostIds } from '../../lib/posts';

export async function getStaticPaths() {
  const paths = getAllPostIds();
  return {
    paths,
    fallback: false,
  };
}
  • pathspages/posts/[id].js에 의해 정의된 파라미터들을 포함하여, getAllPostIds()에 의해 반환된 알려진 경로의 배열을 포함한다. paths 키 문서에서 더 자세히 알아봐라.
  • 지금은 fallback: false를 무시하자 - 추후에 설명할 것이다.

거의 다 끝났지만 - 아직 getStaticProps를 구현해야한다.

Implement getStaticProps

주어진 id로 포스트를 렌더링하기 위해 필요한 데이터를 가져와야한다.

그러려면, lib/posts.js를 다시 열고 하단에 주어진 getPostData 함수를 추가하자. 이는 id에 기반한 포스트 데이터를 반환할 것이다:

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,
  };
}

그런 다음, pages/posts/[id].js를 열고 다음 라인을 바꿔라:

import { getAllPostIds } from '../../lib/posts';

다음 코드로:

import { getAllPostIds, getPostData } from '../../lib/posts';

export async function getStaticProps({ params }) {
  const postData = getPostData(params.id);
  return {
    props: {
      postData,
    },
  };
}

포스트 페이지는 이제 포스트 데이터를 가져오고 prop으로 반환하기 위해 getStaticPropsgetPostData 함수를 사용하고 있다.

이제, postData를 사용하기 위해 Post 컴포넌트를 수정하자. pages/posts/[id].js에서 export된 Post 컴포넌트를 다음 코드로 바꾸자:

export default function Post({ postData }) {
  return (
    <Layout>
      {postData.title}
      <br />
      {postData.id}
      <br />
      {postData.date}
    </Layout>
  );
}

이게 끝이다! 다음 페이지들을 방문해보자:

동적 라우트를 성공적으로 생성했다.

Something Wrong?

오류가 발생한다면, 파일의 코드가 올바른지 확인해라:

Summary

우리가 한 일에 대한 그래픽 요약은 다음과 같다:

아직 블로그 마크다운 컨텐츠를 표시하지 않았다.

Render Markdown

마크다운 컨텐츠를 렌더링하기 위해, remark 라이브러리를 사용할 것이다. 먼저, 설치해보자:

npm install remark remark-html

그런 다음, lib/posts.js 파일을 열고 파일 상단에 다음 import를 추가하자:

import { remark } from 'remark';
import html from 'remark-html';

그리고 getPostData() 함수를 remark를 사용하도록 다음과 같이 수정하자:

export async 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);

  // Use remark to convert markdown into HTML string
  const processedContent = await remark()
    .use(html)
    .process(matterResult.content);
  const contentHtml = processedContent.toString();

  // Combine the data with the id and contentHtml
  return {
    id,
    contentHtml,
    ...matterResult.data,
  };
}

| 중요: remark에서 await를 사용해야 하기 때문에 getPostDataasync 키워드를 추가했다. async/await는 데이터를 비동기적으로 가져올 수 있게 한다.

getPostData를 호출할때 await를 사용하려면 pages/posts/[id].jsgetStaticProps를 수정해야한다:

export async function getStaticProps({ params }) {
  // Add the "await" keyword like this:
  const postData = await getPostData(params.id);

  return {
    props: {
      postData,
    },
  };
}

마지막으로, [dangerouslySetInnerHTML](https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml)을 사용해서 contentHTML을 렌더링하도록 pages/posts/[id].js에서 Post 컴포넌트를 수정하자:

export default function Post({ postData }) {
  return (
    <Layout>
      {postData.title}
      <br />
      {postData.id}
      <br />
      {postData.date}
      <br />
      <div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} />
    </Layout>
  );
}

다음 페이지들을 다시 방문해보자:

거의 끝났다! 다음으로 각 페이지를 다듬어보자.

Polishing the Post Page

Adding title to the Post Page

pages/posts/[id].js에서, 포스트 데이터를 사용하는 title 태그를 추가하자. 파일 상단에 next/head import를 추가해야하고 Post 컴포넌트를 업데이트하여 title 태그를 추가해야한다:

// Add this import
import Head from 'next/head';

export default function Post({ postData }) {
  return (
    <Layout>
      {/* Add this <Head> tag */}
      <Head>
        <title>{postData.title}</title>
      </Head>

      {/* Keep the existing code here */}
    </Layout>
  );
}

Formatting the Data

date를 포맷팅하기 위해, [date-fns](https://date-fns.org/) 라이브러리를 사용할 것이다. 먼저 설치해보자:

npm install date-fns

다음으로, components/date.js 파일을 만들고 다음과 같은 Date 컴포넌트를 추가하자:

import { parseISO, format } from 'date-fns';

export default function Date({ dateString }) {
  const date = parseISO(dateString);
  return <time dateTime={dateString}>{format(date, 'LLLL d, yyyy')}</time>;
}

| Note: date-fns 웹사이트에서 다양한 format() 문자열 옵션을 확인할 수 있다.

이제, pages/posts/[id].js를 열고, 파일 상단에 Date 컴포넌트 import를 추가하고, {postData.date}에서 사용하자:

// Add this import
import Date from '../../components/date';

export default function Post({ postData }) {
  return (
    <Layout>
      {/* Keep the existing code here */}

      {/* Replace {postData.date} with this */}
      <Date dateString={postData.date} />

      {/* Keep the existing code here */}
    </Layout>
  );
}

http://localhost:3000/posts/pre-rendering에 접속하면, "January 1, 2020"이라 써있는 date를 확인할 수 있다.

Adding CSS

마지막으로, 이전에 추가한 styles/utils.module.css 파일을 사용하여 약간의 CSS를 추가하자. pages/posts/[id].js를 열고, CSS 파일 import를 추가하고, 다음 코드로 Post 컴포넌트를 교체하자:

// Add this import at the top of the file
import utilStyles from '../../styles/utils.module.css';

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>
  );
}

http://localhost:3000/posts/pre-rendering에 접속하면, 페이지가 더 보기 좋아졌을 것이다:

다음으로 index 페이지를 다듬고 마무리하자!

Polishing the Index Page

다음으로, index 페이지를 업데이트하자 (pages/index.js). Link 컴포넌트를 사용하여 각 포스트 페이지로의 링크를 추가해야한다.

pages/index.js를 열고 파일 상단에 LinkDate import를 추가하자:

import Link from 'next/link';
import Date from '../components/date';

그런 다음, 같은 파일의 Home 컴포넌트 하단 근처에서, li 태그를 다음과 같이 바꾸자:

<li className={utilStyles.listItem} key={id}>
  <Link href={`/posts/${id}`}>{title}</Link>
  <br />
  <small className={utilStyles.lightText}>
    <Date dateString={date} />
  </small>
</li>

http://localhost:3000으로 가보면, 이제 페이지는 각 article에 대한 링크를 가진다:

| 잘 작동하지 않으면, 코드가 다음과 같은지 확인해라.

이게 끝이다! 강의를 마무리하기 전에, 동적 라우트에 대한 몇가지 팁에 대해 알알보자.

Dynamic Routes Details

동적 라우트에 대해 알아야할 몇가지 필수 정보이다.

Fetch External API or Query Database

getStaticProps, getStaticPaths와 같이 모든 데이터 소스에서 데이터를 가져올 수 있다. 우리의 예제에서, getAllPostIds (getStaticPaths에 의해 사용된)는 외부 API 엔드포인트에서 데이터를 가져올 수 있다:

export async function getAllPostIds() {
  // Instead of the file system,
  // fetch post data from an external API endpoint
  const res = await fetch('..');
  const posts = await res.json();
  return posts.map((post) => {
    return {
      params: {
        id: post.id,
      },
    };
  });
}

Development vs. Production

  • development (npm run dev 또는 yarn dev)에서, getStaticPaths모든 요청에서 실행된다.
  • production에서, getStaticPaths빌드 시에 실행된다.

Fallback

getStaticPaths에서 fallback: false를 반환한것을 기억해보자. 이는 무엇을 의미할까?

만약 fallback이 false면, getStaticPaths에서 반환하지 않은 모든 경로는 404 페이지가 된다.

만약 fallback이 true면, getStaticProps의 동작이 바뀐다:

  • getStaticPaths에서 반환된 경로는 빌드 시 HTML로 렌더링된다.
  • 빌드 시 생성되지 않은 경로는 404 페이지가 되지 않는다. 대신, Next.js는 그러한 경로에 대한 첫번째 요청에서 페이지의 "fallback" 버전을 제공한다.
  • 백그라운드에서, Next.js는 요청된 경로를 정적으로 생성한다. 동일한 경로에 대한 후속 요청은 빌드 시 사전 렌더링된 페이지와 마찬가지로, 생성된 페이지를 제공한다.

만약 fallbackblocking이면, 새 경로는 getStaticProps로 서버-사이드 렌더링되고, 향후 요청을 위해 캐시되므로 경로 당 한번만 발생한다.

강의 내용을 벗어나지만, fallback 문서에서 fallback: truefallback: 'blocking'에 대해 자세히 알아볼 수 있다.

Catch-all Routes

괄호 안에 점 3개(...)를 추가하여 모든 경로를 캐치하도록 동적 라우트를 확장할 수 있다. 예를들어:

  • pages/posts/[...id].js/posts/a 뿐만 아니라 /posts/a/b, /posts/a/b/c 등과 매치된다.

이렇게 하면, getStaticPaths에서, 다음과 같이 id 키 값을 배열로 반환해야한다:

return [
  {
    params: {
      // Statically Generates /posts/a/b/c
      id: ['a', 'b', 'c'],
    },
  },
  //...
];

그리고 params.idgetStaticProps의 배열이 된다:

export async function getStaticProps({ params }) {
  // params.id will be like ['a', 'b', 'c']
}

자세한 내용은 catch all routes 문서를 참고해라.

Router

Next.js 라우터에 엑세스하려면, next/router에서 [useRouter](https://nextjs.org/docs/api-reference/next/router#userouter) hook을 import하면 된다.

404 Pages

커스텀 404 페이지를 만들기 위해, pages/404.js를 만들자. 이 파일은 빌드 시 정적으로 생성된다.

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

자세한 내용은 Error 페이지 문서를 참고해라.

More Examples

getStaticPropsgetStaticPaths를 설명하기 위해 몇가지 예제를 만들었다 - 더 자세히 배우려면 소스 코드를 확인해봐라:

API Routes

Next.js는 API 라우트를 지원하므로, API 엔드포인트를 Node.js 서버리스 함수로 쉽게 만들 수 있다. 비록 우리의 블로그 앱에는 필요하지 않지만, 이번 강의에서 사용방법에 대해 간략하게 설명한다.

What You'll Learn in This Lesson

이번 강의에서, 다음을 배운다:

  • API Route를 만드는 방법
  • API Route에 대한 몇가지 유용한 정보

Creating API Routes

API Route를 통해 Next.js 앱 내부에 API 엔드포인트를 만들 수 있다. pages/api 디렉토리 안에 다음 형식의 함수를 만들어 그렇게 할 수 있다:

// req = HTTP incoming message, res = HTTP server response
export default function handler(req, res) {
  // ...
}

| API Routes 문서에서 위의 request handler에 대해 자세히 알아봐라.

이들을 서버리스 함수 (Lambdas라고도 함)로 배포할 수 있다.

Creating a simple API endpoint

시도해보자. 다음 코드를 사용하여 pages/api 안에 hello.js 파일을 만들자:

export default function handler(req, res) {
  res.status(200).json({ text: 'Hello' });
}

http://localhost:3000/api/hello에 접속해보자. {"text":"Hello"}를 확인할 수 있다. 참고로:

API Routes Details

다음은 API Route에 대해 알아야 할 몇가지 필수 정보들이다.

Do Not Fetch an API Route from getStaticProps or getStaticPaths

getStaticProps 또는 getStaticPaths에서 API Route를 가져오면 안된다. 대신, getStaticProps 또는 getStaticPaths에 직접 서버-사이드 코드를 작성해라 (또는 헬퍼 함수를 호출해라)

이유는 다음과 같다: getStaticPropsgetStaticPaths는 오직 서버-사이드에서만 실행되고 절대 클라이언트-사이드에서 실행되지 않는다. 게다가 이러한 함수들은 브라우저용 JS 번들에도 포함되지 않는다. 즉 직접 데이터베이스에 쿼리하는 코드를 브라우저로 보내지 않고도 작성할 수 있다. 자세한 내용은 Writing Server-Side code 문서를 읽어보자.

A Good Use Case: Handling Form Input

API Route의 좋은 사용 사례는 form input을 처리하는 것이다. 예를들어, 페이지에 form을 만들고 API Route로 POST 요청을 보낼 수 있다. 그런 다음 코드를 작성하여 데이터베이스에 바로 저장할 수 있다. API Route 코드는 클라이언트 번들의 일부가 아니므로, 안전하게 서버-사이드 코드를 작성할 수 있다.

export default function handler(req, res) {
  const email = req.body.email;
  // Then save email to your database, etc...
}

Preview Mode

Static Generation은 headless CMS에서 데이터를 가져올 때 유용하다. 그러나, headless CMS에서 draft를 작성하고 페이지에서 즉시 draft를 미리보고싶을때는 적합하지 않다. Next.js가 빌드 시 말고 요청 시에 이러한 페이지들을 렌더링하고, 퍼블리시된 컨텐츠 대신 draft 컨텐츠를 가져오길 원할 것이다. 이러한 특정한 경우에만 Next.js가 Static Generation을 우회하길 원할 것이다.

Next.js는 위의 문제를 해결하기 위해 Preview Mode라는 기능을 가지고 있으며, 이는 API Route를 활용한다. 자세한 내용은 Preview Mode 문서를 참고해라.

Dynamic API Routes

API Route는 일반 페이지와 마찬가지로 동적일 수 있다. 자세한 내용은 Dynamic API Routes 문서를 참고해라.

Deploying Your Next.js APP

마지막 기초 강의에서, 우리의 Next.js 앱을 production으로 배포할 것이다.

Next.js를 Next.js 개발자들에 의해 만들어진 플랫폼인 Vercel에 배포하는 방법을 배울 것이다. 또한 다른 배포 옵션들에 대해서 이야기할 것이다.

| 준비물: Github 계정이 필요하다.

What You'll Learn in This Lesson

이번 강의에서, 다음을 배우게 된다:

  • Vercel에 Next.js 앱을 배포하는 방법
  • DPS 워크플로우 : Develop, Preview, Ship
  • 자체 호스팅 프로바이더에 Next.js 앱을 배포하는 방법

Push to GitHub

배포하기 전에, 아직 푸시하지 않았다면 Next.js 앱을 깃허브에 푸시하자. 이렇게 하면 배포가 더 쉬워진다.

  • 개인 깃허브 게정에서, nextjs-blog라는 새 레포지토리를 만든다.
  • 레포지토리는 public이나 private여도 된다. README 또는 기타 파일로 초기화할 필요가 없다.
  • 레포지토리 세팅에 도움이 필요하다면, 깃허브에서 이 가이드를 살펴봐라.

그런 다음:

  • Next.js 앱으로 깃 저장소를 로컬로 초기화하지 않았다면, 지금 해라.
  • Next.js 앱을 깃허브 레포지토리로 푸시해라.

Github에 푸시하기 위해 다음 명령을 실행해라 (<username>을 깃허브 username으로 변경해라):

git remote add origin https://github.com/<username>/nextjs-blog.git
git push -u origin main

GitHub 레포지토리가 준비되면 다음 페이지로 넘어가자.

Deploy to Vercel

Next.js를 production으로 배포하는 가장 쉬운 방법은 Next.js 개발자들에 의해 만들어진 Vercel 플랫폼을 사용하는 것이다.

Vercel은 headless content, 커머스 또는 데이터베이스와 통합하도록 만들어진 정적 및 하이브리드 애플리케이션을 위한 서버리스 플랫폼이다. 프론트엔드 팀이 성능은 기본이고 즐거운 사용자 경험을 쉽게 deploy, preview, ship할 수 있도록 한다. 무료로 시작할 수 있다 - 신용카드가 필요하지 않다.

Create a Vercel Account

https://vercel.com/singup으로 가서 Vercel 계정을 만든다. Github로 계속하기를 선택하고 가입 절차를 진행한다.

Import your nextjs-blog repository

가입했으면, nextjs-blog 레포지토리를 Vercel에 import한다. 여기에서 할 수 있다: https://vercel.com/import/git

  • GitHub 용 Vercel을 설치해야한다. 모든 레포지토리에 대한 엑세스 권한을 부여할 수 있다.
  • Vercel을 설치했으면, nextjs-blog를 import해라.
    다음 설정들에 대해 기본값을 사용할 수 있다 - 아무것도 변경할 필요가 없다. Vercel은 자동으로 Next.js 앱을 가짐을 탐지하고 최적의 빌드 설정을 선택한다.
  • 프로젝트 이름
  • 루트 디렉토리
  • 빌드 명령
  • Output 디렉토리
  • Development 명령

배포하면, Next.js 앱이 빌드되기 시작한다. 1분 안에 끝나야 한다.

완료되면, 배포 URL들을 받을 수 있다. URL 중 하나를 클릭하면 Next.js 시작 페이지를 실시간으로 확인할 수 있다.

축하한다! 방금 Next.js를 production에 배포했다. 다음 페이지에서는, Vercel 및 권장 워크플로우에 대한 자세한 내용을 살펴보자.

Next.js and Vercel

Vercel은 Next.js 개발자가 만들고 Next.js를 최고 수준으로 지원한다. Next.js 앱을 Vercel에 배포하면, 다음이 기본적으로 발생한다:

  • Static Generation 및 asset들(JS, CSS, 이미지, 폰트 등)을 사용하는 페이지들은 자동으로 엄청나게 빠른 Vercel Edge Network에 제공된다.
  • 서버 사이드 렌더링 및 API Route를 사용하는 페이지들은 자동으로 격리된 서버리스 함수가 된다. 이를 통해 페이지 렌더링 및 API 요청을 무한대로 확장할 수 있다.

Vercel은 다음과 같은 더 많은 기능들을 가진다:

  • Custom Domains: Vercel에 배포되면, Next.js 앱에 커스텀 도메인을 지정할 수 있다. 여기서 문서를 확인해봐라.
  • Environment Variables: Vercel에서 환경변수를 설정할수도 있다. 여기서 문서를 확인해봐라. 그런 다음 Next.js 앱에서 이러한 환경 변수들을 사용할 수 있다.
  • Automatic HTTPS: HTTPS가 기본적으로 활성화되며 (커스텀 도메인 포함) 추가 설정이 필요하지 않다. SSL 인증서를 자동 갱신한다.

Vercel 문서에서 플랫폼에 대해 더 자세히 알아볼 수 있다.

Preview Deployment for Every Push

| 아래 단계들은 선택사항이다 - 시도해보거나 그냥 읽고 넘어갈 수 있다.

Vercel에 배포한 후, 할 수 있으면 다음을 시도해봐라:

  • 앱에 새 branch를 만들어라.
  • 변경사항을 만들고 깃허브에 푸시해라.
  • pull request를 만들어라.

pull reuqest 페이지에서 vercel 봇의 댓글이 표시되어야한다.

댓글 안의 Preview URL을 클릭해봐라. 방금 변경한 내용이 표시되어야 한다.

pull reuqest가 열려있으면, Vercel은 자동으로 푸시할때마다 해당 branch에 대한 preview deployment를 생성한다. preview URL은 항상 최신 배포를 가리킨다.

이 preview URL을 팀원과 공유하고 즉시 피드백을 얻을 수 있다.

preview deployment가 양호하면, main에 머지해라. 이렇게 하면, Vercel이 자동으로 production 배포를 만든다.

Develop, Preview, Ship

방금 DPS라고 하는 워크플로우를 살펴봤다: Develop, Preview, Ship

  • Develop: Next.js로 코드를 작성했고 핫 리로딩 기능의 이점을 활용하기 위해 Next.js 개발 서버를 사용했다.
  • Preview: 깃허브의 branch에 변경사항을 푸시했고, Vercel이 URL을 통해 이용가능한 preview deployment를 만들었다. 피드백을 위해 이 preview URL을 다른사람들과 공유할 수 있다. 코드 리뷰 외에도, deployment preview를 수행할 수 있다.
  • Ship: production으로 배송하기 위해 pull request를 main에 머지했다.

Next.js 앱을 개발할때 이 워크플로우를 사용하는 것을 강력하게 추천한다 - 앱을 더 빠르게 반복하는데 도움이 된다.

Other Hosting Options

Next.js는 Node.js를 지원하는 모든 호스팅 프로바이더들에 배포될 수 있다.

지금까지 명령을 따라왔다면, package.json이 다음과 같은 buildstart 스크립트를 가지고 있어야한다:

{
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  }
}

자체 호스팅 프로바이더에서, build 스크립트를 한 번 실행하면 .next 폴더에 production 애플리케이션이 빌드된다.

npm run build

빌드 후, start 스크립트는 정적으로 생성된 페이지와 서버-사이드에서 렌더링된 페이지 및 API Route를 모두 제공하는 하이브리드 페이지를 지원하는 Node.js 서버를 시작한다.

npm run start

| Tip: package.json에서 다음과 같이 수정하여 PORT 매개변수를 허용하도록 start 스크립트를 커스텀할 수 있다: "start": "next start -p $PORT".

Finally

기본 강의를 끝낸걸 축하한다! 다음은 몇가지 권장 단계들이다.

Use TypeScript with Next.js

TypeScript 사용을 선호한다면, 여기서 Next.js에서 TypeScript 사용하는 법에 대해 배울 수 있다.

What to Learn Next

더 배우고 싶다면 문서를 살펴봐라. 특히 다음 페이지들이 흥미로울 수 있다:

  • Data Fetching: data fetching에 대해 깊게 알아보기.
  • Environment Variables: 내장 지원 환경 변수들에 대해 알아보기.
  • Search Engine Optimization: Next.js 애플리케이션에서 SEO 최적화하는 방법에 대해 알아보기.

0개의 댓글