Next.js는 자바스크립트 웹 프레임워크 ,리액트에는 없는 SSR, SSG, ISR 등 제공
React는 기본적으로 클라이언트 사이드
에서만 동작
→ 따라서 SEO(검색 엔진 최적화)의 효과를 거의 볼 수 없음.
→ 브라우저가 전체 웹 애플리케이션 번들을 다운로드 하고 그 내용을 분석하고 코드를 실행하기 까지 오래걸림 (첫 화면을 표시하기까지 수 초가 소요)
⇒ 해결책) 서버에서 미리 렌더링을 해두는 방식
HTML페이지로 미리 렌더링을 해두고 브라우저가 이를 다운로드하여 즉각 화면에 표시
클라이언트에서 JS 번들을 다 받으면 그제서야 사용자가 웹 앱과 상호 작용
→ Next.js
Next.js가 제공하는 기능
Gastby → SSG만 제공. 성능이 놀랍도록 좋음. 데이터에 따라 동적으로 변하는 복잡한 웹 사이트는 만들 수 없음.
Razzle → SSR이 가능. 복잡한 설정을 추상화하고 단순하게 만들 수 있음. 프레임워크에 대한 지식이 없어도 됨.
Nuxt.js → Vue.js를 사용함. Next.js와 유사. 더 많은 설정을 필요로 함.
Next.js는 활동적인 커뮤니티를 가지고 있음.
React에서 제공하지 않는 여러 기능을 지원.
리액트 자바스크립트 라이브러리
↔ Next.js 프레임워크
SSR, SSG는 모두 Node.js에서 실행되기 때문에 fetch, window, document같은 웹 브라우저에서 제공하는 전역 객체나 canvas같은 HTML 요소에는 접근할 수 없음!
fs, child_process같은 Node.js에서만 사용할 수 있는 라이브러리나 API를 사용하려는 경우에는 서버 사이드 코드를 실행하거나 페이지 생성 시점에 해당 코드를 처리하는 방식 사용.
Vercel에서 제공하는 create-next-app을 사용.
$ npx create-next-app <app-name>
$ npx create-next-app <app-name> --typescript
yarn 패키지가 설치된 경우 yarn으로 프로젝트를 초기화함.
npm을 기본으로 사용하고 싶다면 --use-npm
옵션을 추가하면 됨.
Github에 원하는 보일러플레이트 코드를 다운로드 받아서 새 Next.js 프로젝트를 시작할 수 있음.
--example
옵션을 사용. 깃허브 레포 주소 입력.
$ yarn create next-app <app-name> --example https://github.com/ww8007/Next_boilerplate
Next.js에서 네비게이션은 pages 디렉토리를 사용하기 때문에 react-router 라이브러리를 설치할 필요 없음.
public 디렉토리는 모든 퍼블릭 페이지와 정적 콘텐츠가 있다.
ex) 이미지 파일, 폰트 …
pages
와 public
디렉토리는 필수이고 최상위 디렉터리에 필요한 디렉토리나 파일을 추가해도 괜찮다.
ex) components, utils 등…
tsconfig.json 파일 생성
npm run dev (yarn dev) 명령어 실행
표시된 메시지와 같이 필요한 패키지를 설치
yarn add -D typescript @types/react @types/node
최상위 디렉토리에 next-env.d.ts 파일 생성
{
"presets": ["next/babel"]
}
파일에 최소 위 내용은 있어야함 next/babel은 Vercel에서 Next.js 애플리케이션을 빌드하고 개발할 때 사용할 수 있는 설정을 미리 저장해둔 바벨 프리셋 해당 파일에 상황에 따라 프리셋, 플러그인 추가 가능module.exports = {
webpack: (config, options) => {
config.module.rules.push({
test: /\.js/,
use: [
options.defaultLoaders. babel,
// 이 부분의 내용은 예시이기 때문에
// 실제로 사용하면 애플리케이션이 작동하지 않습니다.
{
loader: “my-custom-loader", // 사용할 로더 지정
options: loaderOptions, // 로더의 옵션 지정
},
],
});
return config;
},
}
이런식으로 웹팩 설정을 추가하면 Next.js의 기본 설정과 합쳐진다.렌더링 전략이란? 웹 애플리케이션을 웹 브라우저에 제공하는 방법
Next.js에서는…
이 모든 렌더링 전략을 제공한다.
각 요청에 따라 서버에서 HTML 페이지를 동적으로 렌더링하고 웹 브라우저로 전송
서버에서 렌더링한 페이지에 스크립트 코드를 집어넣어서 웹 페이지를 동적으로 처리할 수 있다 → 하이드레이션
하이드레이션이란, 서버에서 생성한 HTML 페이지에 클라이언트 측에서 실행하는 자바스크립트 코드를 추가해서 애플리케이션 상태를 관리하고 렌더링하는 기법
→ 하이드레이션 덕분에 SPA 처럼 작동 (CSR과 SSR의 장점을 모두 가진다.)
Next.js는 기본적으로 빌드 시점에 정적으로 페이지를 만든다.
SSR에서 REST API Data Fetching
import axios from 'axios';
import { NameReturn } from '@/types/api';
import { GetServerSideProps } from 'next';
type IndexPageProps = {
name: string;
};
export default function IndexPage({ name }: IndexPageProps) {
return <>{name}</>;
}
export const getServerSideProps: GetServerSideProps = async () => {
const userRequest = await axios.get<NameReturn>(
'http://localhost:3000/api/hello'
);
const data = userRequest.data;
return {
props: {
name: data.name,
},
};
};
getServerSideProps
예약 함수를 사용한다.
getServerSideProps
비동기 함수를 export한다.getServerSideProps
함수를 호출하도록 만든다. 해당 함수 내의 코드는 항상 서버에서만 실행된다.getServerSideProps
함수는 props 라는 속성값을 갖는 객체를 반환한다.브라우저 전용 객체나 API(window, document)를 사용해야 하는 컴포넌트가 있다면 해당 컴포넌트를 반드시 브라우저에서 렌더링하도록 명시적으로 지정해야 한다.
서버에서 자바스크립트 번들을 클라이언트로 전송한 다음 렌더링을 시작
import React from 'react';
export default function CSRPage() {
const pEl = document.querySelector('#p');
pEl?.classList.add('class');
return (
<div>
<p id="p"></p>
</div>
)
}
해당 페이지에 접근하려고 하면 다음과 같은 에러가 나온다.
Next.js는 기본적으로 빌드 시점에 정적으로 페이지를 만들기 때문에 브라우저에서 제공하는 객체인 document에 접근할 수 없다.
따라서 위 코드를 사용하기 위해 useEffect
훅을 리액트 하이드레이션 이후 브라우저에서 실행하도록 만들어야 한다.
import React, { useEffect } from 'react';
export default function CSRPage() {
useEffect(() => {
const pEl = document.querySelector('#p');
pEl?.classList.add('class');
}, []);
return (
<div>
<p id="p"></p>
</div>
);
}
에러 없이 해당 코드를 잘 실행시킬 수 있다.
useEffect와 useState를 함께 써서 특정 컴포넌트를 정확히 클라이언트에서만 렌더링하도록 지정할 수 있다.
import dynamic from 'next/dynamic';
const Highlight = dynamic(
() >= import(' ../components/Highlight'),
{ ssr: false }
);
이 코드를 실행하면 Highlight 컴포넌트를 동적 임포트로 불러온다.
동적 임포트를 사용하면 Next.js는 해당 컴포넌트를 서버에서 렌더링하지 않는다.
일부 또는 전체 페이지를 빌드 시점에 미리 렌더링한다.
내용이 거의 변하지 않는 페이지는 정적 페이지 형태로 만들어서 제공하는 것이 좋음
SSR처럼 빌드 과정에 미리 렌더링해서 HTML 마크업 형태로 제공.
→ 리액트 하이드레이션 덕분에 이런 정적 페이지에서도 여전히 사용자와 웹 페이지 간의 상호 작용이 가능
Next.js에서는 이런 문제를 해결하기 위해 증분 정적 재생성 (ISR)을 제공
→ 어느 정도의 주기로 정적 페이지를 다시 렌더링하고 해당 내용을 업데이트할지 정할 수 있음
import { NameReturn } from '@/types/api';
import axios from 'axios';
import { GetStaticProps } from 'next';
type IndexPageProps = {
name: string;
};
export default function IndexPage({ name }: IndexPageProps) {
return <>{name}</>;
}
export const getStaticProps: GetStaticProps = async () => {
const response = await axios.get<NameReturn>(
'http://localhost:3000/api/hello'
);
const data = response.data;
return {
props: {
name: data.name,
},
revalidate: 600, // 10분
};
};
getStaticProps
함수는 빌드 과정에서 페이지를 렌더링할 때 이 함수를 호출, 다음 번 빌드 시점까지 더 이상 호출하지 않음
→ 콘텐츠를 바꾸려면 전체 웹 사이트를 새로 빌드해야 한다는 단점
이런 단점을 보완하기 위해 revalidate
옵션 사용
→ 요청이 발생할 때 어느 정도의 주기로 새로 빌드해야 하는지를 나타냄
위 코드는 revalidate를 10분으로 주었기 때문에 아래와 같은 프로세스로 동작함.
ISR을 최대한 지연시켜서 처리하려고 하므로 10분이 지난 후라도 페이지 요청이 없으면 페이지를 새로 빌드하지 않음.
현재까지는 ISR의 페이지를 재생성을 강제로 하는 방법은 없음.
revalidate값에 지정된 시간만큼 기다려야 함.
파일시스템 기반 페이지와 라우팅
pages
디렉토리 안의 모든 파일은 곧 애플리케이션의 페이지와 라우팅 규칙을 의미
웹 사이트에 새로운 글을 쓸때마다 수동으로 새 페이지를 만들지 않도록 posts 디렉토리 안에 [slug].tsx 파일을 만듬
[slug]는 경로 매개변수
로 사용자가 브라우저 주소창에 입력하는 값은 모두 가질 수 있음
http://localhost:3000/posts/2, http://localhost:3000/posts/sangmin 등 주소로 접근 가능
동적 라우팅 규칙을 중첩도 가능
위와 같은 주소로 접근 가능
페이지에서 경로 매개변수 사용
pages/greet/[name].tsx 파일 생성
import { GetServerSideProps, GetServerSidePropsContext } from 'next';
import React from 'react';
type GreetProps = {
name: string;
};
export default function Greet({ name }: GreetProps) {
return <div>{name}</div>;
}
export const getServerSideProps: GetServerSideProps = async ({
params,
}: GetServerSidePropsContext) => {
const name = params?.name;
return {
props: {
name,
},
};
};
useRouter
훅을 사용
import { useRouter } from 'next/router';
import React from 'react';
export default function Greet() {
const { query } = useRouter();
return <div>{query.name}</div>;
}
→ http://localhost:3000/greet/sangmin 으로 접근 시 query.name은 sangmin
Link
컴포넌트 이용
import Link from 'next/link';
import React from 'react';
export default function Navbar() {
return (
<div>
<Link href="/">Home</Link>
<Link href="/about">About</Link>
<Link href="/contact" preload={false}>Contact</Link>
</div>
);
}
Next.js는 모든 Link에 대해 연결된 부분 또는 페이지를 미리 읽어온다.
페이지의 링크를 클릭했을 때 브라우저는 해당 페이지를 화면에 표시하기 위해 필요한 모든 데이터를 이미 불러온 상태.
→ 해당 기능은 Link 컴포넌트에 preload={false}
속성을 전달하면 비활성화 가능
정적 자원은 /public 디렉토리 안에 저장하는 방식으로 클라이언트에 제공
Image
컴포넌트를 사용해서 이미지를 자동으로 최적화할 수 있음
→ 이미지를 WebP와 같은 최신 이미지 포맷으로 제공 가능
next.config.ts
파일의 images
속성에 서비스 호스트명을 추가하면 해당 도메인의 이미지를 불러올 때마다 Next.js가 자동으로 해당 이미지를 최적화함
_app.tsx
와 _document.tsx
→ 렌더링한 HTML을 클라이언트에 보내기 전 특정 작업을 처리
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
App
컴포넌트는 모든 페이지에서 같은 컴포넌트를 보여줄 때 사용 가능
모든 페이지에 Navbar
컴포넌트를 보여준다고 할 때 아래와 같이 작성 가능
import Navbar from '@/components/Navbar';
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Navbar />
<Component {...pageProps} />
</>
);
}
이외에도 다크모드를 구현하기위한 themeContext 추가 등 다양하게 커스텀 가능.
_app.tsx파일을 커스터마이징할 때는 다른 페이지처럼 getServerSideProps
나 getStaticProps
같은 함수를 사용해서 데이터를 불러오는 용도로 사용할 수 없다.
_app.tsx 파일의 주된 목적
_document.tsx 파일에서는 기본적인 HTML 태그인 , , 태그를 수정할 수 있음
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
_document.tsx도 _app.tsx와 마찬가지로 서버에서 데이터를 불러오는 함수를 사용할 수 없음.