๐Ÿ“ Next.js 2์žฅ: App Router ํ•ด๋ถ€ํ•™ (ํŒŒ์ผ ์‹œ์Šคํ…œ ๋ผ์šฐํŒ…)

2026๋…„ 4์›” 30์ผ ์ˆ˜์ •๋จ

๐Ÿ“‹ ๊ฐœ์š”

App Router์˜ ํŒŒ์ผ ์‹œ์Šคํ…œ ๋ผ์šฐํŒ… ๊ตฌ์กฐ์™€ layout, page, loading, error ํŒŒ์ผ์˜ ์—ญํ• ์„ ํŒŒํ—ค์นฉ๋‹ˆ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ


๐Ÿ“Œ ์ด ๋ฌธ์„œ๋ฅผ ์ฝ๊ธฐ ์ „์—

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: 15๋ถ„(์ „์ฒด) / ํ•ต์‹ฌ ํŒŒํŠธ๋งŒ: 8๋ถ„

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ํ๋ฆ„
ํŒŒ์ผ ๊ธฐ๋ฐ˜ ๋ผ์šฐํŒ…์˜ ์›๋ฆฌ โ†’ Layout ๊ณผ Template ์˜ ์ฐจ์ด โ†’ Route Group โ†’ ์‹ค์ „ ์‹ค์Šต ( ์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ ์˜ˆ์ œ )

๐ŸŽฏ ์ด ๋ฌธ์„œ๋ฅผ ๋‹ค ์ฝ์œผ๋ฉด ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ

  • layout.tsx ์™€ template.tsx ๋ฅผ ์ƒํ™ฉ์— ๋งž๊ฒŒ ๊ณจ๋ผ์„œ ์“ธ ์ˆ˜ ์žˆ๋‹ค.
  • ์ƒํƒœ(State) ๊ฐ€ ์œ ์ง€๋˜์–ด์•ผ ํ•˜๋Š” UI ์™€ ์ƒˆ๋กœ ๊ฐฑ์‹ ๋˜๋Š” UI ๋ฅผ ํด๋” ๊ตฌ์กฐ๋กœ ๋ถ„๋ฆฌ ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋ผ์šฐํŠธ ๊ทธ๋ฃน์„ ํ™œ์šฉํ•ด ๋ณต์žกํ•œ ๋Œ€์‹œ๋ณด๋“œ์™€ ์œ ์ € ํŽ˜์ด์ง€์˜ ๋ ˆ์ด์•„์›ƒ์„ ์šฐ์•„ํ•˜๊ฒŒ ๊ณ ๋ฆฝ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ๋ฐฐ๊ฒฝ ์„ธ๊ณ„๊ด€: '์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ'

  • ๐Ÿฃ ์˜์ฒ  ( ์‹ ์ž… ): "๋ฆฌ๋“œ ๋‹˜! ์Šคํ„ฐ๋”” ์นดํ…Œ๊ณ ๋ฆฌ ํƒญ์„ ๋ˆ„๋ฅผ ๋•Œ๋งˆ๋‹ค ํ™”๋ฉด์ด ๋œ์ปน๊ฑฐ๋ฆฌ๋ฉด์„œ, ํž˜๋“ค๊ฒŒ ๊ฒ€์ƒ‰ํ•ด๋‘” ์‚ฌ์ด๋“œ๋ฐ” ํ•„ํ„ฐ๊ฐ’์ด ์ž๊พธ ์ดˆ๊ธฐํ™”๋ผ์š”! ์œ ์ž… ๊ฒฝ๋กœ๊ฐ€ ๋ฐ”๋€Œ๋ฉด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‹ค ์ค‘๋‹จ๋˜๋Š” ๊ฑด๊ฐ€์š”?"
  • ๐Ÿฆ ์˜ํ˜ธ ( ๋ฆฌ๋“œ ): "์˜์ฒ  ๋‹˜, ๊ทธ๊ฑด ๋ณ€ํ•˜์ง€ ์•Š๋Š” ๋ฐฐ๊ฒฝ(Layout) ๊ณผ ๋ณ€ํ•˜๋Š” ์ฃผ์ธ๊ณต(Page) ์„ ํ•œ ํŒŒ์ผ์— ํ•œ ํŒŒ์ผ์— ํ•จ๊ป˜ ๋‘์–ด์„œ ๊ทธ๋ž˜์š”. ๋ฆฌ์•กํŠธ ๋ผ์šฐํ„ฐ ์‹œ์ ˆ์˜ ๊ด€์Šต์„ ์•„์ง ๋ชป ๋ฒ„๋ฆฌ์…จ๊ตฐ์š”."
  • ๐Ÿ‘” ์˜์ˆ˜ ( PM ): "์˜์ฒ  ์”จ, ํ•„ํ„ฐ๊ฐ€ ๋งค๋ฒˆ ์ดˆ๊ธฐํ™”๋˜๋ฉด ์œ ์ €๋“ค์ด ์Šคํ„ฐ๋”” ์ฐพ๋‹ค๊ฐ€ ํ™”๋‚˜์„œ ์•ฑ ์ง€์šธ๊ฑธ์š”? ์ด๊ฑฐ ๋น„์ฆˆ๋‹ˆ์Šค์ ์œผ๋กœ ์น˜๋ช…์ ์ž…๋‹ˆ๋‹ค."
  • ๐ŸŽจ ์˜์ˆ™ ( UX ): "๋งž์•„์š”. ๊ทธ๋ฆฌ๊ณ  ํƒญ์„ ๋„˜๊ธธ ๋•Œ๋งˆ๋‹ค ํ™”๋ฉด์ด ์Šˆ์›…~ ์˜ˆ์˜๊ฒŒ ๋‚˜ํƒ€๋‚˜๋ฉด ์ข‹๊ฒ ๋Š”๋ฐ, layout ์€ ์ ˆ๋Œ€๋กœ ๋‹ค์‹œ ์•ˆ ๊ทธ๋ ค์ง„๋‹ค๋ฉด์„œ์š”? ์ œ ์• ๋‹ˆ๋ฉ”์ด์…˜์€ ์–ด์ฉŒ์ฃ ?"
  • ๐Ÿฆ ์˜ํ˜ธ ( ๋ฆฌ๋“œ ): "์ž, ๋‹ค๋“ค ์ฃผ๋ชฉ! ๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ์—๊ฒ Layout ๊ณผ Template, ๊ทธ๋ฆฌ๊ณ  Route Group ์ด๋ผ๋Š” ๋„๊ตฌ๊ฐ€ ์žˆ๋Š” ๊ฒ๋‹ˆ๋‹ค. ์˜ค๋Š˜ ์ด ๋Œ€์ƒ๋“ค์˜ ํ•ด๋ถ€ํ•™์„ ์ œ๋Œ€๋กœ ํŒŒํ—ค์ณ ๋ณด์ฃ ."

๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€

์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ("๊ฐœ๋ฐœ์ž ์Šคํ„ฐ๋”” ๋งค์นญ ์•ฑ") ํ”„๋กœ์ ํŠธ ์ƒํ™ฉ์ด์•ผ.

์˜์ฒ (์ƒˆ๋กœ ์˜จ ์ฃผ๋‹ˆ์–ด) ์ด๊ฐ€ ์Šคํ„ฐ๋”” ์ฐพ๊ธฐ ํŽ˜์ด์ง€(/studies) ์—์„œ ๋„ค๋น„๊ฒŒ์ด์…˜ ํƒญ(ํ”„๋ก ํŠธ์—”๋“œ ๋ชจ์ง‘, ๋ฐฑ์—”๋“œ ๋ชจ์ง‘) ์„ ๋ˆ„๋ฅผ ๋•Œ๋งˆ๋‹ค ํ™”๋ฉด ์ „์ฒด๊ฐ€ ๊นœ๋นก์ด๋ฉด์„œ ์‚ฌ์ด๋“œ๋ฐ” ํ•„ํ„ฐ๊นŒ์ง€ ์ดˆ๊ธฐํ™”๋˜๋Š” ๋ฌธ์ œ๋ฅผ ๊ฒช์—ˆ์–ด. ์˜์ฒ ์ด๋Š” React Router ์ฒ ํ•™๋Œ€๋กœ ๊ฑฐ๋Œ€ํ•œ App.tsx ์—์„œ ๋ชจ๋“  ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋ ค๋‹ค ์œ ์ง€๋ณด์ˆ˜ ๋‚œ๋งฅ(Maintenance Nightmare) ์— ๋น ์ง€๊ณ  ๋งŒ ๊ฑฐ์•ผ.

์˜ํ˜ธ(FE ๋ฆฌ๋“œ) ๊ฐ€ ํ•œ์ˆจ์„ ์‰ฌ๋ฉฐ ๋‹ค๊ฐ€์™”์–ด. "์˜์ฒ ์•„, Next.js App Router ์˜ layout.tsx ๊ฐ€ ์™œ ์กด์žฌํ•˜๋Š”์ง€ ์•„์ง ๋ชจ๋ฅด๋Š”๊ตฌ๋‚˜. ๋ณ€ํ•˜์ง€ ์•Š๋Š” ๋ฐฐ๊ฒฝ๊ณผ ๋ณ€ํ•˜๋Š” ์ฃผ์ธ๊ณต์„ ํŒŒ์ผ ๋‹จ์œ„๋กœ ์ชผ๊ฐœ์•ผ ํ•ด."

Next.js App Router ๋Š” "ํด๋” ์ด๋ฆ„์ด ๊ณง URL ์ด๊ณ , ํŒŒ์ผ ์ด๋ฆ„์ด ๊ณง ์—ญํ• ์ด๋‹ค" ๋ผ๋Š” ๊ฐ•๋ ฅํ•œ ์›์น™ ์ œ๊ณตํ•ด. ํŠน์ •ํ•œ ์˜ˆ์•ฝ์–ด ํŒŒ์ผ๋“ค(layout.tsx, template.tsx, loading.tsx) ์˜ ๋™์ž‘ ๋ฐฉ์‹๊ณผ ์ƒ์กด ์ฃผ๊ธฐ(์ƒ๋ช…์ฃผ๊ธฐ) ๋ฅผ ๋ชจ๋ฅด๋ฉด, ๋ถˆํ•„์š”ํ•œ ์žฌ๋ Œ๋”๋ง์— ์˜์›ํžˆ ๊ณ ํ†ต๋ฐ›๊ฒŒ ๋  ๊ฑฐ์•ผ.


๐Ÿ—๏ธ ๋น„์œ ๋กœ ๋จผ์ € ์ดํ•ดํ•˜๊ธฐ

๐Ÿค” ์ž ๊น, ๋จผ์ € ์ƒ๊ฐํ•ด๋ด
์šฐ๋ฆฌ๊ฐ€ ์‚ฌ๋Š” ์ง‘์„ ๋– ์˜ฌ๋ ค๋ณด์ž. ๊ฑฐ์‹ค์˜ ๋ฒฝ์ง€๋Š” ๊ทธ๋Œ€๋กœ์ธ๋ฐ, TV ์† ํ™”๋ฉด๋งŒ ๋ฐ”๋€Œ๋Š” ๊ฒƒ๊ณผ ๊ฑฐ์‹ค ์ธํ…Œ๋ฆฌ์–ด ์ „์ฒด๋ฅผ ๋งค๋ฒˆ ๋‹ค ๋œฏ์–ด๊ณ ์น˜๋Š” ๊ฒƒ ์ค‘ ๋ฌด์—‡์ด ๋” ํšจ์œจ์ ์ผ๊นŒ?

๐Ÿ–ผ๏ธ ๋งˆํŠธ๋ฃŒ์‹œ์นด ์ธํ˜•๊ณผ ์•ก์ž ๋น„์œ 

Next.js ์˜ ๋ผ์šฐํŒ… ๊ตฌ์กฐ๋Š” ๊ฒน๊ฒน์ด ์Œ“์ธ ์•ก์ž ํ”„๋ ˆ์ž„ ๊ณผ ๊ฐ™์•„.

  • layout.tsx: ๊ฐ€์žฅ ๋ฐ”๊นฅ์ชฝ์˜ ํŠผํŠผํ•œ ๋‚˜๋ฌด ์•ก์ž ํ‹€. ์•ˆ์˜ ๊ทธ๋ฆผ์„ ์•„๋ฌด๋ฆฌ ๋ฐ”๊ฟ” ๋ผ์›Œ๋„, ๋ฐ”๊นฅ ์•ก์ž ํ‹€์€ ์ „ํ˜€ ์›€์ง์ด๊ฑฐ๋‚˜ ๋ถ€์„œ์ง€์ง€ ์•Š์•„. (์ƒํƒœ๊ฐ€ ์˜์›ํžˆ ์œ ์ง€๋จ)
  • page.tsx: ์•ก์ž ์† ์•Œ๋งน์ด ๊ทธ๋ฆผ. URL ์ด ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค ๊ต์ฒด๋˜๋Š” ํ•ต์‹ฌ ๋‚ด์šฉ์ด์•ผ.
  • template.tsx: ์ƒ๊น€์ƒˆ๋Š” layout ๊ณผ ๋น„์Šทํ•œ๋ฐ, ๊ทธ๋ฆผ ๊ต์ฒดํ•  ๋•Œ๋งˆ๋‹ค ์•ก์ž ํ‹€๊นŒ์ง€ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ์ƒˆ ์•ก์ž๋กœ ๋‹ค์‹œ ๊ฐˆ์•„ ๋ผ์šฐ๋Š” ์˜ˆ๋ฏผํ•œ ๋Œ€์ƒ ์ด์•ผ. ์• ๋‹ˆ๋ฉ”์ด์…˜ ์‹คํ–‰์ด๋‚˜ ์ดˆ๊ธฐํ™”๋ฅผ ๊ฐ•์ œํ•  ๋•Œ ์“ฐ์ง€.

๐Ÿง’ 5์‚ด์—๊ฒŒ ์„ค๋ช…ํ•œ๋‹ค๋ฉด?
"๋ ˆ์ด์•„์›ƒ์€ ํŠผํŠผํ•œ ์šฐ๋ฆฌ ์ง‘ ๋ฒฝ์ด์•ผ. ์šฐ๋ฆฌ๊ฐ€ ๊ฑฐ์‹ค์—์„œ ๋ฐฉ์œผ๋กœ ๊ฐ€๋„ ๋ฒฝ์€ ๊ทธ๋Œ€๋กœ์ง€? ํ•˜์ง€๋งŒ ํ…œํ”Œ๋ฆฟ์€ ์šฐ๋ฆฌ๊ฐ€ ๋ฐฉ์— ๋“ค์–ด๊ฐˆ ๋•Œ๋งˆ๋‹ค ๋งค๋ฒˆ ์ƒˆ๋กœ ์ผœ์ง€๋Š” ์กฐ๋ช… ๊ฐ™์€ ๊ฑฐ์•ผ!"

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
Layout ์€ ๋ณด์กด, Template ์€ ์žฌ์ƒ์„ฑ์ด๋‹ค.


๐Ÿ“„ page.tsx์™€ layout.tsx์˜ ๊ณต์ƒ ๊ด€๊ณ„ ๐ŸŸข

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • Next.js ๊ฐ€ ํ™”๋ฉด์„ ์กฐ๋ฆฝํ•  ๋•Œ ๋ถ€๋ชจ-์ž์‹ ๊ด€๊ณ„๊ฐ€ ์–ด๋–ป๊ฒŒ ์œ ์ง€๋˜๋Š”์ง€ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ƒํƒœ ๋ณด์กด์„ ์œ„ํ•ด ์™œ Layout ์„ ์จ์•ผ ํ•˜๋Š”์ง€ ๊ธฐ์ˆ ์ ์œผ๋กœ ์ฆ๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค.

1. page.tsx: ๋ผ์šฐํŠธ์˜ ์œ ์ผํ•œ ์ฃผ์ธ๊ณต

ํ•ด๋‹น ์ฃผ์†Œ(/studies)๋กœ ์ ‘๊ทผํ–ˆ์„ ๋•Œ ๊ทธ๋ ค์ง€๋Š” ๋ฉ”์ธ UI์•ผ.

2. layout.tsx: ๋ณ€ํ•˜์ง€ ์•Š๋Š” ๋“ ๋“ ํ•œ ๋ฐฐ๊ฒฝ

์ž์‹ ๋ผ์šฐํŠธ ์‚ฌ์ด๋ฅผ ์ด๋™ํ•  ๋•Œ ์ƒํƒœ(State)๋ฅผ ๋ณด์กดํ•˜๊ณ  ๋ฆฌ๋ Œ๋”๋ง์„ ์ฐจ๋‹จํ•ด์ฃผ๋Š” ์ผ๋“ฑ ๊ณต์‹ ์ด์•ผ. ์˜์ฒ ์ด์˜ ๊ณ ๋ฏผ์ด ์—ฌ๊ธฐ์„œ ํ•ด๊ฒฐ๋˜์ง€.

โŒ ์ˆœ์ง„ํ•œ ์ฝ”๋“œ (Naive Approach)
ํŽ˜์ด์ง€ ํŠธ๋žœ์ง€์…˜ ๋กœ์ง์„ page.tsx ์ตœ์ƒ๋‹จ ๋ถ€๋ชจ์—์„œ ๋ฌด๋ฆฌํ•˜๊ฒŒ ์กฐ์ž‘ํ•ด ํ™”๋ฉด ๊นœ๋นก์ž„ ์œ ๋ฐœ.

โœ… ์šฐ์•„ํ•œ ์ฝ”๋“œ ( Pro Approach ) - ์˜ํ˜ธ์˜ ํ•ด๊ฒฐ์ฑ…

// app/studies/layout.tsx
// ๐Ÿฆ ์˜ํ˜ธ: "์ด ํŒŒ์ผ์€ URL์ด ๋ฐ”๋€Œ์–ด๋„ ์ ˆ๋Œ€ ๋‹ค์‹œ ๋ Œ๋”๋˜์ง€ ์•Š๊ณ  ๊ตณ๊ฑดํžˆ ๋ฒ„ํŒ๋‹ˆ๋‹ค."
 
export default function StudiesLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex">
      {/* ๐Ÿฃ ์˜์ฒ : "์™€! ์ด์ œ ํ•„ํ„ฐ์— ์ ์–ด๋‘” ๊ฒ€์ƒ‰์–ด๊ฐ€ ์•ˆ ์‚ฌ๋ผ์ ธ์š”!" */}
      <nav>๊ฐ•๋ ฅํ•œ ์Šคํ„ฐ๋”” ๊ฒ€์ƒ‰ ์‚ฌ์ด๋“œ๋ฐ” ํ•„ํ„ฐ (์ƒํƒœ ์œ ์ง€๋จ)</nav>
 
      {/* ๐Ÿฆ ์˜ํ˜ธ: "children ์ž๋ฆฌ์˜ page.tsx๋งŒ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๊ต์ฒด๋˜๋Š” ๊ฒ๋‹ˆ๋‹ค." */}
      <main className="flex-1">{children}</main>
    </div>
  )
}

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
Page ๋Š” ๋งค๋ฒˆ ๋ฐ”๋€Œ๋Š” ์ฃผ์ธ๊ณต, Layout ์€ ๊ทธ ์ฃผ์ธ๊ณต์„ ์ง€์ผœ์ฃผ๋Š” ๋ฌด๋Œ€ ๋ฐฐ๊ฒฝ์ด๋‹ค.


๐Ÿงฉ layout.tsx vs template.tsx โ€” ๋ฌด์—‡์ด ๋‹ค๋ฅผ๊นŒ? ๐ŸŸก

๐Ÿค” ์ž ๊น, ๋จผ์ € ์ƒ๊ฐํ•ด๋ด
์˜์ˆ™(๋””์ž์ด๋„ˆ)์ด๊ฐ€ ์š”๊ตฌํ–ˆ์–ด. "์Šคํ„ฐ๋”” ํƒญ ํŽ˜์ด์ง€ ์ด๋™ํ•  ๋•Œ๋งˆ๋‹ค ํ™”๋ฉด์ด ์™ผ์ชฝ์—์„œ ์Šˆ์›…~ ๋‚˜ํƒ€๋‚˜๊ฒŒ (Framer Motion) ํ•ด์ฃผ์„ธ์š”! ๊ทธ๋ฆฌ๊ณ  ์ด๋™ํ•  ๋•Œ๋งˆ๋‹ค ํŽ˜์ด์ง€ ๋ทฐ(GA) ์ด๋ฒคํŠธ๋„ ์˜์•„์ฃผ์‹œ๊ณ ์š”."
์ด๊ฑธ ์ ˆ๋Œ€ ๋ฆฌ๋ Œ๋”๋ง๋˜์ง€ ์•Š๋Š” ์ฒ ๋ฒฝ๋ฐฉ์–ด layout.tsx์— ๋ถ™์—ฌ๋‘๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?

์ด๊ฒŒ ๋ญ”๊ฐ€?

template.tsx๋Š” ๋ฌธ๋ฒ•์ ์œผ๋กœ layout๊ณผ ์™„์ „ ๋™์ผ({ children } ํŒจํ„ด)ํ•˜์ง€๋งŒ, ์‚ฌ์šฉ์ž๊ฐ€ ์ž์‹ ๋ผ์šฐํŠธ๋กœ ์ง„์ž…/๋ณ€๊ฒฝํ•  ๋•Œ๋งˆ๋‹ค DOM์„ ์ƒˆ๋กœ ๋งˆ์šดํŠธ(Re-mount)ํ•ด๋ฒ„๋ฆฌ๋Š” ํŒŒ์ผ์ด์•ผ.

์‹ค๋ฌด์—์„œ ์–ด๋–ป๊ฒŒ ํ™œ์šฉ๋˜๋Š”๊ฐ€?

useEffect ๋ฅผ ์ด์šฉํ•ด '๊ฒฝ๋กœ ๋ฐฉ๋ฌธ ์‹œ 1ํšŒ ๋ฐœ๋™' ํ•˜๋Š” ๋กœ์ง์ด๋‚˜ ํŠธ๋žœ์ง€์…˜ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋ถ™์ผ ๋•Œ ์™„๋ฒฝํ•ด.

// app/studies/template.tsx
'use client' // ๐ŸŽจ ์˜์ˆ™: "์• ๋‹ˆ๋ฉ”์ด์…˜์€ ํด๋ผ์ด์–ธํŠธ์—์„œ ๋Œ์•„๊ฐ€์•ผ ํ•˜๋‹ˆ๊นŒ์š”!"
import { useEffect } from 'react'
 
export default function StudyTemplate({ children }: { children: React.ReactNode }) {
  useEffect(() => {
    // ๐Ÿฆ ์˜ํ˜ธ: "ํƒญ์„ ๋ˆ„๋ฅผ ๋•Œ๋งˆ๋‹ค ์—ฌ๊ธฐ๊ฐ€ ๋ฐœ๋™๋˜๋Š” ๋งˆ๋ฒ•์„ ๋ณด์„ธ์š”."
    console.log('๐Ÿ“Œ ํŽ˜์ด์ง€ ๋ทฐ ์ „ํ™˜ ์ด๋ฒคํŠธ ์ˆ˜์ง‘ ๋ฐœ๋™!')
  }, [])
 
  return <div className="animate-slide-in">{children}</div>
}

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
ํ•„ํ„ฐ ๊ฐ’์„ ์ง€ํ‚ค๊ณ  ์‹ถ๋‹ค๋ฉด Layout, ํด๋ฆญํ•  ๋•Œ๋งˆ๋‹ค ๋ถˆ๊ฝƒ๋†€์ด๋ฅผ ํ„ฐ๋œจ๋ฆฌ๊ณ  ์‹ถ๋‹ค๋ฉด Template ์ด๋‹ค.


๐Ÿ”€ layout.tsx + template.tsx ํ•จ๊ป˜ ์“ฐ๋ฉด? ๐ŸŸก

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • ๊ฐ™์€ ํด๋”์— layout.tsx์™€ template.tsx๊ฐ€ ๊ณต์กดํ•  ๋•Œ ๋ Œ๋” ์ˆœ์„œ๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค
  • ๋‘ ํŒŒ์ผ์˜ ์—ญํ• ์„ ๋ถ„๋ฆฌํ•ด์„œ ์‹ค๋ฌด์— ์‘์šฉํ•  ์ˆ˜ ์žˆ๋‹ค

๋‘˜์ด ๊ณต์กดํ•  ์ˆ˜ ์žˆ์–ด?

๊ฒฐ๋ก ๋ถ€ํ„ฐ ๋งํ•˜๋ฉด ์™„๋ฒฝํ•˜๊ฒŒ ๊ณต์กด ๊ฐ€๋Šฅํ•ด. Next.js๋Š” ๊ฐ™์€ ํด๋”์— layout.tsx์™€ template.tsx๊ฐ€ ๋ชจ๋‘ ์žˆ์œผ๋ฉด ์ด ์ˆœ์„œ๋Œ€๋กœ ๊ฐ์‹ธ์„œ ๋ Œ๋”ํ•ด:

layout.tsx
  โ””โ”€ template.tsx
       โ””โ”€ page.tsx

์ฆ‰ layout์ด ๊ฐ€์žฅ ๋ฐ”๊นฅ์ชฝ, template์ด ๊ทธ ์•ˆ์ชฝ, page๊ฐ€ ์•Œ๋งน์ด ๊ตฌ์กฐ์•ผ.

์–ธ์ œ ์ด๋ ‡๊ฒŒ ์“ธ๊นŒ?

์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ ์ƒํ™ฉ์ด์•ผ. ์˜ํ˜ธ๊ฐ€ ์Šคํ„ฐ๋”” ๋ฆฌ์ŠคํŠธ ํŽ˜์ด์ง€์— ๋‘ ๊ฐ€์ง€๋ฅผ ๋™์‹œ์— ์›ํ•ด:

  • ๊ฒ€์ƒ‰ ํ•„ํ„ฐ ์‚ฌ์ด๋“œ๋ฐ” ์ƒํƒœ๋ฅผ URL ์ด๋™ ์ค‘์—๋„ ์œ ์ง€ํ•ด์•ผ ํ•œ๋‹ค (โ†’ layout)
  • ํƒญ์„ ๋ˆŒ๋Ÿฌ ์ƒˆ ์Šคํ„ฐ๋”” ๋ชฉ๋ก์œผ๋กœ ์ด๋™ํ•  ๋•Œ๋งˆ๋‹ค ํŽ˜์ด์ง€ ์ง„์ž… ์• ๋‹ˆ๋ฉ”์ด์…˜๊ณผ GA ์ด๋ฒคํŠธ๋ฅผ ์ด์•ผ ํ•œ๋‹ค (โ†’ template)
// app/studies/layout.tsx โ† ๋ฐ”๊นฅ์ชฝ (์˜๊ตฌ ์ƒ์กด, ์ƒํƒœ ๋ณด์กด)
export default function StudiesLayout({ children }) {
  return (
    <div className="flex">
      <Sidebar />  {/* URL์ด ๋ฐ”๋€Œ์–ด๋„ ์ด ์‚ฌ์ด๋“œ๋ฐ”๋Š” ์ ˆ๋Œ€ ์‚ฌ๋ผ์ง€์ง€ ์•Š์Œ */}
      <main>{children}</main>
    </div>
  )
}
// app/studies/template.tsx โ† ์•ˆ์ชฝ (ํƒญ ์ด๋™๋งˆ๋‹ค ๊ฐ•์ œ ์žฌ๋งˆ์šดํŠธ)
'use client'
import { useEffect } from 'react'
 
export default function StudiesTemplate({ children }) {
  useEffect(() => {
    // ํƒญ์„ ๋ˆ„๋ฅผ ๋•Œ๋งˆ๋‹ค(URL ๋ณ€๊ฒฝ ์‹œ) ์—ฌ๊ธฐ๊ฐ€ ๋ฐœ๋™๋จ โ† template์ด๊ธฐ ๋•Œ๋ฌธ
    sendGAEvent('page_view', { path: window.location.pathname })
  }, [])
 
  return (
    <div className="animate-fade-in">  {/* ์Šˆ์›…~ ํŽ˜์ด๋“œ์ธ ์• ๋‹ˆ๋ฉ”์ด์…˜ */}
      {children}
    </div>
  )
}
// app/studies/[category]/page.tsx โ† ์•Œ๋งน์ด (์นดํ…Œ๊ณ ๋ฆฌ๋ณ„๋กœ ๊ต์ฒด๋จ)
export default function StudyListPage({ params }) {
  return <StudyList category={params.category} />
}

๊ฒฐ๊ณผ:

  • ์‚ฌ์ด๋“œ๋ฐ” ํ•„ํ„ฐ ์ƒํƒœ โ†’ layout์ด ๋ฒ„ํ…จ์ค˜์„œ URL ์ด๋™ ํ›„์—๋„ ๋ณด์กด โœ…
  • ํŽ˜์ด๋“œ์ธ ์• ๋‹ˆ๋ฉ”์ด์…˜ + GA ์ด๋ฒคํŠธ โ†’ template์ด ๋งค๋ฒˆ ์žฌ๋งˆ์šดํŠธ๋˜๋ฉฐ ๋ฐœ๋™ โœ…

๋ Œ๋” ์ˆœ์„œ ์ •๋ฆฌ

๐Ÿค” ์ž ๊น, ๋จผ์ € ์ƒ๊ฐํ•ด๋ด
๋งŒ์•ฝ layout ์•ˆ์— template ์ด ์žˆ๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ๋ฐ˜๋Œ€๋กœ template ์ด layout ์„ ๊ฐ์‹ธ๊ณ  ์žˆ๋‹ค๋ฉด ์–ด๋–ค ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ๊นŒ? (ํžŒํŠธ: ์ƒํƒœ ์œ ์ง€)

[URL: /studies/frontend ์ง„์ž…]
1. layout.tsx ๋งˆ์šดํŠธ (์ตœ์ดˆ 1ํšŒ, ์ดํ›„ ๋ณด์กด)
2. template.tsx ๋งˆ์šดํŠธ (์ง„์ž…๋งˆ๋‹ค ์ƒˆ๋กœ ๋งˆ์šดํŠธ)
3. page.tsx ๋ Œ๋” (URL ๋ณ€๊ฒฝ๋งˆ๋‹ค ๊ต์ฒด)
 
[URL: /studies/backend ์ด๋™ ์‹œ]
1. layout.tsx โ†’ ์œ ์ง€ (์‚ฌ์ด๋“œ๋ฐ” ์ƒํƒœ ์‚ด์•„์žˆ์Œ) โœ…
2. template.tsx โ†’ ์ดˆ๊ธฐํ™” ํ›„ ์ƒˆ๋กœ ๋งˆ์šดํŠธ (์• ๋‹ˆ๋ฉ”์ด์…˜/GA ๋ฐœ๋™) ๐Ÿ”„
3. page.tsx โ†’ ์ƒˆ ์นดํ…Œ๊ณ ๋ฆฌ๋กœ ๊ต์ฒด ๐Ÿ”„

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
Layout ์€ ์˜๊ตฌ์ ์ธ ๊ทผ๊ฑฐ์ง€, Template ์€ ๊ทธ ์•ˆ์—์„œ ํƒญ๋งˆ๋‹ค ์ƒˆ๋กœ ์ง€์–ด์ง€๋Š” ํ…ํŠธ์™€ ๊ฐ™๋‹ค.


๐ŸŽญ ๋ผ์šฐํŠธ ๊ทธ๋ฃน (folder)์˜ ๋งˆ๋ฒ• ๐ŸŸก

์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ์—๋Š” ์ผ๋ฐ˜ ์œ ์ €๋“ค์ด ๋ณด๋Š” **"์Šคํ† ์–ด/์Šคํ„ฐ๋”” ๋ฉ”์ธ ์˜์—ญ"**๊ณผ ์˜์ˆ˜๋งŒ ๋“ค์–ด๊ฐ€๋Š” "๊ด€๋ฆฌ์ž ์–ด๋“œ๋ฏผ(admin)" ์˜์—ญ์ด ์žˆ์–ด.
์ด ๋‘˜์€ ์‚ฌ์ด๋“œ๋ฐ” ๋ชจ์–‘, ํ—ค๋”๊นŒ์ง€ 180๋„ ๋‹ค๋ฅด๊ฒŒ ์ƒ๊ฒผ์ง€.

โŒ ์ˆœ์ง„ํ•œ ์ฝ”๋“œ (Naive Approach)
๋ฃจํŠธ ์ตœ์ƒ๋‹จ app/layout.tsx ํŒŒ์ผ ๋‚ด์—์„œ ๊ธธ๊ณ  ์ง€์ €๋ถ„ํ•œ if (pathname.includes('admin')) return AdminLayout ํ˜•ํƒœ์˜ ์‚ผํ•ญ ์—ฐ์‚ฐ์ž๋ฅผ ๋„ฃ์–ด๋ฒ„๋ฆฌ๋Š” ์˜ค๋ž˜๋œ ๋ฐฉ์‹์˜ ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ. ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ ๊ฐ„ ์˜ค์—ผ ๋ฐœ์ƒ.

โœ… ์šฐ์•„ํ•œ ์ฝ”๋“œ ( Pro Approach ) - ๋ผ์šฐํŠธ ๊ทธ๋ฃน ์ ์šฉ
ํด๋” ์ด๋ฆ„ ์•ž๋’ค์— ๊ด„ํ˜ธ () ๋ฅผ ๋ฌถ์–ด์ฃผ์–ด "URL ๊ฒฝ๋กœ์—์„œ๋Š” ์™„์ „ํžˆ ์ƒ๋žต" ํ•˜๋ฉด์„œ "์กฐ์ง์ ์ธ ํŒŒ์ผ ๊ทธ๋ฃน" ์„ ๋งŒ๋“ค์–ด๋‚ด.

app/
 โ”œโ”€ (consumer)/
 โ”‚   โ”œโ”€ layout.tsx      // ๐ŸŽจ ์˜์ˆ™: "์ผ๋ฐ˜ ์œ ์ €์šฉ ๊น”๋”ํ•œ ๋ ˆ์ด์•„์›ƒ"
 โ”‚   โ”œโ”€ page.tsx        // url: '/'
 โ”‚   โ””โ”€ studies/page.tsx// url: '/studies'
 โ”‚
 โ””โ”€ (admin)/
     โ”œโ”€ layout.tsx      // ๐Ÿ‘” ์˜์ˆ˜: "์‹œ์ปค๋จผ ์–ด๋“œ๋ฏผ ์ „์šฉ ์‚ฌ์ด๋“œ๋ฐ” ๋ ˆ์ด์•„์›ƒ!"
     โ””โ”€ dashboard/page.tsx // url: '/dashboard'

์ด ๊ตฌ์กฐ ๋•๋ถ„์—, ์™„๋ฒฝํžˆ ๊ณ ๋ฆฝ๋œ ์ตœ์ƒ์œ„ ๋ ˆ์ด์•„์›ƒ์„ 2๊ฐœ ์ด์ƒ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๊ฒŒ ๋ผ!

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
Route Group ์€ URL ์— ํ”์ ์„ ๋‚จ๊ธฐ์ง€ ์•Š๋Š” ์œ ๋ น ํด๋”์ด์ž, ๋ ˆ์ด์•„์›ƒ ๋ถ„๋ฆฌ ์ˆ˜๊ฑฐํ•จ์ด๋‹ค.


๐Ÿ’ฅ ์—๋Ÿฌ ํ•ด๊ฒฐ ์นดํƒˆ๋กœ๊ทธ

โŒ You cannot define a route with the same specificity...

์›์ธ: (consumer)/page.tsx ์™€ (admin)/page.tsx ๋ฅผ ๋‘˜ ๋‹ค ๋งŒ๋“ค์–ด ๋ฒ„๋ ธ์Œ.
๊ด„ํ˜ธ ๋ ˆ์ด์–ด๋Š” URL์—์„œ ๋ฌด์‹œ๋˜๋ฏ€๋กœ, ๋‘ ํŽ˜์ด์ง€ ๋ชจ๋‘ ์ธํ„ฐ๋„ท ์ฃผ์†Œ๋กœ๋Š” / ์ตœ์ƒ์œ„๋ฅผ ๋ฐ”๋ผ๋ณด๊ฒŒ ๋˜์–ด ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•ด.
ํ•ด๊ฒฐ์ฑ…: ๊นŠ์ด๊ฐ€ ๊ฒน์น˜๋Š” ๋ฃจํŠธ ํŽ˜์ด์ง€ ๋ผ์šฐํŒ… ์‹œ์—๋Š” ๊ทธ๋ฃน ์ค‘ ํ•˜๋‚˜์—๋งŒ ๋ฐฐ์น˜ํ•˜๊ฑฐ๋‚˜ ํด๋” ๊ฒฝ๋กœ๋ฅผ ๋ช…์‹œํ•ด์•ผ ํ•ด.


๐Ÿ ์ด๋ฒˆ์— ๋ฐฐ์šด ๋‚ด์šฉ ์ด์ •๋ฆฌ

์˜ค๋Š˜ ๋ฐฐ์šด ํ•ต์‹ฌ์„ ํ•œ๋ˆˆ์— ์ •๋ฆฌํ•ด๋ณผ๊นŒ? ์‹ค๋ฌด์—์„œ ๊ธธ์„ ์žƒ์—ˆ์„ ๋•Œ ์ด๊ฒƒ๋งŒ ๋ด๋„ ๋ผ.

๐Ÿ“‹ ํŠน์ˆ˜ ํŒŒ์ผ๋ช… ์—ญํ•  ์š”์•ฝ

ํŠน์ˆ˜ ํŒŒ์ผ๋ช…์ฃผ๋œ ๋ชฉ์ ๊ฒฝ๋กœ ์ด๋™ ์‹œ ๋งˆ์šดํŠธ ์œ ์ง€ ์—ฌ๋ถ€
layout.tsx๊ตฌ์กฐ ์œ ์ง€, ์žฌ๋ Œ๋”๋ง ๋ฐฉ์ง€ (์ƒํƒœ ๋ณด์กด)โœ… ์œ ์ง€๋จ (์™„๋ฒฝ ๋ฐฉ์–ด)
template.tsxํŽ˜์ด์ง€ ์ง„์ž… ์• ๋‹ˆ๋ฉ”์ด์…˜, ๊ฐ•์ œ ์ดˆ๊ธฐํ™”โŒ ์ดˆ๊ธฐํ™” ํ›„ ์ƒˆ๋กญ๊ฒŒ ๋งˆ์šดํŠธ
page.tsxURL ์˜ ์ตœ์ข… ์ฃผ์ธ๊ณต(๋ฉ”์ธ ์ฝ˜ํ…์ธ )-
(folder)URL ์— ์˜ํ–ฅ ์•ˆ ์ฃผ๊ณ  ๋ ˆ์ด์•„์›ƒ ๊ทธ๋ฃนํ•‘ ๋ถ„๋ฆฌ-

โš ๏ธ ์ ˆ๋Œ€ ํ•˜์ง€ ๋ง ๊ฒƒ ( ์•ˆํ‹ฐ ํŒจํ„ด )

์ƒํ™ฉโŒ ๋‚˜์œ ์˜ˆโœ… ์ข‹์€ ์˜ˆ
๋ ˆ์ด์•„์›ƒ ๋ถ„๊ธฐpathname ์ฒดํฌํ•ด์„œ ๊ฑฐ๋Œ€ํ•œ if ๋ฌธ ๋Œ๋ฆฌ๊ธฐRoute Group ์œผ๋กœ ๋ ˆ์ด์•„์›ƒ ๋ฌผ๋ฆฌ์  ๋ถ„๋ฆฌ
์ƒํƒœ ์œ ์ง€๋ฐ์ดํ„ฐ ๋ณด์กด์ด ํ•„์š”ํ•œ๋ฐ template ์“ฐ๊ธฐ๋ฐ˜๋“œ์‹œ Layout ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฆฌ๋ Œ๋”๋ง ์ฐจ๋‹จ
ํŽ˜์ด์ง€ ๊ตฌ์กฐํ•œ ํŒŒ์ผ์— ๋ชจ๋“  ๋ฉ”๋‰ด์™€ ๋‚ด์šฉ์„ ๋‹ค ๋„ฃ๊ธฐ์ƒ์† ๊ตฌ์กฐ์— ๋งž์ถฐ layout ๊ณผ page ๋กœ ์ ์ ˆํžˆ ์ชผ๊ฐœ๊ธฐ

๐Ÿ“ ๋งˆ๋ฌด๋ฆฌ ํ€ด์ฆˆ

Q1. layout.tsx์™€ template.tsx์˜ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์ฐจ์ด๋Š”?

โœ… ์ •๋‹ต: layout์€ ๋ผ์šฐํŠธ ์ด๋™์—๋„ ์ƒํƒœ๋ฅผ ๋ณด์กดํ•˜๊ณ , template์€ ์ด๋™๋งˆ๋‹ค ์ƒˆ ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: ๊ฒ€์ƒ‰ ์ž…๋ ฅ, ํƒญ ์ƒํƒœ์ฒ˜๋Ÿผ ์œ ์ง€๋˜์–ด์•ผ ํ•˜๋Š” UI๋Š” layout์— ์–ด์šธ๋ฆฐ๋‹ค. ๋งค ์ด๋™๋งˆ๋‹ค ์ดˆ๊ธฐํ™”๋˜์–ด์•ผ ํ•˜๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜์ด๋‚˜ ํผ ํ๋ฆ„์€ template์ด ๋” ์ ์ ˆํ•  ์ˆ˜ ์žˆ๋‹ค.


Q2. Route Group์„ ์“ฐ๋ฉด URL์—๋Š” ์–ด๋–ค ๋ณ€ํ™”๊ฐ€ ์ƒ๊ธฐ๋‚˜?

โœ… ์ •๋‹ต: ๊ด„ํ˜ธ ํด๋”๋Š” URL์— ๋‚˜ํƒ€๋‚˜์ง€ ์•Š๋Š”๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: (admin), (marketing) ๊ฐ™์€ ๊ทธ๋ฃน์€ ๋ ˆ์ด์•„์›ƒ๊ณผ ์ฝ”๋“œ ์กฐ์ง์„ ๋‚˜๋ˆ„์ง€๋งŒ ์ฃผ์†Œ๋ฅผ ๋ฐ”๊พธ์ง€ ์•Š๋Š”๋‹ค. URL ์„ค๊ณ„์™€ ํด๋” ์„ค๊ณ„๋ฅผ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ์žฅ์น˜๋‹ค.


Q3. ์˜์ฒ ์ด์˜ ํ…Œ์ŠคํŠธ ํƒ€์ž„: ๊ด€๋ฆฌ์ž ํ™”๋ฉด๊ณผ ๊ณต๊ฐœ ํ™”๋ฉด์˜ ํ—ค๋”๊ฐ€ ์™„์ „ํžˆ ๋‹ค๋ฅด์ง€๋งŒ URL์€ /admin์œผ๋กœ ์œ ์ง€ํ•˜๊ณ  ์‹ถ๋‹ค. ์–ด๋””๋ฅผ ์กฐ์ •ํ• ๊นŒ?

โœ… ์ •๋‹ต: app ํด๋”์—์„œ route group๊ณผ layout ๊ฒฝ๊ณ„๋ฅผ ๋‚˜๋ˆ  ๊ด€๋ฆฌ์ž ์ „์šฉ layout์„ ๋‘”๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: ํŽ˜์ด์ง€๋งˆ๋‹ค ์กฐ๊ฑด๋ฌธ์œผ๋กœ ํ—ค๋”๋ฅผ ๋ฐ”๊พธ๊ธฐ๋ณด๋‹ค ๋ผ์šฐํŠธ ๊ตฌ์กฐ๊ฐ€ ๋ ˆ์ด์•„์›ƒ ์ฑ…์ž„์„ ๊ฐ–๊ฒŒ ํ•˜๋Š” ํŽธ์ด ์œ ์ง€๋ณด์ˆ˜์— ์ข‹๋‹ค. ์˜ˆ์•ฝ ํŒŒ์ผ ์ด๋ฆ„์€ ํ”„๋ ˆ์ž„์›Œํฌ์™€ ๋งบ๋Š” ๊ณ„์•ฝ์ด๋‹ค.

๐Ÿฃ ์˜์ฒ ์ด์˜ ํ‡ด๊ทผ ์ผ๊ธฐ

์˜ค๋Š˜์€ app ํด๋”๊ฐ€ ๋‹จ์ˆœํ•œ ํŒŒ์ผ ๋ชจ์Œ์ด ์•„๋‹ˆ๋ผ ๋ผ์šฐํŒ… ๊ณ„์•ฝ์„œ๋ผ๋Š” ๊ฑธ ๋ฐฐ์› ๋‹ค.

๐Ÿ’ก "์˜ˆ์•ฝ ํŒŒ์ผ์€ ์ด๋ฆ„์ด ๊ณง ํ”„๋ ˆ์ž„์›Œํฌ์™€ ๋งบ๋Š” ์•ฝ์†์ด๋‹ค."

์•ž์œผ๋กœ ํด๋”๋ฅผ ๋งŒ๋“ค ๋•Œ URL, ๋ ˆ์ด์•„์›ƒ ๋ณด์กด, ์ดˆ๊ธฐํ™” ๋ฒ”์œ„๋ฅผ ํ•จ๊ป˜ ๋ฉ”๋ชจํ•˜๊ฒ ๋‹ค.

๐Ÿ”— ๋” ์•Œ์•„๋ณด๊ธฐ