Next.js 14 최신버전 이해하기 및 Next.js 사용하기(1)
1. 라이브러리와 프레임워크의 차이
- 라이브러리 : 코드에 직접 사용하여 원하는 방식으로 동작하게끔 아키텍처를 자유롭게 설계할 수 있다.
- React : UI를 구축하기 위한 대표적인 라이브러리, 개발자가 원하는 방식으로 자유롭게 코드를 작성할 수 있다.
- 프레임워크 : 특정 규칙과 자동화된 기능을 제공해 코드의 흐름을 프레임워크가 주도한다.
- Next.js : 프레임워크로, 다양한 결정을 대신해 주고 자동화된 기능을 제공한다. 예를 들어, 데이터 페칭을 자동으로 처리해 주기 때문에 개발자는 이 규칙을 따르면서 코드를 작성해야 된다.
요약 : 라이브러리는 사용자가 코드를 주도하고, 프레임워크는 사용자의 코드를 주도합니다.
2. Next.js 라우팅 시스템
Next.js는 두 가지 라우터를 제공한다.
- Page Router : Next.js 12까지의 방식이며, `/page`디렉토리 구조를 따른다.
- `pages/index.js`파일은 루트 경로인 `/`에 매핑된다.
- App Router : Next.js 13부터 생긴 새로운 방식이며, `/app`디렉토리 구조를 따른다.
- `app/home/page/tsx`파일은 `/home` 경로에 매핑된다.
Next.js 14에서는 기존의 Page Router를 사용하더라도 에러가 발생하지 않으며, 한 번에 모든 것을 마이그레이션 할 필요 없다.
따라서 개발자는 상황에 따라 App Router로 전환하면 된다.
3. 라우팅과 데이터 페칭의 변화
Next.js 14에서는 라우팅과 데이터 페칭 방식이 크게 변경되었다.
- 라우팅 방식 : Page Router에서 App Router로 변경되었다.
- 데이터 페칭 방식 : 기존의 `getStaticProps`, `getServerSideProps`, `getStaticPaths` 함수는 더 이상 사용되지 않으며, 이 기능들은 클라이언트 및 서버 컴포넌트 내부에서 보다 명시적으로 처리할 수 있다.
4. 파일 시스템 기반 라우팅
Next.js는 파일 시스템을 통해 URL을 정의한다.
예를 들어, `/about-us`페이지는 `app/about-us/page.tsx`파일로 구성됩니다. 라우팅 경로를 지정하기 위해 파일 시스템에서 페이지를 표현하며, 반드시 `page.tsx`파일로 생성해야 된다.
app/
└── about-us/
└── page.tsx # /about-us 경로에 매핑
5. Next.js의 특별한 파일들
Next.js에서 중요한 역할을 하는 파일들은 다음과 같다.
- `page.tsx` : 특정 경로에 대한 페이지를 정의한다.
- 예를 들어, `app/contact/page/tsx`는 `contact`경로에 해당된다.
- `layout.tsx` : 페이지의 공통 레이아웃을 정의한다.
- 예를 들어, 모든 페이지에서 공통으로 사용되는 헤더와 푸터를 정의할 수 있다.
- `non-found.tsx` : 잘못된 경로로 접근 시 표시될 페이지를 정의한다. 사용자가 존재하지 않는 URL에 접근할 때 표시
- 예를 들어, /aaaa 이와 같이 잘못된 경로를 지정할 경우 나온다.
👉 layout에 대한 설명은 9번에서 자세히 설명예정
6. 렌더링 방식
rendering : react component를 가져와 브라우저가 이해할 수 있는 html로 변환하는 작업이다.
- CSR (Client Side Rendering) : 리액트의 기본 렌더링 방식으로, 브라우저에서 자바스크립트를 로드한 후 UI가 빌드된다.
- 단점으로 SEO 최적화에 불리하고, 첫 페이지 로딩 시 빈 화면이 나타날 수 있습니다.
- SSR (Server Side Rendering) : Next.js는 서버에서 미리 렌더링된 HTML을 제공한다. 이는 SEO에 유리하며, 사용자에게 빠르게 콘텐츠를 표시할 수 있습니다.
7. "use client"와 hydrate
- "use client" : "use client" 명령어를 component 최상단에 적으면 copmponent가 hydrate 된다.
- 이 명령어가 있는 컴포넌트는 서버에서 먼저 렌더링 된 후 클라이언트에서 hydrate 된다.
- 클라이언트 컴포넌트는 상호작용이 가능하며, 서버 컴포넌트는 기본적으로 서버에서만 렌더링 된다.
- hydration : 클라이언트 컴포넌트는 서버에서 렌더링 된 HTML을 받아 상호작용 가능하게 만든다. 예를 들어, 서버에서 렌더링 된 버튼이 클라이언트에서 클릭 이벤트를 처리할 수 있도록 변환된다.
(onClick, setState, useState 등을 사용해서 interactive 한 React component로 바꾼다.)
Next.js의 이전버전에는 모든 component가 hydrate가 됐었지만, 이제는 component를 hydrate 할 건지 선택할 수 있다.
component를 hydrate할 건지 선택한다는 점이 장점인 이유
사용자들의 다운로드하여야 하는 JavaScript 코드 양이 줄어든다. 페이지마다 모든 JavaScript 코드를 다운로드하지 않고, interactive 한 client component만 다루는 아주 작은 JavaScript파일을 다운로드 받게 된다.
"use client"는 client에서만 render한다는 의미가 아니다. (client에서도 render 된다는 의미)
모든 component는 backend에서 먼저 render될것이다.
"use client"명령어가 있든 없든 상관없이 client component, server component 모두 backend에서 먼저 server side render로 pre rende 된다.
- 모든 것을 server side render로 진행한다.
- 모든 것을 pre render 되어서 HTML로 변환된다.
- HTML이 사용자에게 넘어간다.
- client components만 hydrate 되고 interactive 하게 된다.
- 다른 component들은 interactive가 안된다.(interactive 해질 필요가 없기 때문)
요약 : 모든 컴포넌트는 서버에서 먼저 렌더링 되고, "use client"가 선언된 컴포넌트만 클라이언트에서 hydrate 됩니다.
8. 클라이언트와 서버 컴포넌트
- 클라이언트 컴포넌트 : 상호작용이 필요한 부분에서 사용되며, 반드시 "use client" 지시어를 선언해야 한다.
- 예를 들어, 버튼 클릭 이벤트가 필요한 경우, 이 컴포넌트는 클라이언트 컴포넌트로 정의해야 된다.
- 서버 컴포넌트 : 기본적으로 모든 컴포넌트는 서버에서 렌더링 된다. "use client"가 없는 컴포넌트는 서버 컴포넌트로 간주되며, 클라이언트 측에서 추가적인 자바스크립트 로딩이 필요하지 않다.
- 예를 들어, 정적인 콘텐츠를 렌더링 할 때 서버 컴포넌트를 사용할 수 있다.
9. Next.js에서의 렌더링 (6, 7, 8 정리 및 요약)
- Next.js는 backend에서 application을 pre render 한다. (Next.js는 SSR을 진행한다.)
- 모든 component를 가져다가 non interactive 한 HTML로 바꾸고 사용자에게 제공한다.
- "use client"명령어를 가진 component가 hydrate 된다. (hydrate는 interactive 된다는 것을 의미함)
요약 : Next.js는 서버에서 모든 페이지와 컴포넌트를 먼저 렌더링 하고, 사용자에게 완전한 HTML을 제공한다. 이후 React는 클라이언트 측에서 추가적으로 상호작용을 활성화한다.
"use client" 명령어를 사용하는 이유
모든 것에 hydrate 할 필요 없다. 만약 어떤 것들은 interactive하고 어떤 것들은 interactive하지 않으면, 예를 들어, UI의 일종의 컴포넌트만을 가지고 있다고 하면 hydrate할 필요없다. 즉 interactive 하게 만들어질 필요 없다는 의미이다.
11. 레이아웃 시스템
Next.js는 레이아웃 시스템을 통해 페이지 간의 공통 요소를 효율적으로 관리할 수 있다.
app/
├── about-us/
│ ├── layout.tsx # about-us 경로 전용 레이아웃, 해당 폴더안에서도 layout 파일 생성 가능
│ └── page.tsx
├── layout.tsx # 공통 레이아웃
├── not-found.tsx
└── page.tsx
next.js는 상위폴더로 이동하여 레이아웃이 있는지 확인하고 레이아웃이 있는 경우에는 해당 layout파일을 사용하여 하위 항목을 렌더링 한다. 레이아웃은 서로 상쇄되지 않고, 서로 대체하지 않는다.(서로 중첩되는 거다.)
12. 메타데이터 관리
Next.js에서 페이지의 메타데이터를 설정하는 객체이다. 메타데이터는 페이지의 헤더에 표시되며, `title`과 `description`등의 정보를 포함한다.
export const metadata = {
title: "Next.js",
description: "Generated by Next.js",
};
위 코드에서 `title`과 `description`은 페이지의 헤더 부분에 반영된다.
주요 특징 및 장점
- 중첩 가능 : 메타데이터는 레이아웃이 중첩되는 방식과 유사하게 중첩될 수 있지만, 실제로는 중첩되니 않고 병합된다. 즉, 페이지와 레이아웃에서 각각 정의된 메타데이터가 병합되어 최종적으로 사용된다.
- 제한 사항 : `metadata`는 `page`나 `layout`에서만 설정할 수 있으며, 일반 컴포넌트에서는 사용할 수 없다. 또한, 메타데이터는 서버 컴포넌트에서만 사용할 수 있다.
- 템플릿 사용 : 메타데이터는 템플릿을 통해 동적으로 설정할 수 있다.
// app/layout.tsx
import { Metadata } from "next";
export const metadata: Metadata = {
title: { // {}안은 문자열일 수도 있고, 템플릿을 포함하는 객체일 수도 있다.
template: "%s | Next Movies", // %s는 동적으로 변경되는 타이틀 자리
default: "Next Movies", // 기본값
},
description: "The best movies on the best framework",
};
// (home)/page.tsx
export const metadata = {
title: "Home",
};
// about-us/page.tsx
export const metadata = {
title: "About us",
};
각 페이지에서 title이 어떻게 나오는지
- / : Home | Next Movies
- /about-us : About us | Next Movies
- /dkdkdk : Next Movies
/about-us로 이동하면 프레임워크가 "About us" 타이틀을 가지고 온다. 이는 next.js가 layout.tsx를 확인하고 `%s`에 About us를 넣은 거다. 그래서 title이 About us | Next Movies가 나온다.
// app / not-found
export const metadata = {
title: "Not found",
};
이 코드를 추가하면 `/dkdkdk`의 title이 Not found로 나온다.
13. Router Groups
Next.js에서 경로들을 논리적으로 그룹화하여 관리할 수 있게 해 준다. 이를 통해 복잡한 레이아웃 구조를 더 유연하게 구성할 수 있다.
Route Groups의 주요 기능
- 루트 레이아웃을 사용하지 않고 여러 레이아웃을 선택적으로 사용할 수 있다.
- 특정 레이아웃을 중첩하지 않으려는 경우, 선택적으로 레이아웃을 적용할 수 있다.
app/
├── (home)/ # Route Group으로 지정된 폴더
│ └── page.tsx
├── about-us/
│ ├── layout.tsx
│ └── page.tsx
├── layout.tsx
├── not-found.tsx
`home`폴더를 `(home)`으로 묶으면, 이 폴더는 Route Group으로 처리된다. 이렇게 하면 폴더 이름이 URL 경로에 반영되지 않으며, URL에 영향을 주지 않으면서 논리적인 그룹화를 할 수 있다. 예를 들어 `/page.tsx`는 `/home/page.tsx`가 아닌 `/`로 접근하게 된다.
14. 동적 라우팅
Next.js는 대관호(`[]`)를 사용하여 동적 라우팅을 지원한다.
(movies)
|- movies
|- [id]
|- page.tsx
위 구조에서는 `movies/[id]`경로로 접속할 때 `[id]`부분이 동적으로 할당된다.
예를 들어, `movies/1111`로 접근하면 `[id]`에 `1111`이 들어간다.
이를 확인하기 위해 props를 출력해 보기
export default function MovieDetail(props) {
console.log(props); // { params: { id: '11111' }, searchParams: {} }
return <h1>Movie</h1>;
}
이 컴포넌트에서 `props`객체는 다음과 같은 두 가지 주요 요소를 포함합니다.
- `params` : 동적 경로 변수, URL의 `[id]`부분에 해당하는 값이 들어간다.
- `serchParams` : URL에 포함된 쿼리 문자열이 이 객체에 저장된다.
이 값들은 터미널에서는 출력되지만, 브라우저의 콘솔에서는 출력되지 않는다. 이는 해당 코드가 백엔드에서 실행되기 때문이다. (ssr이라는 의미!)
쿼리 문자열을 포함한 URL 예시
- `/movies/11111?regin=kr` : `{ params: { id: '11111' }, searchParams: { regin: 'kr' } }` 으로 출력
- `/movies/11111?regin=kr&page=2` : `{ params: { id: '11111' }, searchParams: { regin: 'kr', page: '2' } }` 으로 출력
응용하기
`params` 객체에서 `id`값을 추출하여 사용하는 방법
export default function MovieDetail({
params: { id },
}: {
params: { id: string };
}) {
console.log(id);
return <h1>Movie {id}</h1>;
}
`/movies/11111`로 접근하면 화면에 "Movie 11111"이 출력된다.
참고 자료 : 노머드코드, 챗GPT(읽어보면서 맞는 내용만 적어두었습니다.)