• 모닥위키모닥위키
  • 모닥위키
위키
  • 임의문서
  • 주간인기
  • 문서
  • 시리즈
    AAAdddvvveeerrrtttiiissseeemmmeeennntttAdvertisement

    © 2025 modak.wiki All rights reserved.

      app 디렉토리

      Chapter 2 - 페이지, 레이아웃, API

      컴퓨터/IT학습
      lu

      luasenvy (luasenvy)

      CC BY 4.0 국제규약

      app 디렉토리

      nextjs 구조에서 사전 정의된 디렉토리로 화면, API와 같이 클라이언트가 접근하고 사용할 수 있는 실질적인 어플리케이션(기능)들이 위치한다.

      페이지

      AppRouter 구조에서 페이지를 만들기 위해서는 app 디렉토리 하위에 page.tsx 파일을 만듦으로써 구성할 수 있다. app 디렉토리를 루트 디렉토리로 하여 page.tsx가 위치한 곳까지의 디렉토리 경로를 URL로 치환하여 라우트 경로가 결정된다. 설치 이후에 접속하여 확인한 인덱스 페이지는 ./app/page.tsx가 화면에 로드된 결과이다.

      ./app/first/page.tsx
      export default function FirstPage(){
        return <div>
          Hello World
        </div>
      }
      

      위 처럼 파일을 만들고 저장하게 되면 localhost:3000/first로 접속하여 확인할 수 있다.

      경로 파라미터와 쿼리스트링

      app/first
      ├── [someId]
      │   └── page.tsx
      └── page.tsx
      

      경로 파라미터를 사용하기 위해서 디렉토리를 [variable_name] 형식으로 지정할 수 있다. page.tsx에서는 SSR 방식에 따라 참조할 수 있는 방법이 다르다.

      ./app/first/[someId]/page.tsx
      interface FirstParamsPageProps {
        params: Promise<{ someId: string }>
        searchParams: Promise<{ someName: string }>
      }
      
      export default async function FirstPage({ params, searchParams }: FirstParamsPageProps){
        const { someId } = await params;
        const { someName } = await searchParams;
      
        return <div>
          {someId} - {someName}
        </div>
      }
      

      SSR방식으로 로드할 경우 페이지 함수가 async로 구성되어야한다. params, searchParams의 이름으로 넘어오는 파라미터를 통해 사용할 수 있으며 사용전에 반드시 await 하여야 한다.

      e.g.ClientComponent
      "use client";
      
      import { useParams, useSearchParams } from "next/navigation";
      
      export default function FirstPage(){
        const { someId } = useParams<{ someId: string }>()
        const searchParams = useSearchParams()
        
        return <div>
          {someId} - {searchParams.get("someName")}
        </div>
      }
      

      SSR을 사용하지 않을 경우에는 next/navigation 패키지에 있는 useParams, useSearchParams 훅을 활용하여 사용할 수 있다.

      app/first
      ├── else
      │   └── [...params]
      │       └── page.tsx
      ├── [someId]
      │   └── page.tsx
      └── page.tsx
      

      경로 파라미터로 사용할 변수명 앞에 ... 을 함께 사용하면 하위 모든 형태와 깊이의 경로를 포함하여 경로 파라미터로 사용할 수 있다. 위 경우에는 /app/first/else/*/*에 해당하는 라우트를 생성할 수 있다.

      라우트 영향없이 디렉토리 나누기

      app/first
      ├── (new)
      │   └── help
      │       └── page.tsx
      ├── [someId]
      │   └── page.tsx
      └── page.tsx
      

      라우트 경로에 영향을 주지 않고 디렉토리를 분리하고 싶을 때에는 (dirname) 과 같이 괄호를 감싼 디렉토리 명으로 사용할 수 있다. 위 처럼 구성한다면 localhost:3000/first/help로 라우트 경로를 생성할 수 있다. 적용할 레이아웃을 나누거나 관리목적으로 나누는 경우에 유용하게 사용할 수 있다. 라우트 경로에 영향을 주지 않기 때문에 다른 라우트 경로와 겹치는 경우가 없는지 잘 확인해야한다.

      컴포넌트

      .
      ├── app
      │   └── first
      │       └── page.tsx
      ├── components
      │   └── Counter.tsx
      

      nextjs는 react를 프론트엔드 프레임워크로 사용하고 있다. 리액트용 컴포넌트를 관례적으로 components라는 명칭의 디렉토리에서 관리하는 것이 일반적이나 app과 같이 특별한 기능이 지원되는 디렉토리가 아니다.

      ./components/Counter.tsx
      "use client";
      
      import { useState } from "react";
      
      export default function Counter () {
        const [count, setCount] = useState<number>(0);
      
        const handleClickButton = () => setCount((prev) => prev + 1);
      
        return <button type="button" onClick={handleClickButton}>{count}</button>
      }
      

      app 디렉토리와 같은 위치에 components 디렉토리를 만들고 새로운 리액트 컴포넌트인 Counter.tsx를 작성하였다.

      app/first/page.tsx
      import Counter from "@/components/Counter";
      
      export default function FirstPage(){
        return (
          <div>
            <Counter />
          </div>
        )
      }
      

      프로젝트 루트를 뜻하는 @ 를 사용하면 쉽게 컴포넌트가 있는 위치를 지정할 수 있다. 방금 작성한 컴포넌트를 임포트하여 바로 사용할 수 있다.

      레이아웃

      레이아웃은 페이지를 포함한 화면 구조이다. 웹 어플리케이션의 화면을 구성함에 있어 동적으로 변경되는 페이지를 제외하고 헤더, 푸터, 사이드바와 같이 고정되어 있는 요소를 구성할 때 편리하다. 렌더링 되는 태그 뿐만 아니라 폰트 설정, 전역 CSS와 같이 공용 자원을 미리 불러오는데 사용할 수도 있다.

      레이아웃은 루트로 부터 페이지가 존재하는 위치까지의 모든 레이아웃이 순차적으로 로드된다.

      여기서 ./app/layout.tsx 는 가장 최상위 레이아웃으로 루트 레이아웃이라고 특별히 지칭하기도 하며 <html /> 태그와 <body /> 태그를 포함하고 있어야한다.

      ./app/layout.tsx
      export default function RootLayout({
        children,
      }: Readonly<{
        children: React.ReactNode;
      }>) {
        return (
          <html lang="en">
            <body
              className={`${geistSans.variable} ${geistMono.variable} antialiased`}
            >
              {children}
            </body>
          </html>
        );
      }
      

      최초 설치시에 자동으로 생성된 루트 레이아웃을 열어보면 위 처럼 구성되어있다. {children} 을 렌더링하는 부분에 페이지 또는 하위 레이아웃이 렌더링 된다.

      ./app/first/layout.tsx
      export default function FirstLayout({ children }: React.PropsWithChildren) {
        return <main className="first-layout">
          {children}
        </main>
      }
      

      이렇게 first/layout.tsx를 작성하면 first/page.tsx를 로드할 때 위 레이아웃을 포함하여 렌더링 된다. 즉, 루트 레이아웃의 {children} 에 FirstLayout이 렌더링되고, FirstLayout에 작성된 {children} 에 page.tsx가 렌더링되는 식이다.

      적용범위

      FirstLayout은 ./app/first 디렉토리에 위치하고 있어서 해당 디렉토리의 하위 모든 page.tsx에 적용된다. 예를들어 ./app/first/some/path/page.tsx의 경우도 포함한다. 만약 ./app/second/page.tsx 를 작성하였다면 이 page.tsx에는 영향을 주지 않는다.

      API

      ./app/api/hello/route.ts
      export async function GET() {
        return Response.json({ hello: "world!" })
      }
      
      .
      ├── app
      │   ├── api
      │   │   └── hello
      │   │       └── route.tsx
      

      app 디렉토리에서 route.ts를 작성하게되면 API로 활용할 수 있다. export 하는 함수의 명칭으로 GET, POST, PUT, DELETE, PATCH 등 restful API를 제공할 수 있다. route.ts는 page.tsx처럼 app 디렉토리 하위 어디에나 위치시킬 수 있고 위치한 곳 까지의 디렉토리 경로가 라우트 경로로 결정된다. 특별히 지정된 디렉토리는 없지만 관례적으로 api 디렉토리 하위에 분리하여 관리하는 것이 일반적이다.

      ./app/first/page.tsx
      "use client";
      
      import Counter from "@/components/Counter";
      import { useEffect } from "react";
      
      export default function FirstPage() {
        useEffect(() => {
          (async () => {
            const res = await fetch("/api/hello");
      
            if (!res.ok) throw new Error("Failed to fetch");
            
            const data = await res.json();
            
            console.log(data);
          })()
        }, []);
      
        return <div>
          <Counter />
        </div>
      }
      

      작성한 API 경로를 fetch로 호출하여 테스트 해볼 수 있다.

      초판: 2025. 01. 11. 16:29:34

      © 2025 이 문서는 "CC BY 4.0 국제규약" 라이선스로 배포 되었습니다. 모든 권리는 저자에게 있습니다.

      app 디렉토리

      app 디렉토리
      페이지
      경로 파라미터와 쿼리스트링
      라우트 영향없이 디렉토리 나누기
      컴포넌트
      레이아웃
      적용범위
      API