๐ Nextjs15 App Router ์์ํ๊ธฐ 1
โข2025๋ 9์ 30์ผ ํ์์ผ
๐ง Page ์ปดํฌ๋ํธ์์ async ํจ์๋ฅผ ์จ์ผ ํ๋ ์ด์
Nextjs15 ์์๋ Page ์ปดํฌ๋ํธ๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก ์๋ฒ ์ปดํฌ๋ํธ(Server Component)๋ก
๋์ํด์. ์ฆ, ์๋ฒ์์ ํ ๋ฒ๋ง ์คํ๋๊ธฐ ๋๋ฌธ์ async ํจ์๋ฅผ ์ฌ์ฉํด๋ ์ ํ
๋ฌธ์ ์์ด์! ๋๋ถ์ ๋ฐ์ดํฐ ํจ์นญ์ด ํจ์ฌ ๊ฐํธํด์ง๊ณ , ํด๋ผ์ด์ธํธ์์ ๋ถํ์ํ
๋ก๋ฉ์ด ์ค์ด๋ค์ฃ .
// pages/book/page.tsx
export default async function BookPage() {
const books = await fetchBooks(); // ๋น๋๊ธฐ ์ฒ๋ฆฌ
return <BookList books={books} />;
}๐ URL Parameter์ Search Params
Nextjs15 ์์๋ URL์ ํฌํจ๋ ๋์ ๋งค๊ฐ๋ณ์๋ ๊ฒ์ ์ฟผ๋ฆฌ(Search Params)๋ฅผ ๋ค๋ฃฐ ๋ ๋ฐ๋์ Promise๋ฅผ ๋ฐํํ๋ ํ์ ์ผ๋ก ์์ฑํด์ผ ํด์.
export default async function SearchPage({
searchParams,
}: {
searchParams: Promise<{ query: string }>;
}) {
const { query } = await searchParams;
return <h1>๊ฒ์์ด: {query}</h1>;
}์ด๋ ๊ฒ ํ๋ฉด URL์์ params์ searchParams ๊ฐ์ ์ฝ๊ฒ ๊ฐ์ ธ์ ์ฌ์ฉํ ์ ์์ด์!
๐ Dynamic Route (๋์ ๋ผ์ฐํธ)
๐ ๊ธฐ๋ณธ ๋์ ๋ผ์ฐํธ
ํด๋ ์ด๋ฆ์ [id] ๊ฐ์ ํ์์ ์ฌ์ฉํ๋ฉด ํด๋น ๊ฒฝ๋ก ๊ฐ์ ๋์ ์ผ๋ก ๋ฐ์ ์ ์์ด์. ๋ํ, params๋ Promise๋ฅผ ๋ฐํํ๊ธฐ ๋๋ฌธ์ Promise<{ id: string }>์ ๊ฐ์ด Promise ํ์
์ผ๋ก ์์ฑํด์ผ ํ๊ณ , ํ์ด์ง์ ๊ธฐ๋ณธ ๋ด๋ณด๋ด๊ธฐ ํจ์๋ async ํค์๋๋ฅผ ์ฌ์ฉํด์ผ ํด์. ์ฌ์ฉํ ๋๋ const { id } = await params;์ฒ๋ผ ๋์ ์ผ๋ก ํ์ฉํ ์ ์์ด์.
// app/book/[id]/page.tsx
export default async function BookDetailPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
return <h1>์ฑ
ID: {id}</h1>;
}์์ URL
/book/123โparams.id === '123'/book/456โparams.id === '456'
๐ ์ฌ๋ฌ ๊ฐ์ ๋์ ๋ผ์ฐํธ
[...id]๋ฅผ ์ฌ์ฉํ๋ฉด ์ฌ๋ฌ ๊ฐ์ ๊ฐ์ ๋ฐฐ์ด๋ก ๋ฐ์ ์ ์์ด์.// app/book/[...id]/page.tsx
export default function BookDetailPage({
params,
}: {
params: Promise<{ id: string[] }>;
}) {
const { id } = await params;
return <h1>์ฑ
ID ๋ชฉ๋ก: {id.join(", ")}</h1>;
}์์ URL
/book/123/456โparams.id === ['123', '456']
์ฃผ์
URL ํ๋ผ๋ฏธํฐ๊ฐ ์์ผ๋ฉด 404 ์๋ฌ๊ฐ ๋ฐ์ํ ์ ์์ด์.
๐ฏ Optional Catch All Segments
URL ํ๋ผ๋ฏธํฐ๊ฐ ์์ด๋ ๋์ํ๋๋ก ๋ง๋ค๊ณ ์ถ๋ค๋ฉด [[...id]]๋ฅผ ์ฌ์ฉํ๋ฉด ๋ผ์.
// app/book/[[...id]]/page.tsx
export default async function BookPage({
params,
}: {
params: Promise<{ id?: string[] }>;
}) {
const { id } = await params;
if (!id) {
return <h1>์ฑ
๋ฆฌ์คํธ ํ์ด์ง</h1>;
}
return <h1>์ฑ
ID ๋ชฉ๋ก: {id.join(", ")}</h1>;
}์์ URL
/bookโparams.id === undefinedโ ์ฑ ๋ฆฌ์คํธ ํ์ด์ง ์ถ๋ ฅ/book/1/2/3โparams.id === ['1', '2', '3']โ ์ฑ ID ๋ชฉ๋ก: 1, 2, 3
๐ Route Grouping (๋ผ์ฐํธ ๊ทธ๋ฃนํ)
๋ผ์ฐํธ๋ฅผ ๊ทธ๋ฃน์ผ๋ก ๋ฌถ์ด์ ๊ด๋ฆฌํ๊ณ ์ถ๋ค๋ฉด ()๋ฅผ ์ฌ์ฉํ๋ฉด ๋ผ์!
app/
โโโ (dashboard)/
โ โโโ layout.tsx
โ โโโ page.tsx
โ โโโ settings/page.tsx
โ โโโ profile/page.tsx์ด๋ ๊ฒ ํ๋ฉด (dashboard) ๊ทธ๋ฃน ์์ ์๋ ๋ชจ๋ ํ์ด์ง๊ฐ ๋์ผํ ๋ ์ด์์์ ๊ณต์ ํ ์ ์์ด์.
๐ Navigating (๋ค๋น๊ฒ์ดํ )
App Router์์ ํ์ด์ง ์ด๋์ด ๋ฐ์ํ๋ฉด ์๋ฒ๊ฐ JS Bundle๊ณผ RSC Payload๋ฅผ ์์ฑํ์ฌ ์ ๋ฌํด์. ์ดํ ๋ธ๋ผ์ฐ์ ๊ฐ ์ด๋ฅผ ์คํํ๋ฉด์ ํ์ด์ง๊ฐ ์ ํ๋์ฃ !
๐น ์ ์ ์ธ (Static) ํ์ด์ง
Production ํ๊ฒฝ์์๋ ๋ฏธ๋ฆฌ prefetch๊ฐ ๋ฐ์ํด์.
prefetch ์ JS Bundle + RSC Payload๊ฐ ํจ๊ป ์ ๋ฌ๋ผ์ ๋น ๋ฅด๊ฒ ํ์ด์ง๋ฅผ ๋ก๋ํ ์ ์์ด์.
๐ธ ๋์ ์ธ (Dynamic) ํ์ด์ง
prefetch ์ RSC Payload๋ง ์ ๋ฌ๋๊ณ , JS Bundle์ ํ์ํ ๋ ๋ธ๋ผ์ฐ์ ์์ ๋ก๋๋ผ์.
๋ฐ๋ผ์ ์ฒ์์๋ ๊ฐ๋ฒผ์ด ๋ฐ์ดํฐ๋ฅผ ๋ฐ๊ณ , ์ดํ ํ์ํ JS๊ฐ ์ถ๊ฐ๋ก ์คํ๋๋ ๋ฐฉ์์ด์์.
์ด๋ฐ ๋ฐฉ์ ๋๋ถ์ Next.js๋ ํ์ํ ๋ฐ์ดํฐ๋ง ์ ์ ํ๊ฒ ๋ถ๋ฌ์ ์ฑ๋ฅ์ ์ต์ ํํ ์ ์์ด์!
๐ Server Component๋?
Nextjs15์์๋ ๋ชจ๋ ์ปดํฌ๋ํธ๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก ์๋ฒ ์ปดํฌ๋ํธ์์. ์ฆ, ๋ธ๋ผ์ฐ์ ์์ ์คํ๋์ง ์๊ณ , ์๋ฒ์์๋ง ๋์ํ์ฃ .
โ Server Component์ ํน์ง
- ์๋ฒ์์ ์คํ๋๋ฏ๋ก API ํธ์ถ์ด ํจ์ฌ ํจ์จ์ ์ด์์.
- ์๋ฐ์คํฌ๋ฆฝํธ ๋ฒ๋ค ํฌ๊ธฐ๊ฐ ์ค์ด๋ค์ด์.
- ๋ณด์์ฑ์ด ์ข์์. (๋ฐ์ดํฐ๋ฒ ์ด์ค ์ธ์ฆ ์ ๋ณด ๋ฑ์ ๋ ธ์ถํ์ง ์์)
- ์๋ฒ ์ปดํฌ๋ํธ์์๋ hooks, ์ด๋ฒคํธ ํธ๋ค๋ฌ, ๋ธ๋ผ์ฐ์ API ๊ฐ์ ํด๋ผ์ด์ธํธ ์ ์ฉ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์์ด์.
// app/page.tsx
export default async function Home() {
const data = await fetchData();
return <div>{data}</div>;
}โก Client Component๋?
ํด๋ผ์ด์ธํธ์์ ์คํํด์ผ ํ๋ ๊ฒฝ์ฐ "use client" ์ง์์ด๋ฅผ ์ฌ์ฉํ๋ฉด ๋ผ์.
hooks, ์ด๋ฒคํธ ํธ๋ค๋ฌ, ๋ธ๋ผ์ฐ์ API ๋ฑ์ ์ฌ์ฉํ ์ ์์ด์.
// app/components/ClientComponent.tsx
"use client";
import { useState } from "react";
export default function ClientComponent() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>ํด๋ฆญ {count}</button>;
}์ฃผ์ํ ์
-
Client Component์์ Server Component๋ฅผ importํ ์ ์์ด์.
- ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ฑฐ๋ ์๋์น ์์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ด์.
- ๋ง์ฝ Server Component๋ฅผ Client Component์์ importํ๊ฒ ๋๋ฉด, Server Component๊ฐ ์๋์ผ๋ก Client Component๋ก ๋ณํ๋ผ์.
- ์ด๋ ๊ฒ ๋๋ฉด ์๋ฐ์คํฌ๋ฆฝํธ ๋ฒ๋ค์ ํฌ๊ธฐ๊ฐ ์ปค์ง ์ ์์ผ๋ฏ๋ก ํผํ๋ ๊ฒ ์ข์์.
- Client Component์์ ์คํํด์ผ ํ๋ Server Component๋ผ๋ฉด, Client Component์
childrenํ์ ์ผ๋ก Server Component๋ฅผ ์ ๋ฌํ๋ ๊ฒ ์ข์์. (์ด๋ ๊ฒ ํ๋ฉด Server Component๊ฐ Client Component๋ก ๋ณํ๋์ง ์์์.)
// app/page.tsx "use client"; import ServerComponent from "@/components/ServerComponent"; export default function Page() { return ( <ClientComponent> <ServerComponent /> </ClientComponent> ); }
๐ ์ ๋ฆฌ
1๏ธโฃ Nextjs15์ App Router์์ Page ์ปดํฌ๋ํธ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์๋ฒ ์ปดํฌ๋ํธ๋ผ์ async ํจ์๋ฅผ ์์ ๋กญ๊ฒ ์ฌ์ฉํ ์ ์์ด์!
2๏ธโฃ ๋์ ๋ผ์ฐํธ์์๋ params๊ฐ Promise๋ฅผ ๋ฐํํ๋ฏ๋ก, const { id } = await params; ํํ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์์ผ ํด์.
3๏ธโฃ Production ํ๊ฒฝ์์ ํ์ด์ง ์ด๋ ์ ์ ์ ํ์ด์ง๋ JS Bundle๊ณผ RSC Payload๋ฅผ ํจ๊ป prefetchํ๊ณ , ๋์ ํ์ด์ง๋ RSC Payload๋ง prefetchํ์ฌ ์ฑ๋ฅ์ ์ต์ ํํด์!
4๏ธโฃ ์ํธ์์ฉ์ด ํ์ํ ๊ฒฝ์ฐ์๋ Client Component๋ฅผ ์ฌ์ฉํ๊ณ ์ด ์ธ์๋ Server Component๋ฅผ ์ฌ์ฉํ๋ ๊ฒ ์ข์์! ํนํ, Client์์ Server Component๋ฅผ ์ง์ importํ๋ฉด ์๋์น ์์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ผ๋ ์ฃผ์ํ์ธ์!
Afaik ยฉ 2025
์ธ๋ถ ๋งํฌ