๐Ÿš€ Nextjs15 App Router ์‹œ์ž‘ํ•˜๊ธฐ 1

โ€ข

๐Ÿง 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 (๋™์  ๋ผ์šฐํŠธ)

Next.js์—์„œ๋Š” ๋™์  ๋ผ์šฐํŠธ๋ฅผ ์†์‰ฝ๊ฒŒ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์–ด์š”.

๐Ÿ“– ๊ธฐ๋ณธ ๋™์  ๋ผ์šฐํŠธ

ํด๋” ์ด๋ฆ„์— [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ํ•˜๋ฉด ์˜๋„์น˜ ์•Š์€ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ฃผ์˜ํ•˜์„ธ์š”!

์™ธ๋ถ€ ๋งํฌ