이는 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
→ /users
app/products/page.tsx
→ /products
app/courses/page.tsx
→ /courses
app/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=value2
searchParams
키워드를 통해 접근할 수 있다.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 email
npm 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