
이는 page.tsx, layout.tsx, loading.tsx, route.tsx 등 특별한 파일을 찾아 사용.
| 파일명 | 역할 |
|---|---|
| layout | 세그먼트의 메인 컨텐츠와 하위 세그먼트의 공용 레이아웃 UI |
| page | 세그먼트의 메인 컨텐츠 UI |
| loading | 세그먼트의 메인 컨텐츠와 하위 세그먼트의 로딩 UI |
| not-found | 세그먼트의 메인 컨텐츠와 하위 세그먼트의 Not Found UI |
| error | 세그먼트의 메인 컨텐츠와 하위 세그먼트의 에러 UI |
| global-error | 전역 에러 UI |
| route | 서버 API 엔드포인트 |
| template | 특별하게 재사용될 수 있는 레이아웃 UI |
| default | 패러럴 라우트의 폴백 UI |
각 File Convention은 공식문서에서 확인할 수 있다.
page.tsx 파일app/user/page.tsx → /usersapp/products/page.tsx → /productsapp/courses/page.tsx → /coursesapp/users/page.tsx
import React from "react";
interface User {
id: number;
name: string;
}
const UsersPage = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/users", {
});
const users: User[] = await res.json();
return (
<div>
<div>This is UserPage</div>
<p>{new Date().toLocaleTimeString()}</p>
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
};
export default UsersPage;
app/users/UserTable.tsx
import React from "react";
const UserTable = () => {
return <div>UserTable</div>;
};
export default UserTable;
app/users/page.tsx
import React from "react";
interface User {
id: number;
name: string;
email: string;
}
const UserTable = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/users", {
cache: "no-store",
});
const users: User[] = await res.json();
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{users.map((user) => (
<tr key={user.id}>
<td>{user.name}</td>
<td>{user.email}</td>
</tr>
))}
</tbody>
</table>
);
};
export default UserTable;
app/users/UserTable.tsx
import React from "react";
import UserTable from "./UserTable";
const UsersPage = () => {
return (
<div>
<div>This is UserPage</div>
<p>{new Date().toLocaleTimeString()}</p>
<UserTable />
</div>
);
};
export default UsersPage;
components 폴더에 위치시켜야 한다.page.tsx 파일 내에서 export default 로 선언된 컴포넌트만을 제외하고는 외부로 노출되지 않는다.[id], [userId], [username], …app/users/[id]/page.tsx
import React from "react";
const UserDetailPage = () => {
return <div>UserDetailPage</div>;
};
export default UserDetailPage;
import React from "react";
interface Props {
params: { id: number };
}
const UserDetailPage = (props: Props) => {
console.log(props.params.id);
return <div>UserDetailPage</div>;
};
export default UserDetailPage;
import React from "react";
interface Props {
params: { id: number };
}
const UserDetailPage = ({ params: { id } }: Props) => {
return <div>UserDetailPage {id}</div>;
};
export default UserDetailPage;

/users/[id]/photos/[photoId] 와 같은 동적 라우트를 생성하고 싶다면?app
|- users
|- [id]
|- photos
|- [photoId]
|- page.tsx
import React from "react";
interface Props {
params: { id: number; photoId: number };
}
const PhotoPage = (props: Props) => {
return (
<div>
PhotoPage {props.params.id} {props.params.photoId}
</div>
);
};
export default PhotoPage;

/profile/[username]/[postId][…매개변수] 형식으로 폴더를 생성하고, 내부에 page.tsx 파일을 생성한다.app/profile/[…username]/page.tsx
import React from "react";
const ProfilePage = (props) => {
console.log(props);
return <div>ProfilePage</div>;
};
export default ProfilePage;
/profile/jieun 경로에 접속하여 props를 콘솔에 출력해보자.{ params: { username: [ 'jieun' ] }, searchParams: {} }
import React from "react";
interface Props {
params: {
username: string[];
};
}
const ProfilePage = (props: Props) => {
console.log(props);
return <div>ProfilePage</div>;
};
export default ProfilePage;
/profile/jieun/post1

/profile

[…username]을 [[…username]]으로 변경한 후, /profile 에 다시 접속해보자.
https://example.com/page?name=value&key2=value2searchParams 키워드를 통해 접근할 수 있다.app/products/[..slug]/page.tsx
import React from "react";
const ProductPage = (props) => {
console.log(props);
return <div>ProductPage</div>;
};
export default ProductPage;
/products/computer?sortOrder=name 에 접속 후 콘솔 확인
{
params: { slug: [ 'computer' ] },
searchParams: { sortOrder: 'name' }
}
import React from "react";
interface Props {
params: { slug: string };
searchParams: { sortOrder: string };
}
const ProductPage = ({
params: { slug },
searchParams: { sortOrder },
}: Props) => {
return (
<div>
ProductPage {slug} {sortOrder}
</div>
);
};
export default ProductPage;

https://jsonplaceholder.typicode.com/users?sortOrder=name or emailnpm install fast-sort
app/users/UserTable.tsx 파일을 수정하여 쿼리스트링 기반의 정렬을 적용해보자.import React from "react";
import Link from "next/link";
interface User {
id: number;
name: string;
email: string;
}
const UserTable = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const users: User[] = await res.json();
return (
<table>
<thead>
<tr>
<th>
<Link href="/users?sortOrder=name">Name</Link>
</th>
<th>
<Link href="/users?sortOrder=email">Email</Link>
</th>
</tr>
</thead>
<tbody>
{users.map((user) => (
<tr key={user.id}>
<td>{user.name}</td>
<td>{user.email}</td>
</tr>
))}
</tbody>
</table>
);
};
export default UserTable;
📌 a 태그를 사용하지 않고 Link Component를 사용하는 이유
- a 태그를 사용하면 해당 링크를 클릭할 때마다 연결된 페이지를 방문하기 위해 필요한 모든 자원을 다시 불러와야 하므로 효율성이 떨어질 수 있음
- Link Component
- Next.js의 Link 컴포넌트를 사용하면 클라이언트측 라우팅을 활용하여 페이지 간 전환이 이루어짐
- 이로 인해 페이지가 새로 렌더링되지 않고 필요한 부분만 업데이트되어 더 빠르고 부드러운 사용자 경험을 제공
props의 searchPrams 키워드를 통해 sortOrder 쿼리스트링에 접근하고, 자식 컴포넌트로 sortOrder 값을 전달한다.app/users/page.tsx
import React from "react";
import UserTable from "./UserTable";
interface Props {
searchParams: { sortOrder: string };
}
const UsersPage = ({ searchParams: { sortOrder } }: Props) => {
return (
<div>
<div>This is UserPage</div>
<p>{new Date().toLocaleTimeString()}</p>
<UserTable sortOrder={sortOrder} />
</div>
);
};
export default UsersPage;
props로 전달받은 sortOrder 값에 따라 users 배열을 정렬한다.name으로 하고, sortOrder가 email 일 경우 email을 기준으로 정렬하는 sortUsers 함수를 작성app/users/UserTable.tsx
import React from "react";
import Link from "next/link";
import { sort } from "fast-sort";
interface User {
id: number;
name: string;
email: string;
}
interface Props {
sortOrder: string;
}
const UserTable = async ({ sortOrder }: Props) => {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const users: User[] = await res.json();
const sortedUsers = sort(users).asc(
sortOrder === "email" ? (user) => user.email : (user) => user.name
);
return (
<table>
<thead>
<tr>
<th>
<Link href="/users?sortOrder=name">Name</Link>
</th>
<th>
<Link href="/users?sortOrder=email">Email</Link>
</th>
</tr>
</thead>
<tbody>
{sortedUsers.map((user) => (
<tr key={user.id}>
<td>{user.name}</td>
<td>{user.email}</td>
</tr>
))}
</tbody>
</table>
);
};
export default UserTable;
/users?sortOrder=name

/users?sortOrder=email
