Nextjs 13 with App directory
Working with the app directory in Next.js 13
번역 원본 글 : https://blog.logrocket.com/next-js-13-app-directory/
위의 글과 Nextjs 공식 페이지에 있는 내용에서 필요한 부분만을 정리했습니다.
📚 Working with the app directory in Next.js 13
📌 Next.js 13에서의 새로운 기능
pages diretory → app directory
- pages directory 구조에서는
pages/home.jsx
→ site.com/home
- app directory 구조에서는
app/profile/settings/page.jsx
→ site.com/profile/settings
loading.tsx
app
폴더 내에 만들 수 있는 선택적인 컴포넌트
- 해당
loading.tsx
컴포넌트는 자동으로React suspense boundary
안에 페이지를 감싸게 됨
error.tsx
app
에서 에러가 발생할 수 있는 가능한 작은 영역으로 나누어 분리시킬 수 있음
error.tsx
파일 생성 → React error boundary로 해당 페이지 영역을 감싸게 됨
error.tsx
파일이 위치한 곳에 어떤 에러가 발생하면 → 해당 컴포넌트로 대체 됨
layout.tsx
- 여러 곳에서 공유되는 UI를 정의할 수 있음
layout
컴포넌트 안에 또 다른 layout 컴포넌트를 담을 수 있음
layout
안에 있는 컴포넌트 안에서 route가 변경되면, layout의 state는 유지됨 (layout 컴포넌트가 unmounted되지 않음)
template.tsx
layout.tsx
파일과 유사, 그러나 페이지를 이동하면 상태가 유지되지 않음
layout 과 template를 활용하면, partial rendering 으로 알려진 컨셉의 장점을 취할 수 있음

📌 app directory 를 사용하기
- Next.js 13에서는 새로운 기능이 많이 추가되었음.
pages directory
에서app directory
로 이전하면서 알아야할 것들이 있음
[필수] root 레이아웃
app directory
의 루트 위치에 정의가 되어 있어야 함
app
안에 존재하는 모든 경로에 적용 가능
- 추가로
root 레이아웃
은<html>
과<body>
태그를 작성해줘야 함 - Next.js가 자동으로 추가해주지 않음
Head tag
app directory
내부에head.js
파일이 속한 폴더에<head>
태그의 콘텐츠를 정의할 수 있음
head.js
는<title>
,<meta>
,<link>
,<script>
등과 같은 파일을 return 함
Route groups
- 모든
app directory
내부 구조는 URL path에 영향을 끼침
- 괄호로 폴더 이름을 감싸면 경로 그룹으로 포함되지 않음

Server component
- 기본적으로
app directory
안에 생성된 컴포넌트는React server components
임
RSC
는 더 작은 번들사이즈로 더 좋은 성능을 제공함
- 클라이언트 컴포넌트로 변경하고자 한다면, 파일의 최상단에
use client
라고 명시하면 됨
📌 Next.js 13 핸즈온!
page & layout
app/layout.tsx
export const metadata = { title: 'Areate Next App', description: 'Generated by create next app', }; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body className={inter.className}>{children}</body> </html> ); }
export const metadata
는 static인지 dynamic인지에 따라서 달라짐 (참고링크)
app/page.tsx
export default function Page() { return ( <html lang="en"> <body className={inter.className}> <div className={styles.header}>from layout</div> <div>{children}</div> </body> </html> ); }
app/second/page.tsx
export default function Page() { return <h1>Second route!</h1>; }

📌 중첩된 레이아웃 구조
- 각 레이아웃 컴포넌트들은
app
최상단 뿐 아니라, 각 폴더 레벨에서도 작동함

layout.tsx의 중첩
import styles from './layout.module.css'; export default function Layout({ children }: { children: React.ReactNode }) { return ( <div className={styles.header}> <div> <h1 style={{ marginTop: 0 }}>From layout</h1> </div> <div> <Link href="/profile/teacher">navigate to teacher profile</Link> </div> <div> <Link href="/profile/student">navigate to student profile</Link> </div> </div> }
- 해당
layout.tsx
은/student
와/teacher
에 영향을 끼침
중첩된 라우팅
app/profile/student/page.tsx
const students = { 1: { name: 'John', age: 10, subjects: ['Math', 'English'], }, }; export default function Page() { const student = students[1]; return ( <React.Fragment> <h1>Student profile</h1> <div>Name: {student.name}</div> <div>Age: {student.age}</div> <div>Studies: {student.subjects.join(', ')}</div> </React.Fragment> ); }
app/profile/teacher/page.tsx
import React from 'react'; const teachers = { 1: { name: 'Mr Smith', age: 30, subjects: ['Math', 'English'], }, }; export default function Page() { const teacher = teachers[1]; return ( <React.Fragment> <h1>Teacher profile</h1> <div>Name: {teacher.name}</div> <div>Age: {teacher.age}</div> <div>Teaches: {teacher.subjects.join(', ')}</div> </React.Fragment> ); }
app/layout.tsx → 링크 추가
<Link href="/profile/teacher">navigate to teacher profile</Link> <Link href="/profile/student">navigate to student profile</Link>
- 중첩된 레이아웃 확인
app
은 서버에서 렌더링 → 클라이언트 훅 사용불가📌 에러 처리 - Errorboundary
app/breaking/page.tsx
'use client'; import { useEffect, useReducer, useState } from 'react'; import styles from './breaking.module.css'; export default function Page() { const [error, setError] = useState(false); useEffect(() => { if (error) throw new Error(); }, [error]); return ( <div className={styles.component}> <div>BREAKING</div> <div> <button onClick={(e) => setError(true)}>break this</button> </div> </div> ); }
React Error Boundary
와 마찬가지로 이벤트 핸들러에서 발생하는 에러는 잡지 못함 (렌더링 과정 중에 발생하는 에러를 잡음 → 더 찾아보자)

📌 로딩 컴포넌트 - Suspense
페이지 이동 중 로딩 (like Lazy loading)
app/breaking/loading.tsx
export default function Loading() { return <h1>Loading...</h1> }
/breaking
로 이동하는 순간 발생하는 컴포넌트의 로딩 시간동안 해당 컴포넌트가 보여짐
데이터 페칭에서 발생하는 로딩
app/second/page.tsx
async function getData() { const index = Math.floor(Math.random() * 10); const res = await fetch( `https://jsonplaceholder.typicode.com/posts/${index}` ); return res.json(); } const page = async () => { const data = await getData(); return <p>{data.body}</p>; }; export default page;
app/second/loading.tsx
const Loading = () => { return <div>로딩 중</div>; }; export default Loading;
결과
- [리마인드]
app
폴더에 있는 컴포넌트는 기본적으로 “서버 컴포넌트”
- 마찬가지로 네트워크 요청이 발생하는 위치에
loading
파일이 있으면 자동으로Suspense Fallback
으로 작동
