NextJS 14 Version

1. 파일 기반 라우팅과 SSR에 대한 이해

src/
└── app/
    ├── about/
    └── blog/
        └── page.js ---> my-page.com/blog
    └── page.js ---> my-page.com

page.jslayout.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 페이지로 이동할 수 없습니다.

그 이유는 NextJSsrc/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에는 일부 보호된 파일명이 있습니다.

다음 목록은 NextJS에서 보호된 파일명이며 이 섹션에서 중요한 파일명을 배울 것입니다:

공식 문서에 지원되는 모든 파일 이름과 자세한 설명이 포함된 목록을 찾을 수 있습니다:

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