Post

Next.js 13의 특징과 변화(2) - 폴더구조

📌시작하며

이 글은 아래와 같이 이어집니다.

이 번엔 Next13의 꽃🌺 app 폴더를 살펴보고, api 작성법에 대해 정리해보고자 한다.

✅app router

1
2
3
4
5
6
7
8
9
app/
│
├───page.js 👈
│
└───dashboard/
    │
    ├───page.js 👈
    │
    └───about.js

기본적으로 app router 방식에서는 폴더이름이 루드가 되며, 해당 루트의 기본 페이지를 정의할 땐 page.js 파일을 만들어 사용하면면 된다.

  • / 경로는 app/page.js
  • /dashboard경로는 app/dashboard/page.js
  • /dashboard/about 경로는 app/dashboard/about.js

✅layout.js

기존의 pages/_app.jspages/_document.js는 단일 파일인 app/layout.js로 대체되었다! layout은 여러 경로 간에 공유되는 UI를 정의하는 역할을 한다.

1
2
3
4
5
6
7
app/
│
└───dashboard/
    │
    ├───layout.js 👈
    ├───about.js
    └───page.js

만약 다음과 같은 파일 구조를 가지고 있다고 생각해보자. dashboard 폴더에 layout.js 를 이용하면 /dashboard/dashboard/about 의 화면에서 공통 된 UI를 표시할 수 있다!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <section>
      {/* 공유 UI를 작성한다 */}
      <nav></nav>
      {children}
    </section>
  )
}

➡️Root layout.js(필수)

아래와 같이 app폴더 최상위의 layout.js은 필수 값으로, 모든 경로에 적용된다. 이 레이아웃에는 HTML 과 본문 태그를 포함해야 한다. 또한 layout.js에서 정의한 UI는 기본적으로 중첩되므로, /dashboard 경로에서는 루트에서 정의한 layout과 dashboard에서 정의한 layout 모두 표시된다.

1
2
3
4
5
6
7
8
9
app/
│
├───layout.js //필수값
├───page.js
│
└───dashboard/
    │
    ├───layout.js 👈
    └───page.js
1
2
3
4
5
6
7
8
9
10
11
12
13
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="ko">
      <body>
        <main>{children}</main>
      </body>
    </html>
  )
}

✅not-found.js

not-found.js는 이름 처럼 경로를 찾을 수 없을 때 표시할 UI를 렌더링 한다.

1
2
3
4
5
app/
│
├───layout.js
├───page.js
└───not-found.js 👈
1
2
3
4
5
6
7
8
9
10
11
import Link from "next/link"

export default function NotFound() {
  return (
    <div>
      <h2>404 Not Found</h2>
      <p>경로를 찾을  없습니다.</p>
      <Link href="/">돌아가기</Link>
    </div>
  )
}

✅error.js

error 파일은 error 발생시에 표시 될 UI를 정의한다. error.js 파일 규칙을 사용해 중첩된 경로에서 예기치 않은 런타임 오류를 처리할 수 있고, 전체 페이지를 다시 로드하지 않고 오류에서 복구를 시도하는 기능을 추가할 수 있다.

1
2
3
4
5
6
7
8
9
10
app/
│
├───layout.js //필수값
├───page.js
│
└───dashboard/
    │
    ├───error.js 👈
    ├───layout.js
    └───page.js

dashboard 폴더에서 error.js 파일을 생성한다고 가정하고, 아래와 같이 작성해보자. error.js는 중첩된 자식 세그먼트 또는 page.js 컴포넌트를 감싸는 React 오류 바운더리를 자동으로 생성한다.

즉, dashboard에서 에러가 발생했을 때, dashboard 컴포넌트 영역을 error.js 파일에서 내보낸 React 컴포넌트가 fallback(대체) 컴포넌트로 사용된다는 것!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//app/dashboard/error.tsx
"use client" // error 컴포넌트는 반드시 클라이언트 컴포넌트다.

import { useEffect } from "react"

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  useEffect(() => {
    // 에러 로그를 확인한다
    console.error(error)
  }, [error])

  return (
    <div>
      <h2>에러가 발생했습니다!</h2>
      <button
        onClick={
          // 재렌더링을 시도한다.
          () => reset()
        }
      >
        재시도 하기
      </button>
    </div>
  )
}
  • error: error.js 클라이언트 컴포넌트로 전달된 오류 객체의 인스턴스
  • reset: error boundary를 reset하는 함수. 이 함수가 실행되면 error boundary의 내용을 다시 렌더링하며, 성공하면 폴백 오류 컴포넌트가 다시 렌더링한 결과로 대체된다.

➡️global-error.js

만약 app/page.js 즉 최상위 페이지에서 오류가 발생하면 어떻게 할까? 이때 app/error.jsapp/layout.js 또는 app/template.js 컴포넌트에서 발생하는 오류를 적절히 처리하지 못한다.

즉 이경우에는 app/global-error.js 를 사용해 처리한다. global-error.js 는 전체 애플리케이션을 감싸며, 활성화되면 해당 fallback(대체) 컴포넌트가 루트 레이아웃을 대체한다.

따라서, global-error.js는 자체 <html><body> 태그를 정의해야 한다!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"use client"

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <html>
      <body>
        <h2>에러가 발생했습니다!</h2>
        <button onClick={() => reset()}>재시도 하기n</button>
      </body>
    </html>
  )
}

✅middleware.js

미들웨어를 사용하면 요청이 완료되기 전에 코드를 실행할 수 있다. 예 들어 로그인 하지 않은 사용자인지 확인하고, redirect 하는 등이 포함된다. middleware.js는 프로젝트의 root에 middleware.ts(또는 .js) 파일을 사용해 정의한다. app 폴더와 같은 level에서 정의하면 된다.

1
2
3
4
5
6
7
8
9
10
11
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"

// 내부에 `await`을 사용하는 경우 `async`로 표시할 수 있다.
export function middleware(request: NextRequest) {
  return NextResponse.redirect(new URL("/home", request.url))
}

export const config = {
  matcher: "/about/:path*",
}

➡️matcher

➡️matcher를 사용해 특정 경로에서 실행되도록 미들웨어를 필터링할 수 있다. 예를들어 아래와 같이 여러 path를 작성할 수 있다.

1
2
3
export const config = {
  matcher: ["/about/:path*", "/dashboard/:path*"],
}

✅Metadata

<head> 에서 정의 되어야 할 title과 같은 요소는 아래와 같이 정의할 수 있다. MetaData에 관한 더 자세한 설명은 다른 포스트에서 다루면 좋을 것 같다.

1
2
3
4
5
6
7
8
9
10
11
//app/page.tsx
import { Metadata } from "next"

export const metadata: Metadata = {
  title: "Next.js",
  description: "...",
}

export default function Page() {
  return "..."
}

📩마무리

app router의 폴더 구조를 이렇게 훑어보았다. 다음에는 api routes 작성법에 대해 살펴보자!

🗂️참고 사이트

This post is licensed under CC BY 4.0 by the author.