처음부터 React로 완전한 웹 애플리케이션을 만들기 위해선 고려해야할 중요한 세부사항이 많다:
프레임워크는 이러한 문제들을 해결할 수 있다. 그러나 그러한 프레임워크는 올바른 수준의 추상화를 가져야한다 - 그렇지 않으면 그다지 유용하지 않을 것이다. 또한 좋은 "개발자 경험"이 있어야 당신과 당신의 팀이 코드를 작성하는 동안 놀라운 경험을 만날 수 있다.
React 프레임워크인 Next.js에 들어가보자. Next.js는 위의 모든 문제에 대한 해결책을 제공한다. 그러나 더 중요한 것은, 당신과 당신의 팀이 React 애플리케이션을 만드는것을 성공시키는 것이다.
Next.js는 최고의 개발자 경험과 다음과 같은 많은 빌트인 기능들을 제공한다:
Next.js는 세계에서 가장 큰 브랜드를 포함하여, 수만개의 production 관련 웹사이트 및 웹 애플리케이션에서 사용된다.
이 코스는 Next.js를 어떻게 시작하는지 설명한다.
이 튜토리얼에서, 매우 간단한 블로그 앱을 만들어보면서 Next.js 기초를 배울 수 있다. 다음은 최종 결과물 예시이다: source
| 이 튜토리얼은 JavaScript와 React에 대한 기초 지식이 있다고 가정한다. React 코드를 한번도 작성해본 적이 없다면, 먼저 공식 React 튜토리얼을 확인해라.
먼저, 개발환경이 준비되었는지 확인해라.
| 윈도우를 사용한다면, 윈도우용 Git을 다운로드하고 함께 제공되는 Git Bash를 사용하는 것이 좋다. 이 자습서에서는 UNIX 관련 명령을 지원한다. Windows Subsytem for Linux (WSL)을 사용해도 된다.
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
플래그를 통해 이 템플릿을 사용한다.
작동하지 않으면 이 페이지를 확인해라.
이제 nextjs-blog
라는 새 디렉토리를 가진다. 안으로 cd
하자:
cd nextjs-blog
이제, 다음 명령어를 실행해라:
npm run dev
이렇게 하면 포트 3000에서 Next.js 앱의 "개발 서버" (추후 설명 예정)가 시작된다.
잘 실행되는지 살펴보자. http://localhost:3000을 브라우저에서 열어봐라.
http://localhost:3000에 접근하면 다음과 같은 페이지를 확인할 수 있다. 이는 Next.js에 관한 몇가지 유용한 정보들을 보여주는 시작 템플릿 페이지이다.
이제 이 페이지를 수정해보자!
시작 페이지를 수정해보자.
pages/index.js
를 열어라.<h1>
태그 아래에 "Welcome to"라 적혀있는 텍스트를 찾아 "Learn"으로 변경해라.파일을 저장하는 즉시, 브라우저는 새 텍스트로 페이지를 자동으로 업데이트한다:
Next.js 개발 서버는 Fast Refresh가 활성화되어있다. 파일을 변경하면 Next.js는 변경사항을 브라우저에 거의 즉시 적용한다. 더 이상 새로고침이 필요하지 않다! 이는 앱을 빠르게 반복하는데 효과적이다.
| 개발 서버를 계속 유지해야한다. 하지만 재시작하고 싶다면, 서버를 중단하기 위해 Ctrl + c
를 입력해라.
지금까지, 우리가 만든 Next.js 앱은 오직 한 페이지만 가진다. 웹사이트들과 웹 애플리케이션들은 일반적으로 많은 다른 페이지들을 가진다.
우리의 애플리케이션에 페이지를 추가하는 방법에 대해 알아보자.
[Link](https://nextjs.org/docs/api-reference/next/link)
컴포넌트를 이용한 페이지들 간 클라이언트-사이드 네비게이션을 활성화하는 방법Next.js에서, 페이지는 pages
디렉토리에서 export한 리액트 컴포넌트이다.
페이지들은 파일 이름을 기반으로 라우트와 연결된다. 예를들어, 개발중에서:
pages/index.js
는 /
경로와 연결된다.pages/posts/first-post.js
는 /posts/first-post
경로와 연결된다.이미 pages/index.js
파일은 있으므로, 어떻게 동작하는지 확인하기 위해 pages/posts/first-post.js
파일을 만들어보자.
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를 허용한다.
<Link>
먼저, 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 버전 이상부터는 필수가 아니다.
잘 동작하는지 살펴보자. 이제 각 페이지를 연결하여 앞뒤로 이동할 수 있다.
Link
컴포넌트는 같은 Next.js 앱의 두 페이지 간 클라이언트-사이드 네비게이션을 가능하게 한다.
클라이언트-사이드 네비게이션은 페이지 전환이 브라우저에서 일어나는 기본 네비게이션보다 더 빠른, JavaScript를 이용하여 일어남을 의미한다.
확인할 수 있는 간단한 방법은 다음과 같다:
<html>
의 background
CSS 프로퍼티를 yellow
로 변경해라.이는 브라우저가 전체 페이지를 로드하지 않고, 클라이언트-사이드 네비게이션이 작동하고 있음을 나타낸다.
<Link href="...">
대신 <a href="...">
를 사용하면, 링크 클릭 시 브라우저가 전체 새로고침을 실행하기 때문에 배경색이 초기화된다.
Next.js는 자동으로 코드 스플리팅을 수행하므로, 각 페이지는 그 페이지에 필요한 항목만 로드한다. 즉, 홈페이지가 렌더링될때, 다른 페이지에 대한 코드는 처음에 제공되지 않는다.
이렇게 하면 수백개의 페이지가 있어도 홈페이지가 빠르게 로드된다.
요청한 페이지에 대한 코드만 로딩하는 것은, 페이지가 격리됨을 의미하기도 한다. 특정 페이지에서 오류가 발생해도, 애플리케이션의 나머지 부분은 계속 동작한다.
게다가, Next.js의 production 빌드에서, 브라우저의 뷰포트에 Link
컴포넌트가 나타날때마다, Next.js는 백그라운드에서 자동으로 링크된 페이지의 코드를 prefetch한다. 링크를 클릭할때마다, 목적지 페이지에 대한 코드는 이미 백그라운드에 로드되어 있고, 페이지 전환은 거의 즉각적으로 이루어진다!
Next.js는 코드 스플리팅, 클라이언트-사이드 네비게이션, prefetching (production에서)를 통해 애플리케이션을 자동으로 최적화하여 최상의 성능을 제공한다.
pages
아래에 파일로 경로를 만들고 빌트인 Link
컴포넌트를 사용한다. 라우팅 라이브러리가 필요하지 않다.
next/link
API 레퍼런스와 routing documentation의 일반적인 라우팅에서 Link
컴포넌트에 대해 더 알아볼 수 있다.
우리가 추가한 두번째 페이지는 스타일링이 없다. 이 페이지를 꾸미기 위해 CSS를 추가해보자.
Next.js는 CSS와 Sass를 자체적으로 지원한다. 이 강의에서는 CSS를 사용할 것이다.
이 강의는 이미지와 <title>
태그와 같은 페이지 메타데이터 등의 정적 asset들을 Next.js가 어떻게 다루는지도 알아볼 것이다.
<head>
내부 내용을 커스터마이즈 하는 방법pages/_app.js
에 전역 CSS를 추가하는 방법| Next.js 스타일링에 대한 자세한 문서를 확인하고 싶다면, CSS documentation을 봐라.
Next.js는 이미지와 같은 정적 asset들을 최상위 public
디렉토리 아래에 제공할 수 있다. public
내부의 파일들은 pages
와 비슷하게 애플리케이션의 루트에서 참조할 수 있다.
public
디렉토리는 또한 robots.txt
, Google Site Verification 및 다른 모든 정적 asset에 유용하다. 자세한 내용은 Static File Serving에 대한 문서를 참고해라.
먼저, 프로필 사진을 검색해보자.
.jpg
포맷으로 프로필 사진을 다운로드해라. (또는 이 파일을 사용해라)public
디렉토리 안에 images
디렉토리를 생성해라.public/images
디렉토리 안에 profile.jpg
사진을 저장해라.public
디렉토리 안에 사용하지 않는 SVG 로고 파일을 제거해도 된다.일반 HTMl을 사용하면, 다음과 같이 프로필 사진을 추가해야 한다.
<img src="/images/profile.jpg" alt="Your Name"/>
그러나, 이는 다음을 수동으로 처리해야 함을 의미한다:
등등. 대신, Next.js는 이들을 처리하기 위해 바로 사용가능한 Image
컴포넌트를 제공한다.
next/image
는 모던 웹을 위해 진화된 HTML <img>
요소의 익스텐션이다.
Next.js는 기본적으로 이미지 최적화도 지원한다. 이를 통해, 브라우저에서 지원하는 경우 WebP와 같은 최신 포맷으로 이미지 크기 조정, 최적화 및 이미지 제공이 가능하다. 이렇게 하면 더 작은 뷰포트를 가진 디바이스에 큰 이미지가 전달되는 것을 막을 수 있다. 또한 Next.js가 향후 이미지 형식을 자동으로 채택하고 해당 형식을 지원하는 브라우저에 제공할 수 있다.
자동 이미지 최적화는 모든 이미지 소스에서 동작한다. 이미지가 CMS와 같은 외부 데이터 소스에서 호스팅되는 경우에도, 여전히 최적화할 수 있다.
빌드 타임에 이미지를 최적화하는 대신, Next.js는 유저 요청시에 수요에 맞게 이미지를 최적화한다. 정적 사이트 생성기 및 정적 전용 솔루션과 달리, 10개의 이미지를 전달하던 1천만개의 이미지를 전달하든 빌드 시간이 늘어나지 않는다.
이미지는 기본적으로 lazy load된다. 이는 뷰포트 외부의 이미지에 의해 페이지 속도가 저하되지 않음을 의미한다. 이미지는 뷰포트로 스크롤되면서 로드된다.
이미지는 항상 Google이 검색 랭킹에 사용할 Core Web Vital인 Cumulative Layout Shift를 피하는 방식으로 렌더링된다.
다음은 next/image
를 이용하여 프로필 사진을 표시하는 예제이다. height
와 width
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 레퍼런스를 확인해라.
<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할 수 있다.
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는 서드 파티 소스에서 추가된 모든 스크립트를 의미한다. 주로, 애널리틱스, 광고, 고객 지원 위젯과 같이 처음부터 작성할 필요가 없는 새로운 기능을 사이트에 도입하기 위해 서드-파티 스크립트가 포함된다.
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 코드와 관련하여 언제 로드될지 명확하게 알 수 없다. 만약 특정 스크립트가 렌더링을 블로킹하고 페이지 컨텐츠 로딩을 지연시킬 수 있는 경우, 성능에 큰 영향을 미칠 수 있다.
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 스타일링에 대해 이야기해보자.
보다시피, 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 모듈을 사용하면 고유한 클래스 이름을 자동으로 생성하며, 컴포넌트 레벨에서 CSS 스코프를 로컬로 지정할 수 있다. 이를 통해 클래스 이름 충돌에 대한 걱정 없이 다른 파일에서 동일한 CSS 클래스 이름을 사용할 수 있다.
CSS 모듈 외에도, 다음과 같은 다양한 방법으로 Next.js 애플리케이션을 스타일할 수 있다:
.css
, .scss
파일 import를 가능하게 하는 Sass이 강의에서는, Next.js에서 CSS 모듈과 Sass를 사용하는 방법에 대해 설명한다.
먼저, 모든 페이지에서 공유할 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>
);
}
이제, 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
클래스를 사용하려면, 다음이 필요하다:
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로 들어가면, 이제 텍스트가 중앙 컨테이너 안에 있음을 확인할 수 있다.
이제, 브라우저의 개발자 도구에서 HTML을 보면, Layout
컴포넌트에 의해 렌더링된 <div>
가 layout_container__...
과 같은 클래스 이름을 가지고 있음을 확인할 수 있다:
이것이 CSS Module이 하는 일이다: 고유한 클래스 이름을 자동으로 생성한다. CSS 모듈을 사용하는 한, 클래스 이름 충돌을 걱정할 필요가 없다.
게다가, Next.js의 코드 스플리팅은 CSS Module에서도 잘 작동한다. 각 페이지에 최소한의 CSS가 로드되도록 한다. 그 결과 번들 사이즈는 더 작아진다.
CSS Module은 빌드 타임에 JavaScript 번들에서 추출되고 Next.js에 의해 자동으로 로드되는 .css
파일을 생성한다.
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
파일에 대해 더 자세히 알아보자.
Important: pages/_app.js
을 추가할 때 개발 서버를 다시 시작해야 한다. Ctrl + C
를 눌러 서버를 중단하고 다시 실행하자:
npm run dev
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
를 업데이트했을 때 개발 서버를 재시작했는지 확인하라.
지금까지는, CSS 모듈과 같은 개념을 설명하기 위해 최소한의 React와 CSS 코드만 추가했다. 다음 강의인 data fetching으로 이동하기 전에, 우리의 페이지 스타일링과 코드를 다듬어보자.
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;
}
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에 대해 자세히 알아보자.
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
등)home
prophome
이 false
인 경우 하단에 있는 "Back to home" 링크next/image
가 포함된 이미지 추가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 강의로 넘어갈 준비가 되었다.
유용할만한 몇가지 스타일링 팁들이 있다.
| 그냥 이 섹션을 읽고 넘어가도 된다. 앱을 변경할 필요가 없다!
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>
);
}
아무런 설정없이, 바로 사용할 수 있는 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를 시작하려면, 예제를 참고해라.
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 문서를 참고해라.
우리는 블로그를 만들고 싶지만(원하는 결과물), 지금까진 아무런 블로그 컨텐츠를 추가하지 않았다. 이번 강의에서, 앱으로 외부 블로그 데이터를 가져오는 방법을 배울 것이다. 파일 시스템에 블로그 컨텐츠를 저장하지만, 컨텐츠가 아무곳이나(ex) 데이터베이스나 Headless CMS) 저장되어 있으면 작동할 것이다.
이번 강의에서, 다음을 배울것이다:
getStaticProps
와 인덱스 페이지로 외부 블로그 데이터를 import하는데 사용하는 방법getStaticProps
에 대한 몇가지 유용한 정보data fetching에 대해 이야기하기 전에, Next.js에서 가장 중요한 개념 중 하나인 pre-rendering에 대해 이야기해보자.
기본적으로, Next.js는 모든 페이지를 사전 렌더링한다. 즉 Next.js는 클라이언트-사이드 JavaScript에서 모든 작업을 수행하는 대신, 각 페이지의 HTML을 미리 생성한다. 사전 렌더링은 성능과 SEO 측면에서 더 좋은 결과를 가져다준다.
생성된 각 HTML은 해당 페이지에서 필요한 최소한의 JavaScript 코드와 연결된다. 페이지가 브라우저에 의해 로드되면, 해당 JavaScript 코드가 실행되고 페이지가 완전히 인터렉티브하게 만든다. (이 프로세스를 hydration이라 부른다.)
다음 단계를 수행하여 사전 렌더링이 일어나고 있는지 확인할 수 있다:
1. 브라우저의 JavaScript를 비활성화하기 (크롬에서 수행하는 방법)
2. 이 페이지에 엑세스해보기 (이 튜토리얼의 최종 결과)
앱이 JavaScript 없이 렌더링되는 것을 볼 수 있다. 이는 Next.js가 앱을 정적 HTML로 사전 렌더링하여, JavaScript 실행 없이 앱 UI를 볼 수 있게 하기 때문이다.
| Note: localhost
에서도 위의 단계를 시도할 수 있지만, JavaScript를 비활성화하면 CSS가 로드되지 않을 것이다.
만약 앱이 일반 React.js 앱이라면 (Next.js 없이), 사전 렌더링이 없으므로, JavaScript를 비활성화하면 앱을 볼 수 없다. 예를들어:
다음은 간단한 그래픽 요약이다:
이제, Next.js의 두가지 형태의 사전 렌더링에 대해 이야기해보자.
Next.js는 두가지 형태의 사전렌더링을 가진다: Static Generation과 Server-side Rendering이다. 차이점은 페이지에서 HTML을 생성하는 시점이다.
| 개발 모드에서(npm run dev
또는 yarn dev
를 실행할 때), 페이지들은 모든 요청마다 사전 렌더링된다. 이는 Static Generation에도 적용되어 개발하기가 더 쉽다. production으로 전환할 때, Static Generation은 모든 요청에 대해서가 아닌, 빌드 시 단 한번만 발생한다.
중요한점은, Next.js에서 각 페이지에서 사용할 사전 렌더링 방식을 선택할 수 있다는 점이다. 대부분의 페이지에서는 Static Generation을 사용하고 나머지 페이지에서는 Server-side Rendering을 사용하는 "하이브리드" Next.js 앱을 만들 수 있다.
Static Generation을 사용하면, 페이지를 한번 빌드하고 CDN에서 제공할 수 있으므로 모든 요청마다 페이지를 서버에서 렌더링하는 것보다 훨씬 빠르기 때문에, 가능하면 Static Generation ( 데이터 포함 및 제외)을 사용하는것을 추천한다.
다양한 유형의 페이지에서 Static Generation을 사용할 수 있다, 다음을 포함해서:
스스로에게 물어봐라: "유저 요청보다 먼저 이 페이지를 미리 렌더링할 수 있는가?". 만약 대답이 yes라면, Static Generation을 선택해야 한다.
반면에, Static Generation은 유저 요청보다 먼저 페이지를 사전 렌더링할 수 없는 경우 좋은 선택이 아니다. 아마도 페이지에 자주 변하는 데이터가 표시되고, 모든 요청마다 페이지의 컨텐츠가 변경될 것이다.
이러한 경우에는, Server-side 렌더링을 사용할 수 있다. 속도는 느려지지만, 사전 렌더링된 페이지는 항상 최신상태이다. 또는 사전 렌더링을 생략하고 클라이언트측 JavaScript를 사용하여 자주 업데이트 되는 데이터를 채울 수 있다.
이 강의에서는, Static Generation에 집중할 것이다. 다음 페이지에서, 데이터가 있거나 없는 Static Generation에 대해 이야기해보자.
Static Generation은 데이터가 있든 없든 수행할 수 있다.
지금까지, 우리가 만든 모든 페이지들은 외부 데이터를 가져올 필요가 없다. 이 페이지들은 앱이 production으로 빌드될 때 자동으로 정적으로 생성될 것이다.
그러나, 일부 페이지의 경우, 일부 외부 데이터를 처음에 가져오지 않고서는 HTML을 렌더링하지 못할 수 있다. 빌드 시에 파일 시스템에 접근해야하거나, 외부 API를 가져와야 하거나, 데이터베이스를 쿼리해야할 수 있다. Next.js는 이런 경우를 바로 지원한다 - Static Generation with data
getStaticProps
어떻게 동작할까? 음, Next.js에서는, 페이지 컴포넌트를 export할 때, getStaticProps
라는 async
함수도 export할 수 있다. 이렇게 하면, 다음과 같다:
getStaticProps
는 production에서 빌드시에 실행된다. 그리고,...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
는 대신 각 요청마다 실행된다.
getStaticProps
해보면서 배우는것이 더 쉬우므로, 다음 페이지부터 블로그를 구현하는데 getStaticProps
를 사용할 것이다.
이 예제의 블로그 포스트는 애플리케이션 디렉토리의 로컬 마크다운 파일로 저장되므로(외부 데이터 소스에서 가져오지 않음), 파일 시스템에서 데이터를 읽어야 한다.
이 섹션에서, 파일 시스템에서 마크다운 데이터를 읽는 블로그를 만드는 단계를 살펴보자.
먼저, 루트 폴더에 **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.
| 각 마크다운 파일의 최상단에 title
과 date
를 포함하는 메타데이터 섹션이 있음을 알아챘을 것이다. 이를 YAML Front Matter라고 하며, gray-matter라는 라이브러리를 사용하여 파싱할 수 있다.
먼저, 각 마크다운 파일의 메타데이터를 파싱할 수 있는 gray-matter를 설치하자.
npm install gray-matter
다음으로, 파일 시스템의 데이터를 파싱하기 위한 유틸리티 함수를 만들자. 이 유틸리티 함수를 사용하여, 다음을 수행하고자 한다:
title
, date
, 파일 이름(포스트 URL에서 id
처럶 사용될 것)을 얻는다.루트 디렉토리에 최상위 디렉토리 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
는 각 마크다운 파일의 메타데이터를 파싱할 수 있는 라이브러리이다.lib
폴더에는 pages
폴더와 같이 할당된 이름이 없으므로, 아무 이름이나 지정할 수 있다. lib
또는 utils
를 사용하는것이 관습이다.이제 블로그 데이터가 파싱되었으므로, index 페이지(pages/index.js
)에 추가해야한다. 이는 [getStaticProps()](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation)
라는 data fetching 메소드를 사용하여 수행할 수 있다. 다음 섹션에서, getStaticProps()
를 어떻게 구현하는지 알아보자.
Next.js에는 두가지 형태의 사전 렌더링이 있다: Static Generation과 Server-side Rendering. 차이점은 페이지에서 HTML을 생성하는 시점이다.
중요한점은, Next.js에서 각 페이지에서 사용할 사전 렌더링 방식을 선택할 수 있다는 점이다. 대부분의 페이지에서는 Static Generation을 사용하고 나머지 페이지에서는 Server-side Rendering을 사용하는 "하이브리드" Next.js 앱을 만들 수 있다.
getStaticProps()
)이제, getSortedPostsData
import를 추가하고 pages/index.js
의 getStaticProps
내부에서 이를 호출해야한다.
에디터에서 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
에 대해 알아야할 몇가지 필수 정보들이다.
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 번들에도 포함되지 않는다. 즉 브라우저로 보내지 않고도 직접 데이터베이스 쿼리와 같은 코드를 작성할 수 있음을 의미한다.
npm run dev
또는 yarn dev
)에서, getStaticProps
는 모든 요청에서 실행된다.getStaticProps
는 빌드시에 실행된다. 그러나, 이 동작은 getStaticPaths
에 의해 반환된 fallback
키를 이용하여 향상될 수 있다.빌드 시 실행되기 때문에, 쿼리 파라미터나 HTTP 헤더와 같이 요청시에만 사용할 수 있는 데이터를 사용할 수 없다.
getStaticProps
는 오직 page에서만 export될 수 있다. 페이지가 아닌 파일에서는 export할 수 없다.
이 제한의 이유 중 하나는 페이지가 렌더링되기 전에 React는 필요한 데이터를 모두 가지고 있어야 하기 때문이다.
Static Generation은 빌드시 한번 발생하므로, 자주 업데이트되거나 모든 유저 요청시마다 변경되는 데이터에는 적합하지 않다.
이와 같이, 데이터가 변경될 가능성이 있는 곳에서는, Server-side 렌더링을 사용할 수 있다. 다음 섹션에서 서버 사이드 렌더링에 대해 자세히 알아보자.
빌드 시가 아닌 요청시에 데이터를 가져와야하는 경우, Server-side 렌더링을 시도할 수 있다:
서버 사이드 렌더링을 사용하려면, 페이지에서 getStaticProps
대신 getServerSideProps
를 export해야한다.
getServerSideProps
여기 getServerSideProps
에 대한 시작코드가 있다. 우리의 블로그 예제에는 필요하지 않으므로, 구현하지 않는다.
export async function getServerSideProps(context) {
return {
props: {
// props for your component
},
};
}
getServerSideProps
는 요청 시 호출되기 때문에, 파라미터(context
)는 요청마다의 매개변수가 포함된다.
getServerSideProps
는 요청 시 데이터를 가져와야하는 페이지를 사전 렌더링해야하는 경우에만 사용해야한다. 첫번째 바이트까지의 시간 (TTFB)는 서버가 모든 요청에 대해 결과를 계산해야하고, 추가 구성 없이 CDN에서 결과를 캐시할 수 없기 때문에 getStaticProps
보다 느리다.
데이터를 사전 렌더링할 필요가 없는 경우, 다음 전략(Client-side Rendering)을 사용할 수도 있다.
이 접근방식은 예를들자면 사용자 대시보드에 적합하다. 대시보드는 비공개의, 유저별 페이지이므로, SEO와 관련이 없으며, 페이지는 사전렌더링될 필요가 없다. 데이터는 자주 변경되므로 요청시마다 데이터를 가져와야한다.
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 문서에서 getStaticProps
및 getServerSideProps
에 대한 자세한 정보를 얻을 수 있다.
우리는 블로그 데이터로 index 페이지를 채웠지만, 아직 개별 블로그 페이지를 만들지 않았다(원하는 결과). 우리는 이러한 페이지의 URL이 블로그 데이터에 의존하기를 원한다, 즉 동적 라우트을 사용해야한다.
이번 강의에서, 다음을 배우게 된다:
getStaticPaths
를 사용하여 동적 라우트 페이지를 정적으로 생성하는 방법getStaticProps
를 작성하는 방법remark
를 이용하여 마크다운을 렌더링하는 방법이전 강의에서, 페이지 컨텐츠가 외부 데이터에 의존하는 경우를 다뤘다. 우리는 index 페이지를 렌더링하는데 필요한 데이터를 가져오기 위해 getStaticProps
를 사용했다.
이 강의에서는, 각 페이지 경로가 외부 데이터에 의존하는 경우에 대해 이야기 할 것이다. Next.js를 사용하면 외부 데이터에 의존하는 경로가 있는 페이지를 정적으로 생성할 수 있다. 이는 Next.js에서 동적 URL이 가능하게 한다.
우리의 경우, 블로그 포스트에 대한 동적 라우트을 만들고 싶다:
/posts/<id>
경로를 갖길 원한다. 여기서 <id>
는 최상위 posts
디렉토리 아래의 마크다운 파일의 이름이다.ssg-ssr.md
및 pre-rendering.md
파일이 있으므로, 우리는 경로가 /posts/ssg-ssr
와 /posts/pre-rendering
이 되기를 원한다.다음 단계를 따라서 이를 수행할 수 있다. 아직 이러한 변경을 할 필요는 없다 - 다음 페이지에서 모든 작업을 수행할 것이다.
먼저, 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
로 블로그 포스트에 필요한 데이터를 가져온다. getStaticProps
는 id
를 포함하는 (파일 이름이 [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
}
다음은 방금 이야기한 내용을 그래픽으로 요약한 것이다:
먼저 파일을 세티앻보자:
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,
};
}
paths
는 pages/posts/[id].js
에 의해 정의된 파라미터들을 포함하여, getAllPostIds()
에 의해 반환된 알려진 경로의 배열을 포함한다. paths
키 문서에서 더 자세히 알아봐라.fallback: false
를 무시하자 - 추후에 설명할 것이다.거의 다 끝났지만 - 아직 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으로 반환하기 위해 getStaticProps
의 getPostData
함수를 사용하고 있다.
이제, postData
를 사용하기 위해 Post
컴포넌트를 수정하자. pages/posts/[id].js
에서 export된 Post
컴포넌트를 다음 코드로 바꾸자:
export default function Post({ postData }) {
return (
<Layout>
{postData.title}
<br />
{postData.id}
<br />
{postData.date}
</Layout>
);
}
이게 끝이다! 다음 페이지들을 방문해보자:
동적 라우트를 성공적으로 생성했다.
오류가 발생한다면, 파일의 코드가 올바른지 확인해라:
pages/posts/[id].js
는 다음과 같아야 한다.lib/posts.js
는 다음과 같아야 한다.우리가 한 일에 대한 그래픽 요약은 다음과 같다:
아직 블로그 마크다운 컨텐츠를 표시하지 않았다.
마크다운 컨텐츠를 렌더링하기 위해, 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
를 사용해야 하기 때문에 getPostData
에 async
키워드를 추가했다. async/await
는 데이터를 비동기적으로 가져올 수 있게 한다.
즉 getPostData
를 호출할때 await
를 사용하려면 pages/posts/[id].js
의 getStaticProps
를 수정해야한다:
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>
);
}
다음 페이지들을 다시 방문해보자:
거의 끝났다! 다음으로 각 페이지를 다듬어보자.
title
to the Post Pagepages/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>
);
}
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를 확인할 수 있다.
마지막으로, 이전에 추가한 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 페이지를 다듬고 마무리하자!
다음으로, index 페이지를 업데이트하자 (pages/index.js
). Link
컴포넌트를 사용하여 각 포스트 페이지로의 링크를 추가해야한다.
pages/index.js
를 열고 파일 상단에 Link
및 Date
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에 대한 링크를 가진다:
| 잘 작동하지 않으면, 코드가 다음과 같은지 확인해라.
이게 끝이다! 강의를 마무리하기 전에, 동적 라우트에 대한 몇가지 팁에 대해 알알보자.
동적 라우트에 대해 알아야할 몇가지 필수 정보이다.
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,
},
};
});
}
npm run dev
또는 yarn dev
)에서, getStaticPaths
는 모든 요청에서 실행된다.getStaticPaths
는 빌드 시에 실행된다.getStaticPaths
에서 fallback: false
를 반환한것을 기억해보자. 이는 무엇을 의미할까?
만약 fallback
이 false면, getStaticPaths
에서 반환하지 않은 모든 경로는 404 페이지가 된다.
만약 fallback
이 true면, getStaticProps
의 동작이 바뀐다:
getStaticPaths
에서 반환된 경로는 빌드 시 HTML로 렌더링된다.만약 fallback
이 blocking
이면, 새 경로는 getStaticProps
로 서버-사이드 렌더링되고, 향후 요청을 위해 캐시되므로 경로 당 한번만 발생한다.
강의 내용을 벗어나지만, fallback
문서에서 fallback: true
와 fallback: 'blocking'
에 대해 자세히 알아볼 수 있다.
괄호 안에 점 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.id
는 getStaticProps
의 배열이 된다:
export async function getStaticProps({ params }) {
// params.id will be like ['a', 'b', 'c']
}
자세한 내용은 catch all routes 문서를 참고해라.
Next.js 라우터에 엑세스하려면, next/router
에서 [useRouter](https://nextjs.org/docs/api-reference/next/router#userouter)
hook을 import하면 된다.
커스텀 404 페이지를 만들기 위해, pages/404.js
를 만들자. 이 파일은 빌드 시 정적으로 생성된다.
// pages/404.js
export default function Custom404() {
return <h1>404 - Page Not Found</h1>;
}
자세한 내용은 Error 페이지 문서를 참고해라.
getStaticProps
와 getStaticPaths
를 설명하기 위해 몇가지 예제를 만들었다 - 더 자세히 배우려면 소스 코드를 확인해봐라:
Next.js는 API 라우트를 지원하므로, API 엔드포인트를 Node.js 서버리스 함수로 쉽게 만들 수 있다. 비록 우리의 블로그 앱에는 필요하지 않지만, 이번 강의에서 사용방법에 대해 간략하게 설명한다.
이번 강의에서, 다음을 배운다:
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라고도 함)로 배포할 수 있다.
시도해보자. 다음 코드를 사용하여 pages/api
안에 hello.js
파일을 만들자:
export default function handler(req, res) {
res.status(200).json({ text: 'Hello' });
}
http://localhost:3000/api/hello에 접속해보자. {"text":"Hello"}
를 확인할 수 있다. 참고로:
req
는 http.IncomingMessage 인스턴스와, 미리 만들어진 미들웨어들이다.res
는 http.ServerResponse 인스턴스와, 일부 헬퍼 함수들이다.다음은 API Route에 대해 알아야 할 몇가지 필수 정보들이다.
getStaticProps
or getStaticPaths
getStaticProps
또는 getStaticPaths
에서 API Route를 가져오면 안된다. 대신, getStaticProps
또는 getStaticPaths
에 직접 서버-사이드 코드를 작성해라 (또는 헬퍼 함수를 호출해라)
이유는 다음과 같다: getStaticProps
와 getStaticPaths
는 오직 서버-사이드에서만 실행되고 절대 클라이언트-사이드에서 실행되지 않는다. 게다가 이러한 함수들은 브라우저용 JS 번들에도 포함되지 않는다. 즉 직접 데이터베이스에 쿼리하는 코드를 브라우저로 보내지 않고도 작성할 수 있다. 자세한 내용은 Writing Server-Side code 문서를 읽어보자.
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...
}
Static Generation은 headless CMS에서 데이터를 가져올 때 유용하다. 그러나, headless CMS에서 draft를 작성하고 페이지에서 즉시 draft를 미리보고싶을때는 적합하지 않다. Next.js가 빌드 시 말고 요청 시에 이러한 페이지들을 렌더링하고, 퍼블리시된 컨텐츠 대신 draft 컨텐츠를 가져오길 원할 것이다. 이러한 특정한 경우에만 Next.js가 Static Generation을 우회하길 원할 것이다.
Next.js는 위의 문제를 해결하기 위해 Preview Mode라는 기능을 가지고 있으며, 이는 API Route를 활용한다. 자세한 내용은 Preview Mode 문서를 참고해라.
API Route는 일반 페이지와 마찬가지로 동적일 수 있다. 자세한 내용은 Dynamic API Routes 문서를 참고해라.
마지막 기초 강의에서, 우리의 Next.js 앱을 production으로 배포할 것이다.
Next.js를 Next.js 개발자들에 의해 만들어진 플랫폼인 Vercel에 배포하는 방법을 배울 것이다. 또한 다른 배포 옵션들에 대해서 이야기할 것이다.
| 준비물: Github 계정이 필요하다.
이번 강의에서, 다음을 배우게 된다:
배포하기 전에, 아직 푸시하지 않았다면 Next.js 앱을 깃허브에 푸시하자. 이렇게 하면 배포가 더 쉬워진다.
nextjs-blog
라는 새 레포지토리를 만든다.그런 다음:
Github에 푸시하기 위해 다음 명령을 실행해라 (<username>
을 깃허브 username으로 변경해라):
git remote add origin https://github.com/<username>/nextjs-blog.git
git push -u origin main
GitHub 레포지토리가 준비되면 다음 페이지로 넘어가자.
Next.js를 production으로 배포하는 가장 쉬운 방법은 Next.js 개발자들에 의해 만들어진 Vercel 플랫폼을 사용하는 것이다.
Vercel은 headless content, 커머스 또는 데이터베이스와 통합하도록 만들어진 정적 및 하이브리드 애플리케이션을 위한 서버리스 플랫폼이다. 프론트엔드 팀이 성능은 기본이고 즐거운 사용자 경험을 쉽게 deploy, preview, ship할 수 있도록 한다. 무료로 시작할 수 있다 - 신용카드가 필요하지 않다.
https://vercel.com/singup으로 가서 Vercel 계정을 만든다. Github로 계속하기를 선택하고 가입 절차를 진행한다.
nextjs-blog
repository가입했으면, nextjs-blog
레포지토리를 Vercel에 import한다. 여기에서 할 수 있다: https://vercel.com/import/git
nextjs-blog
를 import해라.배포하면, Next.js 앱이 빌드되기 시작한다. 1분 안에 끝나야 한다.
완료되면, 배포 URL들을 받을 수 있다. URL 중 하나를 클릭하면 Next.js 시작 페이지를 실시간으로 확인할 수 있다.
축하한다! 방금 Next.js를 production에 배포했다. 다음 페이지에서는, Vercel 및 권장 워크플로우에 대한 자세한 내용을 살펴보자.
Vercel은 Next.js 개발자가 만들고 Next.js를 최고 수준으로 지원한다. Next.js 앱을 Vercel에 배포하면, 다음이 기본적으로 발생한다:
Vercel은 다음과 같은 더 많은 기능들을 가진다:
Vercel 문서에서 플랫폼에 대해 더 자세히 알아볼 수 있다.
| 아래 단계들은 선택사항이다 - 시도해보거나 그냥 읽고 넘어갈 수 있다.
Vercel에 배포한 후, 할 수 있으면 다음을 시도해봐라:
pull reuqest 페이지에서 vercel
봇의 댓글이 표시되어야한다.
댓글 안의 Preview URL을 클릭해봐라. 방금 변경한 내용이 표시되어야 한다.
pull reuqest가 열려있으면, Vercel은 자동으로 푸시할때마다 해당 branch에 대한 preview deployment를 생성한다. preview URL은 항상 최신 배포를 가리킨다.
이 preview URL을 팀원과 공유하고 즉시 피드백을 얻을 수 있다.
preview deployment가 양호하면, main
에 머지해라. 이렇게 하면, Vercel이 자동으로 production 배포를 만든다.
방금 DPS라고 하는 워크플로우를 살펴봤다: Develop, Preview, Ship
main
에 머지했다.Next.js 앱을 개발할때 이 워크플로우를 사용하는 것을 강력하게 추천한다 - 앱을 더 빠르게 반복하는데 도움이 된다.
Next.js는 Node.js를 지원하는 모든 호스팅 프로바이더들에 배포될 수 있다.
지금까지 명령을 따라왔다면, package.json
이 다음과 같은 build
및 start
스크립트를 가지고 있어야한다:
{
"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"
.
기본 강의를 끝낸걸 축하한다! 다음은 몇가지 권장 단계들이다.
TypeScript 사용을 선호한다면, 여기서 Next.js에서 TypeScript 사용하는 법에 대해 배울 수 있다.
더 배우고 싶다면 문서를 살펴봐라. 특히 다음 페이지들이 흥미로울 수 있다: