Next.js App router의 사용과 파일 관리
포스트
취소

Next.js App router의 사용과 파일 관리

Next.js 13.3 버전부터 App Router(일부는 App Directory라고 하기고 하더라)가 Stable로 승격되면서 pages를 사용하던 이전 프로젝트에서 관리하던 폴더 구조(Directory Structure)에 대한 생각을 해 보아야 겠다는 생각을 하게 되었다.

App Router

Folder Structure

App router는 기존 pages/ 아래에 파일 이름으로 Route를 생성하던 것과 달리 app/ 폴더 안에 폴더 이름으로 Route를 생성한다. 즉 다음과 같은 변화가 있다

  • 일반적인 Nested Route
    • pages/foo/bar.tsx
    • app/foo/bar/page.tsx
  • Catch Route
    • pages/foo/[id].tsx
    • app/foo/[id]/page.tsx
  • Catch Nested Routes
    • pages/foo/[...slug].tsx
    • app/foo/[...slug]/page.tsx
  • Catch All Nexted Routes
    • pages/foo/[[...slug]].tsx
    • app/foo/[[...slug]]/page.tsx 이런 차이로 생기는 장점은 app directory에 내 마음대로 파일을 넣어도 Route에 영향을 미치지 않는다는 점이다. 따라서 pages/ 폴더를 사용할 때 (즉 page router를 사용할 때) view라는 이름으로 따로 폴더를 만들거나 그럴 필요가 없어졌다는 것이다. 실제로 나는 이전에 대형 프로젝트를 진행할 때 Fragment, Fragment Group, Section, Modal, Page, Component이라는 개념을 새로 만들어서 사용했던 적이 있다. 그때 사용했던 기준은 다음과 같았다.
  • Fragment
    • 독립된 URL을 가지지는 않지만, 화면 전체를 차지하는 UI Component를 말한다.
    • Fragment를 사용하는 Page에서만 사용되어야 한다.
    • fragment/(fragment group name)/에 위치한다
  • Fragment Group
    • 같은 URL에서 보여지는 Fragment들의 집합이다.
    • 한 Fragment Group은 fragement/의 하위 디렉토리에 위치하여야 한다.
  • Section
    • Fragment 또는 Page가 너무 커지는 것을 방지하기 위해 사용한다
    • Fragment 또는 Page에서만 사용되어야 한다
    • 기능이 명확하여야 한다
    • 한 Fragment Group 또는 Page에서 사용될 경우 sections/(fragment group name)/ 또는 sections/(page name)/에 위치할 수 있다.
  • Modal
    • 화면 전체를 가리지만 배경이 투명한 Ui Component 이다
  • Page
    • URL을 가지는 하나의 페이지이다.
  • Component
    • 여러 Fragment, Page 또는 Section에서 사용되는 UI Component이다.

이러한 구분 기준을 가지고 구분하였던 적이 있지만, App Router가 나오면서 이러한 구분을 하는 이유가 흐릿해지기 시작했다. 왜냐하면 필요한 Component들은 그 자리에 놓으면 되기 때문이다. 또한 App Router는 RSC(React Server Components)를 적극적으로 사용하기 때문에, 이러한 구분이 많이 필요하지도 않다.

React Server Component

Next.js 13.3 버전 이전에도 getStaticProps, getServerSideProps와 같은 방법을 통해 Frontend Engineer가 Backend에 근접한 업무를 하게 될 것이라는 생각은 하고 있었지만, React Server Component가 나오면서 이러한 속도가 훨씬 거 빨라진 것 같다.
React Server Component가 나오면서 Data Fetching이 다시 Server Side로 넘어오게 되었고, Server Component의 등장으로 Data Fetching Layer와 Client interaction layer가 철저하게 분리된다는 것을 느꼈다.
예를 들어 보자. Prisma를 사용하는 Web Application에서 Client Side Rendering을 툥해 다음과 같은 형태로 운영하고 있었다고 해보자.

pages/api/users.ts

1
2
3
4
5
6
7
8
9
10
import { NextApiRequest, NextApiResponse } from "next";
import { PrismaClient } from "@prisma/client";

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const prisma = new PrismaClient();

  const users = await prisma.user.findMany();
  return res.json(users);
}

pages/index.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { useQuery } from "@tanstack/react-query";
import axios from "axios";

export default function MainPage() {
  const { data, isLoading } = useQuery(['users'], async () => {
    return await axios.get("/api/users");
  })

  if (isLoading) {
    return null;
  }

  return (
    <div>
      {data.map((user) => (
        <p key={user.id}>{user.name}</p>
      ))}
    </div>
  )
}

pages/_app.tsx

1
2
3
4
5
6
7
8
9
10
11
12
import type { AppProps } from "next/app";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

export default function App({ Component, pageProps }) {
  const queryRef = useRef<QueryClient>(new QueryClient());

  return (
    <QueryClientProvider client={queryRef.current}>
      <Component {...pageProps} />
    </QueryClientProvider>
  )
}

App Directory를 사용하면 다음과 같이 변한다. API Route과 Client Side Rendering이 합쳐지는 것이다. 가장 중요한 건 JSX가 들어가는 “컴포넌트”가 async일 수 있다는 것이다.

app/pages.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { PrismaClient } from "@prisma/client";

export default async function Page() {
  const prisma = new PrismaClient();

  const users = await prisma.user.findMany();

  return (
    <div>
      {users.map((user) => (
        <p key={user.id}>{user.name}</p>
      ))}
    </div>
  )
}

다만 Function이나, Provider, Hook은 Server Component에 사용할 수 없기 때문에, User Interaction이 필요한 경우 Client Component를 이용해야 한다.

결론

다음과 같은 Folder Structure를 유지하고 있다.

  • src
    • app
      • layout.tsx
      • page.tsx
    • foo
      • page.tsx
      • Form.tsx
      • Providers.tsx
    • components
      • Header.tsx
      • Button.tsx

어쩌피 하나의 Client Component는 그 Route에서만 사용되게 되므로 그 app directory 안에다가 넣어놓는 것이 적당하고, 공통적으로 생성되는 Header나 Button의 경우 components 폴더 안에 넣어 놓음으로써 import의 용이성을 가질 수 있다.
전체를 싸고 있는 src의 경우 편의상, 그리고 습관상 만들어놓게 되는데 언젠가는 없어질 수도 있지 않을까라는 생각을 하고 있다. (실제로 Create Next App에서 src directory 만드는 걸 추천 안하는 느낌이기도 했다)

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.