NextJS 14 Version
1. 파일 기반 라우팅과 SSR에 대한 이해
src/
└── app/
├── about/
└── blog/
└── page.js ---> my-page.com/blog
└── page.js ---> my-page.com
page.js
는 layout.js
와 마찬가지로 보호된 파일명입니다.page.js
파일은 NextJS
에게 페이지를 렌더링해야 한다고 말해주는 것입니다.
1_1. src/app/page.js
/**
* 아래 리액트 컴포넌트는 소위 말해 서버에서 렌더링되는 컴포넌트이다.
* 이런 컴포넌트는 React 만으로는 만들 수 없고 NextJS 에서 수용되고 지원되는 형식입니다.
* 표면적으로는 일반 React 컴포넌트와 다를게 없습니다.
*
* 겉으로는 그다지 특별할게 없어보이지만, NextJS 는 이 컴포넌트를 서버에서 렌더링하고 컴포넌트 함수가 서버에서 실행되는 것을 보장합니다.
* 즉, 예를 들어 아래와 같이 console.log('executing...') 코드를 추가하면 클라이언트측 브라우저에선 해당 콘솔 내용이 보이지 않을 것입니다.
* 해당 콘솔은 백엔드 터미널창에서 볼 수 있습니다. (아래 사진 참고)
* 이를 통해 아래 컴포넌트는 서버에서 렌더링된다는 것을 알 수 있습니다.
* */
export default function Home() {
// console.log('executing...')
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<h1>NextJS 14 버전 공부 시작!</h1>
</main>
);
}
2. 파일 시스템을 통한 추가 경로 추가 (src/app/about/page.js)
NextJS
에서는 파일명이 중요하다.
파일명을 통해 NextJS
에게 이를 페이지로 만들고 싶다고 전달하기 때문입니다.
localhost:3000/about
이라는 페이지를 만드는 방법에 대해 살펴봅니다.
src/
└── app/
├── about/
├────── page.js ---> my-page.com/about
├── blog/
├───└── page.js ---> my-page.com/blog
└── page.js ---> my-page.com
위와 같이 src/app/about/page.js
파일을 추가하고
export default function About() {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<h1>About 페이지</h1>
</main>
);
}
위와 같이 작성해주면 아래와 같이 페이지가 제대로 렌더링됩니다.
3. 페이지 간 이동—옳고 그른 해결책 (앵커 태그 vs. Link 컴포넌트)
// src/app/page.tsx
+ import Link from "next/link";
/**
* 아래 리액트 컴포넌트는 소위 말해 서버에서 렌더링되는 컴포넌트이다.
* 이런 컴포넌트는 React 만으로는 만들 수 없고 NextJS 에서 수용되고 지원되는 형식입니다.
* 표면적으로는 일반 React 컴포넌트와 다를게 없습니다.
*
* 겉으로는 그다지 특별할게 없어보이지만, NextJS 는 이 컴포넌트를 서버에서 렌더링하고 컴포넌트 함수가 서버에서 실행되는 것을 보장합니다.
* 즉, 예를 들어 아래와 같이 console.log('executing...') 코드를 추가하면 클라이언트측 브라우저에선 해당 콘솔 내용이 보이지 않을 것입니다.
* 해당 콘솔은 백엔드 터미널창에서 볼 수 있습니다. (아래 사진 참고)
* 이를 통해 아래 컴포넌트는 서버에서 렌더링된다는 것을 알 수 있습니다.
* */
export default function Home() {
// console.log('executing...')
+ /**
+ * NextJS 에서 페이지 이동을 위한 링크를 걸 때, 아래와 같이 앵커 태그로 작업할 수도 있으나, 이 방법은 큰 결점이 있습니다.
+ * <a href="/about">About Us</a>
+ * 위와 같이 정의하고 페이지 이동을하면 그 결점이 확인되는데 페이지가 새로고침되는 걸 확인할 수 있습니다.
+ * 이는 백엔드에서 새로운 페이지가 다운로드 됐다는 의미입니다.
+ * 즉, 현재 페이지에서 벗어나 새로운 페이지를 받았다는 것입니다.
+ *
+ * 여기서 단점은 일반적인 리액트와 달리 더 이상 단일 페이지 애플리케이션이 아니라는 것입니다.
+ * 물론 이렇게 작동하는 것에 대해 이상하게 여기지 않을 수도 있습니다.
+ * NextJS 에서는 풀스택 애플리케이션을 만드는 것으로 내용이 백엔드에서 랜더링되어서 클라이언트측인 프론트엔드에서 SPA로 동작하지 않는 것이 자연스럽게 보입니다.
+ *
+ * 그러나 NextJS 의 장점은 둘 다 가능하다는 것입니다.
+ * 특정 페이지 처음 방문은 서버에서 렌더링되고, 그 이후에는 클라이언트에서 렌더링되는 것이 가능합니다.
+ * 엄밀히 말하면 NextJS 페이지의 내용은 서버에서 렌더링되기 전 단계이지만 클라이언트 측 자바슼릡트 코드로 클라이언트 사이드에 렌더링되는 것이 가능합니다.
+ * 그러므로 두 가지 장점을 모두 얻을 수 있습니다.
+ *
+ * 때문에 이러한 2가지 장점을 모두 가져가기 위해선 앵커 태그를 사용하지 않습니다.
+ *
+ * 대신 Link 라는 NextJS 에서 제공하는 컴포넌트를 사용합니다.
+ * */
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<h1>NextJS 14 버전 공부 시작!</h1>
+ {/*<p><a href="/about">About Us</a></p>*/}
+ <p><Link href="/about">About Us</Link></p>
</main>
);
}
4. 페이지 및 레이아웃 작업하기 (src/app/layout.js)
src/
└── app/
├── about/
├────── page.js |---> my-page.com/about
├── blog/
├───└── page.js |---> my-page.com/blog
├── page.js |---> my-page.com
└── layout.js
+ // src/app/layout.tsx
+ /**
+ * 이 파일도 역시 보호된 파일명을 가진 파일입니다.
+ * page.js 파일이 페이지의 내용을 정의한다면, layout.js 파일은 하나 또는 그 이상의 페이지를 감싸는 껍데기를 정의합니다.
+ * 이름이 의미하는 것과같이 페이지가 렌더링되는 레이아웃을 의미합니다.
+ * 모든 NextJS 프로젝트에는 최소 하나의 근본 layout.js 파일이 필요합니다.
+ * 즉, src/app 폴더 안에 layout.js 파일이 하나는 있어야 합니다.
+ * 중첩된 layout.js 파일도 있을 수 있습니다.
+ * src/app/about 폴더 안에 layout.js 파일이 있을 수도 있습니다.
+ * src/app/about/layout.js 파일은 about 폴더의 페이지와 중첩된 폴더에만 적용됩니다.
+ * 그러나 최소 하나의 근본 layout.js 파일이 필요합니다.
+ * */
+
import {ReactNode} from "react";
import type {Metadata} from "next";
import {Inter} from "next/font/google";
import "./globals.css";
const inter = Inter({subsets: ["latin"]});
+ /**
+ * head 안의 title 태그와 meta 태그를 설정하기 위해 metadata 객체를 생성합니다.
+ * 이 metadata 라는 변수명 또한 NextJS 프레임워크에서 사용하는 이름입니다.
+ * 보호된 파일명처럼 정해진 이름입니다.
+ * 이 이름의 객체를 export 하면 NextJS 프레임워크가 이 객체를 사용하여 head 태그에 title 태그와 meta 태그를 추가합니다.
+ * 기타 메타데이터도 있을 것인데 이것은 다음에 설명하겠습니다.
+ * 여튼 아래 메타데이터는 모든 페이지에 공통으로 적용됩니다.
+ * 따라서 head 요소가 없는 이유는 head 에 들어가는 모든 내용은 metadata 객체를 통해 설정할 수 있기 때문입니다.
+ * */
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
+ /**
+ * 아래 내용에서도 볼 수 있듯이 여기도 리액트 컴포넌트를 export 하는데, page.js 파일 내용과 동일합니다.
+ * 리액트에서 모든 컴포넌트가 사용할 수 있는 children 속성을 이 컴포넌트가 사용해 body 태그 사이에 내용을 추가합니다.
+ * */
+ /**
+ * 아래 RootLayout 컴포넌트에 props 로 children을 받아서 body 태그 사이에 렌더링합니다.
+ * 이 children 은 무엇일까요?
+ * 지금 현재 활성화된 페이지의 컴포넌트입니다.
+ * 다시 말하지만 레이아웃은 하나 또는 그 이상의 페이지를 감싸는 포장지와 같습니다.
+ * 그리고 경로에 따라 children 은 현재 활성중인 page.js 파일의 컴포넌트입니다.
+ * */
export default function RootLayout({
children,
}: Readonly<{
children: ReactNode;
}>) {
+ /**
+ * 이것도 아주 흥미로운 내용입니다.
+ * 이 컴포넌트는 아래와 같이 html 태그와 body 태그를 렌더링합니다.
+ * 즉, 리액트 컴포넌트에서 자주 사용하지 않는 요소이지만, NextJS 프로젝트의 근본 레이아웃은 웹사이트의 일반적인 HTML 뼈대를 잡기 위해 필수입니다.
+ * 그럼 head 요소는 어디에 있을까요?
+ * 일반적으로 제목 또는 메타데이터를 설정하기 위해 html 요소 안에 body 요소 위에 head 요소를 추가하지만, 여기에는 렌더링되지 않습니다.
+ * 이는 위의 metadata 객체를 사용하여 설정할 수 있습니다.
+ * */
return (
<html lang="ko">
<body className={inter.className}>{children}</body>
</html>
);
}
5. 보호된 파일명 (globals.css, favicon.ico), 커스텀 컴포넌트 및 NextJS 프로젝트 정리 방법 (src/components)
// src/app/layout.js
// ...
+ /**
+ * globals.css 파일은 layout.js 파일이 렌더링될 때마다 렌더링되는 CSS 파일입니다.
+ * 즉, 이 파일은 모든 페이지에 적용되는 CSS 파일입니다.
+ * */
import "./globals.css";
// ...
// src/app/page.js
// ...
+ /**
+ * 보호된 파일명을 사용하지 않은 컴포넌트를 import 합니다.
+ * */
+ import Header from "@/components/header";
// ...
export default function Home() {
+ /**
+ * 아래와 같이 작업해도 여전히 리액트 환경이기 때문에 리액트처럼 작업도 당연히 가능합니다.
+ * 단지 NextJS 가 제공하는 추가 기능이 있는 것 뿐입니다.
+ * */
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
+ <Header />
<h1>NextJS 14 버전 공부 시작!</h1>
{/*<p><a href="/about">About Us</a></p>*/}
<p><Link href="/about">About Us</Link></p>
</main>
);
}
// src/components/header.js
/**
* 해당 컴포넌트는 보호된 파일명이 아니기 때문에 다른 곳에서 import 해서 사용할 수 있습니다.
* */
import Link from "next/link";
export default function Header() {
return (
<header className="flex items-center justify-between p-4 bg-gray-800 text-white">
<nav>
<ul className="flex">
<li className="mr-4">
<Link href="/02_next_app/public">Home</Link>
</li>
<li>
<Link href="/about">About</Link>
</li>
</ul>
</nav>
</header>
);
}
src/
└── app/
├── about/
├────── page.js |---> my-page.com/about
├── blog/
├───└── page.js |---> my-page.com/blog
├── page.js |---> my-page.com
├── layout.js
├── globals.css
└── favicon.ico
favicon.ico
역시 특별한 파일명입니다.favicon.ico
파일명을 넣으면 NextJS에서 favicon
으로 사용하게 됩니다.layout.js
파일에 favicon
설정을 하지 않은 것을 볼 수 있습니다.
설정 안했음에도 불구하고 브라우저를 확인하면 favicon
이 적용된걸 볼 수 있습니다.src/app/favicon.ico
이렇게 src/app
폴더에 위치하는 것만으로 이런 기능이 알아서 구현됩니다.
지금까지 보신 것과 같이 app
폴더는 아주 중요합니다.app
폴더에 폴더를 추가해 라우트를 정의하고 page.js
, favicon.ico
또는 layout.js
와 같은 보호된 파일명이 있어서
다양한 기능을 사용할 수 있게 해줍니다.
물론 일반 컴포넌트를 생성해 사용할 수도 있습니다.
보호된 파일명을 사용하지 않아도 됩니다.
src/
└── app/
├── about/
├────── page.js |---> my-page.com/about
├── blog/
├───└── page.js |---> my-page.com/blog
├── page.js |---> my-page.com
├── layout.js
├── globals.css
├── favicon.ico
└── header.js
위와 같이 src/app/header.js
파일을 추가하면 header.js
파일은 보호된 파일명이 아니기 때문에
NextJS
에서 자동으로 포착하거나 어떤 기능을 구현하지 않습니다.
해당 컴포넌트는 보호된 파일명을 사용하지 않는 커스텀 컴포넌트이므로 src/app/components
폴더 안으로 옮기겠습니다.
src/
└── app/
├── about/
├────── page.js |---> my-page.com/about
├── blog/
├───└── page.js |---> my-page.com/blog
├── components/
├───└── header.js
├── page.js |---> my-page.com
├── layout.js
├── globals.css
└── favicon.ico
그런데 여기서 중요한 것이 있습니다.src/app/about/page.js
파일이 있다면, http://localhost:3000/about
페이지로 이동할 수 있습니다.
그러나 src/app/components/header.js
파일이 있다면, http://localhost:3000/components/header
페이지로 이동할 수 없습니다.
그 이유는 NextJS
가 src/app/components/header.js
파일을 무시하기 때문입니다.
src/app/components
폴더 안에 page.js
파일이 있었다면 라우트 경로로 인식했을겁니다.
이것이 Next App Router
작동 원리입니다.
하지만 위 폴더 구조는 헷갈릴 수 있기 때문에 components
폴더는 app
폴더 외부에 저장하는 것이 선호됩니다.
src/
└── app/
├── about/
├────── page.js |---> my-page.com/about
├── blog/
├───└── page.js |---> my-page.com/blog
├── page.js |---> my-page.com
├── layout.js
├── globals.css
└── favicon.ico
└── components/
└── header.js
하지만 정답이 있는건 아닙니다. 여전히 app
폴더 내부에 위치해도됩니다.
5_1. 경로 alias 설정 (tsconfig.json)
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
위 설정을 보시면 paths
속성이 있습니다.
이 속성 안에 @/*
라는 경로를 설정했습니다.
이는 src
폴더를 가리키는 경로입니다.
이를 통해 import
경로도 단순화할 수 있습니다.
위 alias
기능을 사용하시면 root
프로젝트 폴더를 @
를 사용해 조회할 수 있습니다.
6. 보호된 파일명
배우신 것과 같이 NextJS
에는 일부 보호된 파일명이 있습니다.
- 중요:
- 이 파일명들은
app/폴더(부 폴더 포함)
내부에서 생성될 때만 보호됩니다. app/폴더
외부에서 생성될 경우 이 파일명들을 특별한 방식으로 처리하지 않습니다.
- 이 파일명들은
다음 목록은 NextJS
에서 보호된 파일명이며 이 섹션에서 중요한 파일명을 배울 것입니다:
page.js
=> 신규 페이지 생성 (예:app/about/page.js
은<your-domain>/about
page
를 생성)layout.js
=> 형제 및 중첩 페이지를 감싸는 신규 레이아웃 생성not-found.js
=>‘Not Found’
오류에 대한 폴백 페이지(형제 또는 중첩 페이지 또는 레이아웃에서 전달된)error.js
=> 기타 오류에 대한 폴백 페이지(형제 또는 중첩 페이지 또는 레이아웃에서 전달된)loading.js
=> 형제 또는 중첩 페이지(또는 레이아웃)가 데이터를 가져오는 동안 표시되는 폴백 페이지route.js
=>API
경로 생성(즉,JSX
코드가 아닌 데이터를 반환하는 페이지, 예:JSON
형식)
공식 문서에 지원되는 모든 파일 이름과 자세한 설명이 포함된 목록을 찾을 수 있습니다:
https://nextjs.org/docs/app/api-reference/file-conventions
7. 동적 경로 환경설정 및 경로 매개 변수 사용 방법 (src/app/blog/[slug]/page.js)
아래와 같이 src/app/blog/[slug]
라는 폴더를 생성합니다.slug
라는 이름은 임의로 지어도 됩니다.
이 임의로 지은 이름은 나중에 중요해집니다.
src/
└── app/
├── about/
├────── page.js |---> my-page.com/about
├── blog/
├───├── [slug]/
├───├───└── page.js |---> my-page.com/blog/[slug]
├───└── page.js |---> my-page.com/blog
├── page.js |---> my-page.com
├── layout.js
├── globals.css
└── favicon.ico
└── components/
└── header.js
그리고 위와 같이 src/app/blog/[slug]/page.js
파일을 생성합니다.
해당 경로로 렌더링되는 페이지는 my-page.com/blog/[slug]
가 됩니다.
방문 가능한 페이지를 만들고 싶다면 page.js
파일이 필요합니다.
[slug]
이렇게 대괄호로 감싸여져있는 폴더명은 동적 경로를 의미합니다.
동적 라우트는 어떠한 경로 분할을 원하지만 정확한 값은 모른다는 뜻입니다.
// src/app/blog/[slug]/page.js
/**
* [slug] 동적 라우트로 전달되는 값은 params 로 전달됩니다.
* 이 또한 NextJS 에서 알아서 전달해줍니다.
* page.js 보호된 파일명은 수동으로 렌더링하는 것이 아니기 때문입니다.
* NextJS 에서 해주고 props 를 대신 설정해줍니다.
*
* 이런 slug 에 설정되는 값을 통해 데이터베이스에 저장된 데이터를 가져와서 렌더링할 수 있습니다.
* 이렇게 dynamic route 를 사용하면 더 많은 기능들을 구현할 수 있습니다.
* */
interface BlogPostPropsType {
params: {
slug: string;
};
}
export default function BlogPost({params}: BlogPostPropsType) {
return (
<main className="flex min-h-screen flex-col items-center">
<h1>블로그 포스트</h1>
<p>{params.slug}</p>
</main>
);
}