๐Ÿ”€ Next.js 9์žฅ: Parallel & Intercepting Routes โ€” ๋ชจ๋‹ฌ๊ณผ ๋ผ์šฐํŒ…์˜ ๊ทนํ•œ

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

๐Ÿ“‹ ๊ฐœ์š”

Parallel Routes์™€ Intercepting Routes๋กœ URL ๊ธฐ๋ฐ˜ ๋ชจ๋‹ฌ์„ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ฐฐ์›๋‹ˆ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ


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

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

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ํ๋ฆ„
๋…๋ฆฝ๋œ ์˜์—ญ ๋ณ‘๋ ฌ ๋ Œ๋”๊ธฐ(@) โ†’ ํ๋ฆ„ ๊ฐ€๋กœ์ฑ„๊ธฐ((..)) โ†’ ์ด ๋‘˜์„ ์กฐํ•ฉํ•œ ๊ถ๊ทน์˜ "๊ณต์œ  URL ๋ชจ๋‹ฌ" ํŒจํ„ด ๋งˆ์Šคํ„ฐ

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

  • ์ธ์Šคํƒ€๊ทธ๋žจ, ํ•€ํ„ฐ๋ ˆ์ŠคํŠธ์ฒ˜๋Ÿผ (1) ํ”ผ๋“œ ์œ„์—์„œ๋Š” ๋ชจ๋‹ฌ๋กœ ๋œจ๊ณ , (2) ๋‚จ์—๊ฒŒ ์นดํ†ก์œผ๋กœ ๊ณต์œ ํ•œ URL์„ ๋ˆ„๋ฅด๋ฉด ์ „์ฒดํ™”๋ฉด ์ „์šฉ ํŽ˜์ด์ง€๋กœ ์ ‘์†๋˜๋Š” ์ด์ค‘ ๋ผ์šฐํŒ… ์•„ํ‚คํ…์ฒ˜๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ํ•˜๋‚˜์˜ layout.tsx ์•ˆ์—์„œ A, B, C ๊ตฌ์—ญ์ด ๊ฐ๊ฐ ๋‚จ์˜ ๋ˆˆ์น˜ ์•ˆ ๋ณด๊ณ  ๋…๋ฆฝ์ ์ธ ๋กœ๋”ฉ/์—๋Ÿฌ ํ™”๋ฉด์„ ๋ฟœ์–ด๋‚ด๋Š” "๋Œ€์‹œ๋ณด๋“œ ๋ณ‘๋ ฌ ๋ Œ๋”๋ง" ์„ ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ๋‹ค.

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

  • ์˜์ฒ (์ƒˆ๋กœ ์˜จ ์ฃผ๋‹ˆ์–ด): "๋ฆฌ๋“œ ๋‹˜... ํ”ผ๋“œ์—์„œ ์‚ฌ์ง„์„ ํด๋ฆญํ•˜๋ฉด ๋ชจ๋‹ฌ๋กœ ๋„์›Œ์คฌ๊ฑฐ๋“ ์š”. ๊ทธ๋Ÿฐ๋ฐ ์‚ฌ์šฉ์ž๊ฐ€ ํŒ์—…์„ ๋‹ซ์œผ๋ ค๊ณ  '๋’ค๋กœ ๊ฐ€๊ธฐ'๋ฅผ ๋ˆŒ๋ €๋”๋‹ˆ ์‚ฌ์ดํŠธ ๋ฐ–์œผ๋กœ ํŠ•๊ฒจ ๋‚˜๊ฐ”๋Œ€์š”! URL์ด ์•ˆ ๋ณ€ํ•ด์„œ ๊ทธ๋Ÿฐ ๊ฒƒ ๊ฐ™์€๋ฐ, ๊ทธ๋ ‡๋‹ค๊ณ  ์ƒˆ ํŽ˜์ด์ง€๋กœ ์ด๋™์‹œํ‚ค์ž๋‹ˆ ๋ฐฐ๊ฒฝ ํ”ผ๋“œ๊ฐ€ ๋‹ค ์‚ฌ๋ผ์ ธ์š”. ๐Ÿ˜ญ"
  • ์˜ํ˜ธ(FE ๋ฆฌ๋“œ): "์˜์ฒ  ๋‹˜... ๋ชจ๋‹ฌ์˜ ์ตœ๊ณ  ๋‚œ์ œ์ธ 'URL ๋™๊ธฐํ™” ๋ ˆ์ด์–ด' ๋ฅผ ๋งˆ์ฃผํ•˜์…จ๊ตฐ์š”! Next.js์˜ @folder (๋ณ‘๋ ฌ ๋ผ์šฐํŠธ) ์™€ (..) (๊ฐ€๋กœ์ฑ„๊ธฐ ๋ผ์šฐํŠธ) ๋ฅผ ์„ž์–ด ์“ฐ๋ฉด, URL์€ ์ •ํ™•ํžˆ ๋ฐ”๋€Œ๋ฉด์„œ๋„ ๋ฐฐ๊ฒฝ ํ™”๋ฉด์€ ๊ทธ๋Œ€๋กœ ์œ ์ง€๋˜๋Š” ์ธ์Šคํƒ€๊ทธ๋žจ ๋บจ์น˜๋Š” ํŒ์—…์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค๊ณ ์š”!"

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

๊ณผ๊ฑฐ ๋ฆฌ์•กํŠธ ์‹œ์ ˆ์— ํŒ์—…/๋ชจ๋‹ฌ ๋„์šฐ๊ธฐ๋Š” ์‰ฌ์› ์–ด. const [isOpen, setIsOpen] = useState(false) ํ•˜๋‚˜ ์„ ์–ธํ•˜๊ณ  ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง({isOpen && <Modal/>}) ๋•Œ๋ฆฌ๋ฉด ๋์ด์—ˆ์ง€.
ํ•˜์ง€๋งŒ ์ด ๋ฐฉ์‹์—” ์น˜๋ช…์ ์ธ ๊ฒฐํ•จ์ด ์žˆ์—ˆ๋‹จ๋‹ค.

  1. ๋’ค๋กœ ๊ฐ€๊ธฐ ๋ถˆ๊ฐ€๋Šฅ: ๋ธŒ๋ผ์šฐ์ € ํžˆ์Šคํ† ๋ฆฌ(URL)์— ๊ธฐ๋ก์ด ์•ˆ ๋‚จ์•„์„œ, ๋’ค๋กœ ๊ฐ€๊ธฐ๋ฅผ ๋ˆ„๋ฅด๋ฉด ๋ชจ๋‹ฌ๋งŒ ๊บผ์ง€๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ ์•ž ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•ด๋ฒ„๋ฆผ.
  2. ๊ณต์œ  ๋ถˆ๊ฐ€๋Šฅ: ์ด "์‚ฌ์ง„ ๋ชจ๋‹ฌ์ด ๋–  ์žˆ๋Š” ์ƒํƒœ" ์ž์ฒด๋ฅผ ์นœ๊ตฌํ•œํ…Œ ๋งํฌ๋กœ ๋ณต์‚ฌํ•ด์„œ ์ค„ ์ˆ˜๊ฐ€ ์—†์Œ.
  3. SEO ๋ถ•๊ดด: ํฌ๋กค๋Ÿฌ ์ž…์žฅ์—์„œ๋Š” ์ˆจ๊ฒจ์ง„ div ๋ฉ์–ด๋ฆฌ์ผ ๋ฟ, ๋…๋ฆฝ๋œ ๋ฌธ์„œ(์‚ฌ์ง„ ์ƒ์„ธ ์ •๋ณด)๋กœ ์ธ์‹ํ•˜์ง€ ๋ชปํ•จ.

์ด๊ฑธ ๊ณ ์น˜๋ ค๋ฉด ๋ชจ๋‹ฌ์„ ๋„์šธ ๋•Œ ๊ฐ•์ œ๋กœ URL์„ ๋ฐ”๊ฟ”์•ผ ํ•˜๋Š”๋ฐ(์˜ˆ: /feed -> /photo/123), ๊ณผ๊ฑฐ์—” URL์ด ๋ฐ”๋€Œ๋ฉด ๋ฐฐ๊ฒฝ์— ๊น”๋ ค์žˆ๋˜ ํ”ผ๋“œ ๋ชฉ๋ก์ด ์‹น ๋‚ ์•„๊ฐ€๊ณ  ์ƒˆ๋กœ์šด ๋ฐฑ์ง€ ๋ Œ๋” ์˜์—ญ(๋ชจ๋‹ฌ๋งŒ ๋ฉ๊ทธ๋Ÿฌ๋‹ˆ ์žˆ๋Š” ํŽ˜์ด์ง€) ์ด ๋‚˜ํƒ€๋‚˜๋Š” ํ•œ๊ณ„๊ฐ€ ์žˆ์—ˆ์–ด.

์ด ๋ชจ๋‹ฌ ๋”œ๋ ˆ๋งˆ๋ฅผ "์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ ํ”„๋ ˆ์ž„์›Œํฌ" ์ฐจ์›์—์„œ ๊ฐ€์žฅ ์šฐ์•„ํ•˜๊ฒŒ, ์™„๋ฒฝํ•˜๊ฒŒ ๊นจ๋ฒ„๋ฆฐ Next.js์˜ ์ž๋ž‘์Šค๋Ÿฌ์šด ๋‘ ๊ฐ€์ง€ ๊ณ ๊ธ‰ ํŒŒ์ผ ์‹œ์Šคํ…œ ๋ฃฐ์„ ๋ฐฐ์›Œ๋ณด์ž.


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

๐Ÿง’ 5์‚ด์—๊ฒŒ ์„ค๋ช…ํ•œ๋‹ค๋ฉด? (์˜์ˆ˜๋„ค ๊ตญ๋ฐฅ์ง‘์˜ ๋…๋ฆฝ ํ…Œ์ด๋ธ”๊ณผ ์‹œ์‹ ๊ฐ€๋กœ์ฑ„๊ธฐ)

Parallel Routes (@) : 1์ธ์šฉ ์นธ๋ง‰์ด ํ…Œ์ด๋ธ”
์‹๋‹น ํ•œ๊ฐ€์šด๋ฐ์— ๋…๋ฆฝ๋œ ๋ฐฅ์ƒ 3๊ฐœ(@table1, @table2, @table3) ๊ฐ€ ๋†“์—ฌ ์žˆ๋Š” ๊ฑฐ์•ผ.
1๋ฒˆ ์†๋‹˜์ด ๊น๋‘๊ธฐ๋ฅผ ๋” ๋‹ฌ๋ผ๊ณ  ํ•ด๋„(@analytics) , 2๋ฒˆ ์†๋‹˜์€ ์ž๊ธฐ ๊ตญ๋ฐฅ์„ ์กฐ์šฉํžˆ ๋จน๊ณ  ์žˆ์–ด.
์˜† ํ…Œ์ด๋ธ”์—์„œ ๊ทธ๋ฆ‡์„ ๊นจ๋œจ๋ ค๋„(@error) , ๋‚ด ํ…Œ์ด๋ธ” ์†๋‹˜์€ ์•„๋ฌด ์ผ ์—†๋‹ค๋Š” ๋“ฏ ๋ฐฅ์„ ๋จน์„ ์ˆ˜ ์žˆ๋Š” ์™„๋ฒฝํ•œ ๋…๋ฆฝ ์‹์‚ฌ ๊ณต๊ฐ„ ์ด์•ผ.

Intercepting Routes ((..)): ์ฃผ๋ฐฉ ์•ž ๊ฐ€๋กœ์ฑ„๊ธฐ ์‹œ์‹ํšŒ
์›๋ž˜ "ํŠน๋Œ€ ์ˆ˜์œก" ์ฃผ๋ฌธ(photo/1) ์€ "์•ˆ์ชฝ ๋‹จ์ฒด ๋ฐฉ" ์œผ๋กœ ๋ฐฐ๋‹ฌ๋˜์–ด์•ผ ํ•ด.
๊ทธ๋Ÿฐ๋ฐ ์†๋‹˜์ด ๋ณต๋„๋ฅผ ์ง€๋‚˜๊ฐ€๋‹ค๊ฐ€ ์ฃผ๋ฐฉ ์ž…๊ตฌ์—์„œ "์ž ๊น! ๊ทธ ์ˆ˜์œก ๋‚˜ ํ•œ ์ ๋งŒ ๋ง›๋ณด๊ฒŒ ํ•ด์ค˜!" ํ•˜๊ณ  ๋ชฉ์ ์ง€๋กœ ๊ฐ€๋˜ ์ˆ˜์œก์„ ์ค‘๊ฐ„์— ๋‚š์•„์ฑ„์„œ(Intercept) ํ˜„์žฌ ์žˆ๋Š” ๋ณต๋„(ํ”ผ๋“œ ํ™”๋ฉด) ์—์„œ ํ•œ ์ž… ๋จน์–ด๋ณด๋Š” ๋งˆ์ˆ  ์ด์•ผ.
๋‹ค ๋จน๊ณ  ์ ‘์‹œ๋ฅผ ๋Œ๋ ค์ฃผ๋ฉด(๋’ค๋กœ ๊ฐ€๊ธฐ) , ๋‹ค์‹œ ์›๋ž˜ ์ž๋ฆฌ๋กœ ๋Œ์•„๊ฐ€๊ณ , ์•„์˜ˆ ์ฒ˜์Œ๋ถ€ํ„ฐ "๋‹จ์ฒด ๋ฐฉ" ์œผ๋กœ ๋“ค์–ด์˜จ ์†๋‹˜(์ง์ ‘ URL ์ ‘์†) ์€ ์ œ๋Œ€๋กœ ๋œ ์ˆ˜์œก ํ•œ ์ƒ์„ ๋ฐ›๊ฒŒ ๋ผ.

๐Ÿงฉ Parallel Routes (@folder): ํ•œ ์ง€๋ถ• ๋‘ ๊ฐ€์กฑ ๐ŸŸข

๋จผ์ € ๋ณ‘๋ ฌ ๋ผ์šฐํŠธ์•ผ. ํด๋” ์ด๋ฆ„ ์•ž์— ๊ณจ๋ฑ…์ด(@)๋ฅผ ๋ถ™์ด๋ฉด, Next.js๋Š” ์ด๊ฑธ ๋‹จ์ˆœํ•œ URL ๊ฒฝ๋กœ ์กฐ๊ฐ์œผ๋กœ ํ•ด์„ํ•˜์ง€ ์•Š๊ณ  "๋ถ€๋ชจ Layout์— ๊ฝ‚์•„ ๋„ฃ์„ ๋…๋ฆฝ๋œ ์Šฌ๋กฏ(Slot)" ์œผ๋กœ ์ทจ๊ธ‰ํ•ด.

์ƒํ™ฉ: ๋ณต์žกํ•œ ๊ด€๋ฆฌ์ž ๋Œ€์‹œ๋ณด๋“œ

๋Œ€์‹œ๋ณด๋“œ(Root) ์•ˆ์— 1) ์‚ฌ์šฉ์ž ํ†ต๊ณ„ํ‘œ์™€ 2) ์ตœ์‹  ์•Œ๋ฆผ์ฐฝ ๋‘ ๊ฐœ์˜ ๊ฑฐ๋Œ€ํ•œ ๊ตฌ์—ญ์ด ์žˆ์–ด.

app/
 โ”œโ”€ layout.tsx      (๋ผˆ๋Œ€ ๋ ˆ์ด์•„์›ƒ)
 โ”œโ”€ page.tsx        (๊ฐ€์šด๋ฐ ๊ธฐ๋ณธ ๊ตฌ์—ญ)
 โ”œโ”€ @analytics/     (๊ณจ๋ฑ…์ด! ์Šฌ๋กฏ A)
 โ”‚   โ””โ”€ page.tsx
 โ””โ”€ @notifications/ (๊ณจ๋ฑ…์ด! ์Šฌ๋กฏ B)
     โ””โ”€ page.tsx

์•„๋น  ๋ ˆ์ด์•„์›ƒ(layout.tsx)์˜ ๋งˆ๋ฒ• ์Šฌ๋กฏ ์กฐ๋ฆฝ

@ํŒŒ์ผ๋ช… ํด๋”๋ฅผ ๋งŒ๋“ค๋ฉด, Next.js ํ”„๋ ˆ์ž„์›Œํฌ ์ฝ”์–ด๊ฐ€ ์ž๋™์œผ๋กœ layout.tsx ์ปดํฌ๋„ŒํŠธ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ(Props)์— ๊ทธ ์ด๋ฆ„๊ณผ ๋˜‘๊ฐ™์€ children์„ ๋ชฐ๋ž˜ ์ฃผ์ž…ํ•ด์ค˜! ์—„์ฒญ๋‚œ ํ‘๋งˆ๋ฒ•์ด์ง€.

// app/layout.tsx
export default function DashboardLayout({
  children, // ์›๋ž˜ ๋“ค์–ด์˜ค๋˜ ๊ธฐ๋ณธ page.tsx ๊ตฌ์—ญ
  analytics, // @analytics ํด๋” ๋‚ด๋ถ€์˜ ๋ Œ๋”๋ง ๊ฒฐ๊ณผ๋ฌผ์ด ํ†ต์งธ๋กœ ๋“ค์–ด์˜ด! (๋งˆ์ˆ )
  notifications, // @notifications ๋‚ด๋ถ€ ๊ฒฐ๊ณผ๋ฌผ
}: {
  children: React.ReactNode
  analytics: React.ReactNode
  notifications: React.ReactNode
}) {
  return (
    <body>
      <main className="grid">
        <div className="center"> {children} </div>
 
        {/* ํ•œ ์ง€๋ถ•(layout) ์•„๋ž˜์— 3๊ฐ€์กฑ์ด ๋…๋ฆฝ๋œ ์Šคํฌ๋ฆฐ์œผ๋กœ ๊ณต์กดํ•œ๋‹ค! */}
        <aside className="left"> {analytics} </aside>
        <aside className="right"> {notifications} </aside>
      </main>
    </body>
  )
}

์ด ์ง“์„ ์™œ ํ•˜๋‚˜์š”? (์ตœ๋Œ€ ์žฅ์ )
์ € 3๊ฐœ์˜ ๊ตฌ์—ญ(children, analytics, notifications)์€ ์™„์ „ํžˆ ๋‚จ๋‚จ์ฒ˜๋Ÿผ ํ–‰๋™ํ•ด.

  • ๋งŒ์•ฝ ํ†ต๊ณ„(analytics) ๋ฐ์ดํ„ฐ ์กฐํšŒ๊ฐ€ 5์ดˆ๋‚˜ ๊ฑธ๋ ค์„œ ๋กœ๋”ฉ ์ค‘(์ž์ฒด loading.tsx ๋ณด์œ )์ด๊ฑฐ๋‚˜ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜๋„(์ž์ฒด error.tsx ๋ณด์œ ), ์˜ค๋ฅธ์ชฝ์˜ ์•Œ๋ฆผ์ฐฝ(notifications)์€ ๋ˆˆ์น˜ ์•ˆ ๋ณด๊ณ  0.1์ดˆ ๋งŒ์— ๋ Œ๋”๋ง์„ ๋๋‚ด๊ณ  ํ˜ผ์ž ๊ทธ๋ ค์ ธ. ์™„๋ฒฝํ•œ ๊ฒฉ๋ฆฌ ๋ณด๋ฃจ๊ฐ€ ๋˜๋Š” ๊ฑฐ์•ผ!

๐ŸŒฑ Intercepting Routes ((..)): ๋‚ด๊ฐ€ ์ฑ„๊ฐ€๋Š” ๋งˆ์ˆ  ๐ŸŸก

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

(.) : ๋‚˜์™€ ๊ฐ™์€ ํด๋” ๋ ˆ๋ฒจ ๊ฒฝ๋กœ ๊ฐ€๋กœ์ฑ„๊ธฐ
(..) : ๋‚˜๋ณด๋‹ค ํ•œ ๋‹จ๊ณ„ ์œ—๋ฐฉ(๋ถ€๋ชจ) ๊ฒฝ๋กœ ๊ฐ€๋กœ์ฑ„๊ธฐ
(..)(..) : ๋‘ ๋‹จ๊ณ„ ์œ—๋ฐฉ ๊ฐ€๋กœ์ฑ„๊ธฐ
(...) : ์•„์˜ˆ ์ตœ์ƒ๋‹จ app/ ๊ธฐ์ค€ ๋ฃจํŠธ(Root) ๊ฐ€๋กœ์ฑ„๊ธฐ

๐ŸŽฏ ์˜์ฒ ์ด์˜ ์ธ์Šคํƒ€๊ทธ๋žจ ๋งŒ๋“ค๊ธฐ ๋ฏธ์…˜

์šฐ๋ฆฌ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ /photo/123 ์ด๋ผ๋Š” ๊ณ ์œ  ์ฃผ์†Œ๋ฅผ ๊ฐ€์ง„ ์ •์งํ•˜๊ณ  ์ปค๋‹ค๋ž€ "์‚ฌ์ง„ ์ „์šฉ ์ƒˆ ํŽ˜์ด์ง€"๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์–ด. (๋‚จํ•œํ…Œ ์นดํ†ก์œผ๋กœ ๊ณต์œ ํ–ˆ์„ ๋•Œ ๋–จ์–ด์ง€๋Š” ๋„์ฐฉ์ง€)

app/
 โ”œโ”€ feed/
 โ”‚   โ””โ”€ page.tsx      (๊ฒŒ์‹œ๋ฌผ ๋ชฉ๋ก)
 โ””โ”€ photo/
     โ””โ”€ [id]/
         โ””โ”€ page.tsx  (๊ทธ๋ƒฅ ์ ‘์†ํ•˜๋ฉด ๋œจ๋Š” ์ปค๋‹ค๋ž€ ์ „์ฒด์‚ฌ์ง„ ํŽ˜์ด์ง€)

์ด ์ƒํƒœ์—์„œ feed/page.tsx ์•ˆ์˜ ์‚ฌ์ง„ ๋งํฌ <Link href="/photo/123"> ๋ฅผ ํด๋ฆญํ•˜๋ฉด? ์•ˆํƒ€๊น๊ฒŒ๋„ ํ™”๋ฉด ๋ฐฐ๊ฒฝ์ด ํ•˜์–—๊ฒŒ ์‹น ๋‹ค ์ง€์›Œ์ง€๋ฉฐ ๋ป˜๊ฑด ์ „์ฒดํ™”๋ฉด ์‚ฌ์ง„ ํŽ˜์ด์ง€ /photo/123 ์œผ๋กœ ๋„˜์–ด๊ฐ€๋ฒ„๋ ค.

์ด๊ฑธ ๋ง‰์œผ๋ ค๋ฉด? ํ”ผ๋“œ(Feed) ๊ตฌ์—ญ ๋‚ด๋ถ€์—์„œ, ์›๋ž˜ /photo/ ๋ฐฉ์œผ๋กœ ํ˜๋Ÿฌ ๋“ค์–ด๊ฐ€์•ผ ํ•  ๋ผ์šฐํŒ… ํ๋ฆ„์„ ๊ฐ€๋กœ์ฑ„๋ฉด ๋ผ!


๐Ÿ›ก๏ธ ์ธ์Šคํƒ€๊ทธ๋žจ ๋ชจ๋‹ฌ ์„ค๊ณ„: ๋‘ ๊ธฐ์ˆ ์˜ ์™„๋ฒฝํ•œ ์•™์ƒ๋ธ” ๐Ÿ”ด

์ด์ œ ์œ„์—์„œ ๋ฐฐ์šด ๋‘ ๊ฐœ์˜ ๋ฌด๊ธฐ(๋ณ‘๋ ฌ @, ๊ฐ€๋กœ์ฑ„๊ธฐ (..))๋ฅผ ํ•ฉ์ฒดํ•ด ์ „์„ค์˜ ๋ฌด๊ธฐ(Soft Navigation Modal)๋ฅผ ๋ฒผ๋ ค๋‚ด์ž.

์™„๋ฒฝํ•œ ๋ชจ๋‹ฌ ํด๋” ๊ตฌ์กฐ ์„ค๊ณ„๋„

app/
 โ”œโ”€ layout.tsx
 โ”œโ”€ feed/
 โ”‚   โ”œโ”€ layout.tsx    โ˜… ๋ชจ๋‹ฌ ์Šฌ๋กฏ์„ ๊ตฌ๋น„ํ•ด๋†“์Œ
 โ”‚   โ”œโ”€ page.tsx      โ˜… <Link href="/photo/1">๊ฐ€ ์žˆ๋Š” ๋ชฉ๋ก
 โ”‚   โ””โ”€ @modal/       โ˜… [Parallel] ๋ชจ๋‹ฌ์„ ๋„์šธ ์ „์šฉ ๋…๋ฆฝ ๋ณ‘๋ ฌ ์Šฌ๋กฏ
 โ”‚       โ”œโ”€ default.tsx     (ํ‰์†Œ ๋กœ๋”ฉ ์ „์šฉ ํˆฌ๋ช…์ธ๊ฐ„ ๋ทฐ)
 โ”‚       โ””โ”€ (..)photo/      โ˜… [Intercept] ํ๋ฆ„ ๊ฐ€๋กœ์ฑ„๊ธฐ ํด๋”! (ํ”ผ๋“œ์˜ ํ˜•์ œ์ธ photo๋ฅผ ๋‚š์•„์ฑ”)
 โ”‚           โ””โ”€ [id]/
 โ”‚               โ””โ”€ page.tsx โ˜… ์—ฌ๊ธฐ๊ฐ€ ๋ฐ”๋กœ ํ”ผ๋“œ ๋ฐฐ๊ฒฝ์„ ๊น”๊ณ  ๋œจ๋Š” "๋ชจ๋‹ฌ ๋ฐ•์Šค" ์ปดํฌ๋„ŒํŠธ!
 โ”‚
 โ””โ”€ photo/            โ˜… [Original] ์—ฌ๊ธฐ๋Š” ๋ณ€ํ•จ ์—†์ด ์กด์žฌํ•˜๋Š” "๊ทผ๋ณธ ์ „์ฒดํ™”๋ฉด" ๋ชฉ์ ์ง€.
     โ””โ”€ [id]/
         โ””โ”€ page.tsx  โ˜… ์นดํ†ก/์ธ์Šคํƒ€๋กœ ๋ณต์‚ฌ๋œ ๋งํฌ ์ง๊ฒฐ ์ ‘์† ์‹œ ๋œจ๋Š” ๊ฑฐ๋Œ€ํ•œ ํ™”๋ฉด!

[๋งˆ๋ฒ•์ด ํŽผ์ณ์ง€๋Š” ์ˆœ๊ฐ„]

  1. ์ƒํ™ฉ 1: ์นดํ†ก์œผ๋กœ ๋งํฌ ๊ณต์œ ๋ฐ›์•„ ๋ธŒ๋ผ์šฐ์ €์— ์Œฉ์œผ๋กœ ๋ณต๋ถ™ ์ ‘์†ํ–ˆ์„ ๋•Œ (Hard Navigation)
    ๋ˆ„๊ฐ€ /photo/1 ์ณค๋‹ค. Next.js ์—”์ง„์€ "๊ฐ€๋กœ์ฑŒ ์ถœ๋ฐœ์ง€๊ฐ€ ์—†๋„ค?" ํ•˜๊ณ  ์ •์งํ•˜๊ฒŒ ๊ทผ๋ณธ ๊ฒฝ๋กœ์ธ ๋งจ ๋ฐ‘๋ฐ”๋‹ฅ app/photo/[id]/page.tsx ์ฐํŽ˜์ด์ง€๋ฅผ ์—ด์–ด์„œ ๋ณด์—ฌ์ค€๋‹ค.

  2. ์ƒํ™ฉ 2: ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์ดํŠธ ๋‚ด์—์„œ ํ”ผ๋“œ(feed) ์ธ๋„ค์ผ์„ ๋งˆ์šฐ์Šค๋กœ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ํด๋ฆญํ•  ๋•Œ (Soft Navigation)
    ํ”ผ๋“œ(feed/page.tsx) ์•ˆ์—์„œ <Link href="/photo/1"> ํด๋ฆญ!
    ๊ฒฝ๋กœ๊ฐ€ ์ด๋™ํ•˜๋ ค๋Š”๋ฐ, ์•—! feed ํด๋” ๋‚ด๋ถ€์— ์ž ๋ณต ๊ฒฝ์ฐฐ (..)photo ๊ฐ€ "์žก์•˜๋‹ค ์š”์ฒญ!" ํ•˜๊ณ  ๋‚š์•„์ฑˆ๋‹ค.
    ๋‚š์•„์ฑˆ ์ด ๊ฐ€์งœ ๋ชจ๋‹ฌ ๊ป๋ฐ๊ธฐ๋Š” ์•„๊นŒ ๊ณจ๋ฑ…์ด๋กœ ๋šซ์–ด๋‘” @modal ๋ณ‘๋ ฌ ์Šฌ๋กฏ ์œ„์น˜์—(์•„๋น  ๋ ˆ์ด์•„์›ƒ ์˜†์ž๋ฆฌ์— ๋ผ์–ด์„œ) ๋ฟ… ํ•˜๊ณ  ํŠ€์–ด๋‚˜์˜จ๋‹ค!

๊ฒฐ๊ณผ? URL ์ฐฝ์—” /photo/1 ์ด๋ผ๊ณ  ์˜ˆ์˜๊ฒŒ ์ฐํ˜€์žˆ์ง€๋งŒ, ํ™”๋ฉด์€ ์›๋ž˜ ์žˆ๋˜ feed ๋ชฉ๋ก ์œ„์— ๋ชจ๋‹ฌ(@modal)์ด ๋–  ์žˆ๋Š” ๊ถ๊ทน์˜ ์•„ํ‚คํ…์ฒ˜๊ฐ€ ์™„์„ฑ๋œ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ํฐ ๋’ค๋กœ ๊ฐ€๊ธฐ๋ฅผ ๋ˆ„๋ฅด๋ฉด ๋ชจ๋‹ฌ๋งŒ ๊บผ์ง€๊ณ  ๋‹ค์‹œ ๊ทผ์›์ง€(ํ”ผ๋“œ URL)๋กœ ์™ ํšŒ๊ท€ํ•œ๋‹ค.

๐ŸŒŸ layout.tsx ํ•ฉ์ฒด ์ฝ”๋“œ

// app/feed/layout.tsx
export default function FeedLayout({
  children,
  modal // @modal ํด๋” ์ž์ฒด๊ฐ€ Props๋กœ ๊ฝ‚ํž˜!
}: { children: React.ReactNode, modal: React.ReactNode }) {
  return (
    <>
      {children} {/* ํ”ผ๋“œ ๋ชฉ๋ก (๋ฐฐ๊ฒฝ์— ์˜์›ํžˆ ๊น”๋ ค์žˆ์Œ) */}
      {modal}    {/* ๊ฐ€๋กœ์ฑ„๊ธฐ ๋ ˆ์ด๋”. ํ‰์†Œ์—” ๋น„์–ด์žˆ๋‹ค๊ฐ€ ๋ˆ„๋ฅด๋ฉด ์ด ์ž๋ฆฌ์— ํŒ์—…์ด ๋™‡! */}
    </>
  )
}

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

์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ๋œจ๋ฉด Ctrl+F๋กœ ๊ฒ€์ƒ‰ํ•ด๋ด.

โŒ ๋ชจ๋‹ฌ ํด๋” ์„ธํŒ…์„ ๋‹ค ํ–ˆ๋Š”๋ฐ๋„ ๋นŒ๋“œ๋‚˜ ํŽ˜์ด์ง€ ์ด๋™ ์‹œ ํ„ฐ์ง‘๋‹ˆ๋‹ค (404 Error)

์›์ธ: Parallel Route (@ํด๋”)๋ฅผ ์‹ฌ์–ด๋†“๊ณ , ํ‰์ƒ์‹œ ๋ Œ๋”๋ง์— ํ•„์š”ํ•œ default.tsx ๋ฅผ ๋ฏธ๋ฐฐ์น˜ํ•จ.
ํ•ด๊ฒฐ์ฑ…: Next.js ๋ณ‘๋ ฌ ๋ผ์šฐํŠธ๋Š” "ํ™”๋ฉด์— ๋„์›Œ์ค„ URL ๋Œ€์ƒ์ด ๋ช…ํ™•ํ•˜์ง€ ์•Š์„ ๋•Œ" ํ‘œ์‹œํ•  ๋นˆ ๊นกํ†ต ์Šคํฌ๋ฆฐ ํŒŒ์ผ์„ ๊ฐ•์š”ํ•œ๋‹ค. ์ปดํฌ๋„ŒํŠธ๋ช… default.tsx ๋ฅผ ๋งŒ๋“ค๊ณ  ์ฟจํ•˜๊ฒŒ return null; ํ•˜๋‚˜๋งŒ ์ ์–ด์„œ ๋ฐ˜ํ™˜์‹œํ‚ค๋ฉด ๋งˆ๋ฒ•์ฒ˜๋Ÿผ ์ถฉ๋Œ์ด ์‚ฌ๋ผ์ง„๋‹ค.

โŒ (..) ๊ฒฝ๋กœ๋ฅผ ํ•œ ๋ฒˆ ์ผ๋Š”๋ฐ ๊ฐ€๋กœ์ฑ„๊ธฐ๊ฐ€ ์ž‘๋™์„ ์•ˆ ํ•˜๊ณ  ๋ณธ ํŽ˜์ด์ง€๋กœ ์ž๊พธ ํŠ•๊น๋‹ˆ๋‹ค.

์›์ธ: (..) ์งฌ์งฌ ํด๋”์˜ ๊นŠ์ด ๋งค์นญ ์˜ค๋ฅ˜.
์˜ˆ๋ฅผ ๋“ค์–ด ๊ฐ€๋กœ์ฑ„๋ ค๋Š” ๋Œ€์ƒ์ด app/movies/[id] ์ธ๋ฐ, ์ž ๋ณต ๊ฒฝ์ฐฐ ์œ„์น˜๊ฐ€ app/feed/@modal/(..)movies ๋ผ ์น˜์ž.
๊ฒฝ์ฐฐ์€ "๋‚ด ๋ถ€๋ชจ(feed)๋ž‘ ๊ฐ™์€ ๊ธ‰์˜ ๋ฐฉ(movies)" ๋งŒ ๋‚š์•„์ฑŒ ์ˆ˜ ์žˆ๋‹ค! ๋งŒ์•ฝ ๊นŠ์ด๊ฐ€ ํ•˜๋‚˜๋ผ๋„ ์–ด๊ธ‹๋‚œ๋‹ค๋ฉด ๊ฐ€๋กœ์ฑ„๊ธฐ๋Š” ๋ฐœ๋™ ์กฐ๊ฑด์„ ์ถฉ์กฑํ•˜์ง€ ๋ชปํ•ด ์ •์งํ•œ ๋งํฌ๋กœ ํŠ•๊ฒจ๋ฒ„๋ฆฐ๋‹ค. (๊นŠ์ด๊ฐ€ ํ—ท๊ฐˆ๋ฆฌ๋ฉด ํด๋” ํŠธ๋ฆฌ๋ฅผ ์ข…์ด์— ๊ทธ๋ ค๋ณด๋Š” ๊ฑธ ์›์น™์œผ๋กœ ํ•˜๋ผ!)


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

๊ธฐ๋Šฅ ๊ตฌ๋ถ„์ฃผ์š” ์—ญํ•  ์„ค๋ช…์‹ค๋ฌด ์‚ฌ์šฉ ๋นˆ๋„ & ์˜ˆ์‹œ
@folder (Parallel)ํ•œ ํƒญ ๋ฌธ์„œ ํ™”๋ฉด์„ ์—ฌ๋Ÿฌ ๊ฐœ์˜ "๋…๋ฆฝ ์ƒ์กด ๊ตฌ์—ญ"์œผ๋กœ ์นผ๋กœ ์ชผ๊ฐฌ๋Œ€์‹œ๋ณด๋“œ 3๋ถ„ํ•  ๋ทฐ, ๋กœ๊ทธ์ธ ๋‹ค์ด์–ผ๋กœ๊ทธ ๊ตฌ์—ญ ํ™•๋ณด (๋งค์šฐ ๋†’์Œ)
(..) (Intercept)์ด๋™ํ•˜๋ ค๋Š” ๋ชฉ์ ์ง€์˜ URL๊ณผ ๋‚ด์šฉ๋ฌผ์„ ๋‚š์•„์ฑ„ "ํ˜„์žฌ ์žˆ๋Š” ๋ทฐํฌํŠธ์— ๋ฎ์–ด์”€"์ธ์Šคํƒ€ ์‚ฌ์ง„ ํ”ผ๋“œ ํ™•๋Œ€, ํ•€ํ„ฐ๋ ˆ์ŠคํŠธ ์นด๋“œ ํด๋ฆญ ์‹œ (๋†’์Œ)
์œ„ ๋‘ ๊ฐœ ์กฐํ•ฉ ๋งˆ์ˆ URL ์ผ์น˜, ๋ธŒ๋ผ์šฐ์ € ๋’ค๋กœ ๊ฐ€๊ธฐ ๋™์ž‘, ์ง์ ‘ ์ ‘์†(๊ณต์œ ) ์ฒ˜๋ฆฌ๊นŒ์ง€ ์™„๋ฒฝ ์ปค๋ฒ„ํ•˜๋Š” ๋ผ์šฐํŒ… ๋ ˆ๋ฒจ์˜ ๋ชจ๋‹ฌ ํ˜๋ช…ํ”„๋กœ๋•ํŠธ ์ตœ๊ณ ๋‚œ๋„ UX ํŒ์—… (Next.js๋ฅผ ๋„์ž…ํ•˜๋Š” ์ด์œ  ๊ทธ ์ž์ฒด!)

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
ํŒ์—… ๋ชจ๋‹ฌ์„ ๋งŒ๋“ค ๋•Œ useState(false) Boolean ์Šค์œ„์น˜์—๋งŒ ์˜์กดํ•˜๋˜ ๋‹จ์ˆœํ•œ ์‹œ์ ˆ์€ ๋๋‚ฌ๋‹ค! ๊ฒฝ๋กœ(URL)๋ฅผ ๊ฐ€๋กœ์ฑ„๊ณ ((..)), ๊ทธ ๊ฐ€๋กœ์ฑˆ ํ™”๋ฌผ์„ ๋‚ด ์˜†์— ๋ณ‘๋ ฌ ๋ถ€ํ•˜(@)๋กœ ๋ฐฐ์น˜ํ•˜๋ฉด ์ง„์งœ ์•ฑ ๊ฐ™์€ ๋„ค์ดํ‹ฐ๋ธŒ ํŒŒ์›Œ ๋ผ์šฐํŒ… ๋ชจ๋‹ฌ์ด ํƒ„์ƒํ•œ๋‹ค!


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

Q1. Parallel Route ์Šฌ๋กฏ์— default.tsx๊ฐ€ ํ•„์š”ํ•œ ์ด์œ ๋Š”?

โœ… ์ •๋‹ต: ํ˜„์žฌ URL์— ์Šฌ๋กฏ์ด ๋งค์นญ๋˜์ง€ ์•Š์„ ๋•Œ ๋ณด์—ฌ์ค„ ๊ธฐ๋ณธ ์ƒํƒœ๊ฐ€ ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: ์ƒˆ๋กœ๊ณ ์นจ์ด๋‚˜ ์ง์ ‘ ์ง„์ž…์—์„œ๋Š” ๋ชจ๋‹ฌ ์Šฌ๋กฏ์— ๋Œ€์‘ํ•˜๋Š” URL ์ƒํƒœ๊ฐ€ ์—†์„ ์ˆ˜ ์žˆ๋‹ค. default.tsx๊ฐ€ ์—†์œผ๋ฉด ์˜๋„์น˜ ์•Š์€ 404๋‚˜ ๊นจ์ง„ ๋ ˆ์ด์•„์›ƒ์ด ์ƒ๊ธด๋‹ค.


Q2. Intercepting Route์˜ (.)์™€ (..)๋Š” ๋ฌด์—‡์„ ๊ธฐ์ค€์œผ๋กœ ํ•ด์„๋ ๊นŒ?

โœ… ์ •๋‹ต: ํŒŒ์ผ ์‹œ์Šคํ…œ ๊นŠ์ด๊ฐ€ ์•„๋‹ˆ๋ผ URL ์„ธ๊ทธ๋จผํŠธ ๋ ˆ๋ฒจ์„ ๊ธฐ์ค€์œผ๋กœ ํ•ด์„๋œ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: ํด๋”๊ฐ€ ๊นŠ์–ด ๋ณด์—ฌ๋„ route group์ด๋‚˜ slot์€ URL์— ๋‚˜ํƒ€๋‚˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ž˜์„œ ์‹ค์ œ ์ฃผ์†Œ ๊ตฌ์กฐ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ธํ„ฐ์…‰ํŠธ ์œ„์น˜๋ฅผ ํŒ๋‹จํ•ด์•ผ ํ•œ๋‹ค.


Q3. ์˜์ฒ ์ด์˜ ํ…Œ์ŠคํŠธ ํƒ€์ž„: ํ”ผ๋“œ์—์„œ ์‚ฌ์ง„์„ ๋ˆ„๋ฅด๋ฉด ๋ชจ๋‹ฌ, ์ƒˆ ํƒญ์œผ๋กœ ์—ด๋ฉด ์ „์ฒด ํŽ˜์ด์ง€๊ฐ€ ๋– ์•ผ ํ•œ๋‹ค. ํ•„์š”ํ•œ ๊ตฌ์กฐ๋Š”?

โœ… ์ •๋‹ต: ์›๋ณธ ์ƒ์„ธ page์™€ ํ”ผ๋“œ ์ปจํ…์ŠคํŠธ์—์„œ ๊ฐ€๋กœ์ฑ„๋Š” intercepting modal route๋ฅผ ํ•จ๊ป˜ ๋‘”๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: ๋ชจ๋‹ฌ์€ ์ปจํ…์ŠคํŠธ๊ฐ€ ์žˆ์„ ๋•Œ๋งŒ ๋ชจ๋‹ฌ์ด์–ด์•ผ ํ•œ๋‹ค. ์ง์ ‘ ์ ‘๊ทผ๊นŒ์ง€ ๊ณ ๋ คํ•ด์•ผ ๊ณต์œ  ๋งํฌ์™€ ์ƒˆ๋กœ๊ณ ์นจ์ด ๊นจ์ง€์ง€ ์•Š๋Š”๋‹ค.

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

์˜ค๋Š˜์€ ๋ชจ๋‹ฌ๋„ URL๊ณผ ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ๊ฐ€์ง„ ํ™”๋ฉด์ด ๋  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฑธ ๋ฐฐ์› ๋‹ค.

๐Ÿ’ก "๊ณต์œ  ๊ฐ€๋Šฅํ•œ UI๋Š” ์ƒํƒœ ๋ณ€์ˆ˜๋ณด๋‹ค ๋ผ์šฐํŒ…์œผ๋กœ ์„ค๊ณ„ํ•ด์•ผ ์ƒˆ๋กœ๊ณ ์นจ๊ณผ ๋’ค๋กœ๊ฐ€๊ธฐ๊ฐ€ ์ž์—ฐ์Šค๋Ÿฝ๋‹ค."

๋‹ค์Œ ๋ชจ๋‹ฌ ์š”๊ตฌ์‚ฌํ•ญ์—๋Š” ๋จผ์ € ์ƒˆ ํƒญ, ๋’ค๋กœ๊ฐ€๊ธฐ, ์ƒˆ๋กœ๊ณ ์นจ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์ ์–ด ๋ณด๊ฒ ๋‹ค.

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