🀜 Next.js 14 Conf

@winuss Β· December 21, 2023 Β· 4 min read

Next.js의 μ΅œμ‹  버전, Next.js 14의 μΆœμ‹œλŠ” ν”„λ‘ νŠΈμ—”λ“œ 개발의 세계에 μƒˆλ‘œμš΄ λ³€ν™”μ˜ λ°”λžŒμ„ λΆˆλŸ¬μΌμœΌν‚€κ³  μžˆμŠ΅λ‹ˆλ‹€. 이 μ—…λ°μ΄νŠΈλŠ” 개발 속도와 μ‚¬μš©μž κ²½ν—˜μ„ 극적으둜 κ°œμ„ ν•˜λŠ” λͺ‡ 가지 μ€‘μš”ν•œ κΈ°λŠ₯을 μ œκ³΅ν•©λ‹ˆλ‹€. 이 κΈ€μ—μ„œλŠ” Next.js 14의 핡심 κΈ°λŠ₯κ³Ό 그것듀이 μ›Ή μ•± κ°œλ°œμ— μ–΄λ–€ 영ν–₯을 미칠지에 λŒ€ν•΄ 이야기 ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

Next.js 14의 ν˜μ‹ : λΉ λ₯΄κ³  효율적인 개발 κ²½ν—˜

Next.jsλŠ” μ›Ή μ•± κ°œλ°œμ„ μœ„ν•œ 졜고의 ν”„λ ˆμž„μ›Œν¬ 쀑 ν•˜λ‚˜λ‘œ 자리 μž‘μ•˜μœΌλ©°, Next.js 14λŠ” μ΄λŸ¬ν•œ μœ„μΉ˜λ₯Ό λ”μš± 곡고히 ν•©λ‹ˆλ‹€. 이 λ²„μ „μ—μ„œλŠ” 특히 μ„Έ 가지 μ£Όμš” κΈ°λŠ₯이 λˆˆμ— λ•λ‹ˆλ‹€.

  1. Turbopack: 속도와 νš¨μœ¨μ„±μ˜ μƒˆλ‘œμš΄ κΈ°μ€€ Turbopack은 둜컬 μ„œλ²„ μ‹œμž‘ μ‹œκ°„μ„ μ΅œλŒ€ 53%κΉŒμ§€ λ‹¨μΆ•ν•˜κ³ , μ½”λ“œ μ—…λ°μ΄νŠΈ μ‹œκ°„μ„ 94%λ‚˜ μ€„μ—¬μ€λ‹ˆλ‹€. 이것은 λŒ€κ·œλͺ¨ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ— λŒ€ν•œ μ‹€μ œ 벀치마크 결과둜, Next.js의 μ„±λŠ₯ ν–₯상을 λͺ…ν™•νžˆ λ³΄μ—¬μ€λ‹ˆλ‹€.
  2. Server Actions: κ°„κ²°ν•˜κ³  효율적인 λ°±μ—”λ“œ 톡합 Server ActionsλŠ” λ°±μ—”λ“œ μ—”λ“œν¬μΈνŠΈ ꡬ좕을 κ°„μ†Œν™”ν•˜κ³ , μ‚¬μš©μž κ²½ν—˜μ„ ν–₯μƒμ‹œν‚΅λ‹ˆλ‹€. 이λ₯Ό 톡해 κ°œλ°œμžλŠ” API 경둜λ₯Ό μˆ˜λ™μœΌλ‘œ 생성할 ν•„μš” 없이, React μ»΄ν¬λ„ŒνŠΈ λ‚΄μ—μ„œ 직접 μ„œλ²„μ—μ„œ μ‹€ν–‰λ˜λŠ” ν•¨μˆ˜λ₯Ό μ •μ˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  3. 뢀뢄적 Pre-rendering: λΉ λ₯Έ 초기 응닡과 동적 μ½˜ν…μΈ  Next.js의 μƒˆλ‘œμš΄ 뢀뢄적 pre-rendering κΈ°λŠ₯은 초기 정적 응닡과 동적 μ½˜ν…μΈ  μŠ€νŠΈλ¦¬λ°μ„ μ‘°ν™”λ‘­κ²Œ κ²°ν•©ν•©λ‹ˆλ‹€. μ΄λŠ” μ‚¬μš©μžμ—κ²Œ 더 λΉ λ₯Έ νŽ˜μ΄μ§€ λ‘œλ”© κ²½ν—˜μ„ μ œκ³΅ν•˜λ©΄μ„œ λ™μ‹œμ— 동적인 μ½˜ν…μΈ λ₯Ό μœ μ—°ν•˜κ²Œ μ œκ³΅ν•©λ‹ˆλ‹€.
npx create-next-app@latest

개발의 νŽΈμ˜μ„±: 더 μ‰¬μ›Œμ§„ Next.js 개발 κ²½ν—˜

Next.js 14λŠ” κ°œλ°œμžλ“€μ΄ 보닀 λΉ λ₯΄κ³  효율적으둜 μž‘μ—…ν•  수 μžˆλ„λ‘ μ„€κ³„λ˜μ—ˆμŠ΅λ‹ˆλ‹€. μ΄λŸ¬ν•œ νŽΈμ˜μ„±μ€ λ‹€μŒκ³Ό 같은 ν˜•νƒœλ‘œ λ‚˜νƒ€λ‚©λ‹ˆλ‹€:

  • ν†΅ν•©λœ 캐싱 및 μž¬κ²€μ¦: λ°μ΄ν„°μ˜ μœ νš¨μ„±μ„ λΉ λ₯΄κ²Œ ν™•μΈν•˜κ³  μΊμ‹œλœ μ½˜ν…μΈ λ₯Ό μ΅œμ‹  μƒνƒœλ‘œ μœ μ§€ν•©λ‹ˆλ‹€.
  • κ°„λ‹¨ν•œ ν•¨μˆ˜ 호좜: μ„œλ²„ μž‘μ—…μ„ μœ„ν•œ λ³΅μž‘ν•œ μ„€μ • 없이 κ°„λ‹¨ν•œ ν•¨μˆ˜ 호좜둜 λ°±μ—”λ“œ κΈ°λŠ₯을 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • 보닀 효율적인 μ½”λ“œ μž‘μ„±: κ°œλ°œμžλ“€μ€ μ½”λ“œ μž‘μ„± μ‹œκ°„μ„ 쀄이고, 더 직관적인 λ°©μ‹μœΌλ‘œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ ꡬ성할 수 μžˆμŠ΅λ‹ˆλ‹€.

Server Action: ν”„λ‘ νŠΈμ—”λ“œμ™€ λ°±μ—”λ“œμ˜ κ°„νŽΈν•œ 톡합

Server Action은 λ°±μ—”λ“œ κΈ°λŠ₯을 ν”„λ‘ νŠΈμ—”λ“œ μ»΄ν¬λ„ŒνŠΈμ— 직접 ν†΅ν•©ν•¨μœΌλ‘œμ¨ κ°œλ°œμžλ“€μ΄ λ”μš± μ§κ΄€μ μœΌλ‘œ μž‘μ—…ν•  수 μžˆλ„λ‘ ν•΄μ€λ‹ˆλ‹€. 이 κΈ°λŠ₯은 Next.jsκ°€ React Canary μ±„λ„μ˜ μ•ˆμ •μ„±μ„ 기반으둜 ν•˜μ—¬ μƒˆλ‘œμš΄ κΈ°λŠ₯을 μ±„νƒν•˜λŠ” 방식을 λ°˜μ˜ν•©λ‹ˆλ‹€.

export default function Page() {
  async function create(formData: FormData) {
    "use server"
    const id = await createItem(formData)
  }

  return (
    <form action={create}>
      <input type="text" name="name" />
      <button type="submit">Submit</button>
    </form>
  )
}

양식 제좜 μ‹œ μ„œλ²„μ—μ„œ μ‹€ν–‰λ˜λŠ” processFormData ν•¨μˆ˜λ₯Ό 직접 ν˜ΈμΆœν•©λ‹ˆλ‹€. μ΄λŠ” 기쑴의 λ³΅μž‘ν•œ API 경둜 μ„€μ • 없이 μ„œλ²„ λ‘œμ§μ„ κ΅¬ν˜„ν•  수 μžˆλ„λ‘ ν•΄μ£ΌλŠ” ν˜μ‹ μ μΈ λ°©μ‹μž…λ‹ˆλ‹€.

Server Action의 μž₯점

  • κ°„κ²°μ„±: React μ»΄ν¬λ„ŒνŠΈ λ‚΄μ—μ„œ μ„œλ²„ μž‘μ—…μ„ 직접 μ •μ˜ν•¨μœΌλ‘œμ¨ μ½”λ“œλ₯Ό κ°„κ²°ν•˜κ²Œ μœ μ§€ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • νƒ€μž… μ•ˆμ „μ„±: TypeScriptλ₯Ό μ‚¬μš©ν•˜μ—¬ ν΄λΌμ΄μ–ΈνŠΈμ™€ μ„œλ²„ κ°„ μ™„λ²½ν•œ νƒ€μž… μ•ˆμ „μ„±μ„ 보μž₯ν•©λ‹ˆλ‹€.
  • 효율적인 데이터 처리: 단일 λ„€νŠΈμ›Œν¬ μ™•λ³΅μœΌλ‘œ 데이터 λ³€κ²½, νŽ˜μ΄μ§€ λ¦¬λ Œλ”λ§ λ˜λŠ” λ¦¬λ‹€μ΄λ ‰μ…˜μ„ μ²˜λ¦¬ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • μž¬μ‚¬μš©μ„±: λ‹€μ–‘ν•œ μž‘μ—…μ„ κ΅¬μ„±ν•˜κ³  μž¬μ‚¬μš©ν•  수 μžˆμ–΄ 개발 과정을 효율적으둜 λ§Œλ“­λ‹ˆλ‹€.

Server Action은 λ°±μ—”λ“œ 개발 κ²½ν—˜μ΄ μžˆλŠ” κ°œλ°œμžλ“€μ—κ²Œ μΉœμˆ™ν•˜λ©΄μ„œλ„, μ›Ήμ˜ κΈ°λ³Έ 사항인 Formκ³Ό FormData Web APIλ₯Ό 기반으둜 μƒˆλ‘œμš΄ μ ‘κ·Ό 방식을 μ œκ³΅ν•©λ‹ˆλ‹€. μ΄λŸ¬ν•œ 방식은 κ°œλ°œμžλ“€μ΄ Next.jsλ₯Ό μ‚¬μš©ν•˜μ—¬ 보닀 μ‹ μ†ν•˜κ³  효과적으둜 ν”„λ‘œμ νŠΈλ₯Ό 진행할 수 있게 λ„μ™€μ€λ‹ˆλ‹€.

뢀뢄적 Pre-rendering

Next.jsλ₯Ό μœ„ν•΄ μž‘μ—… 쀑인 λΆ€λΆ„ pre-rendering(λΉ λ₯Έ 초기 정적 응닡을 κ°–μΆ˜ 동적 μ½˜ν…μΈ μ— λŒ€ν•œ 컴파일러 μ΅œμ ν™”)의 미리 보기λ₯Ό μ œκ³΅ν•˜κ³ , pre-rendering은 μ„œλ²„μΈ‘ λ Œλ”λ§(SSR), 정적 μ‚¬μ΄νŠΈ 생성(SSG) 및 증뢄적 정적 μž¬κ²€μ¦(ISR)에 λŒ€ν•œ 10λ…„ κ°„μ˜ 연ꡬ κ°œλ°œμ„ 기반으둜 κ΅¬μΆ•λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

React Suspenseλ₯Ό 기반으둜 ꡬ좕

λΆ€λΆ„ 사전 λ Œλ”λ§μ€ Suspense 경계에 따라 μ •μ˜λ©λ‹ˆλ‹€.

export default function Page() {
  return (
    <main>
      <header>
        <h1>My Store</h1>
        <Suspense fallback={<CartSkeleton />}>
          <ShoppingCart />
        </Suspense>
      </header>
      <Banner />
      <Suspense fallback={<ProductListSkeleton />}>
        <Recommendations />
      </Suspense>
      <NewProducts />
    </main>
  )
}

pre-rendering이 ν™œμ„±ν™”λ˜λ©΄ 이 νŽ˜μ΄μ§€λŠ” <Suspense /> boundaryλ₯Ό 기반으둜 정적 셸을 μƒμ„±ν•©λ‹ˆλ‹€. React Suspense의 fallback은 사전 λ Œλ”λ§λ©λ‹ˆλ‹€.

그런 λ‹€μŒ μ…Έμ˜ Suspense fallback은 cookieλ₯Ό 읽어 카트λ₯Ό κ²°μ •ν•˜κ±°λ‚˜ μ‚¬μš©μžλ₯Ό 기반으둜 λ°°λ„ˆλ₯Ό ν‘œμ‹œν•˜λŠ” λ“±μ˜ 동적 μ»΄ν¬λ„ŒνŠΈλ‘œ λŒ€μ²΄λ©λ‹ˆλ‹€.

μš”μ²­μ΄ 이루어지면 정적 HTML 셸이 μ¦‰μ‹œ μ œκ³΅λ©λ‹ˆλ‹€.

<main>
  <header>
    <h1>My Store</h1>
    <div class="cart-skeleton">
      <!-- Hole -->
    </div>
  </header>
  <div class="banner" />
  <div class="product-list-skeleton">
    <!-- Hole -->
  </div>
  <section class="new-products" />
</main>

<ShoppingCart />λŠ” μΏ ν‚€μ—μ„œ 읽어 μ‚¬μš©μž session을 ν™•μΈν•˜λ―€λ‘œ 이 μ»΄ν¬λ„ŒνŠΈλŠ” 정적 μ…Έκ³Ό λ™μΌν•œ HTTP μš”μ²­μ˜ μΌλΆ€λ‘œ μŠ€νŠΈλ¦¬λ°λ©λ‹ˆλ‹€. μΆ”κ°€ λ„€νŠΈμ›Œν¬ 왕볡이 ν•„μš”ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

import { cookies } from 'next/headers'

export default function ShoppingCart() {
  const cookieStore = cookies()
  const session = cookieStore.get('session')
  return ...
}

κ°€μž₯ 세뢀적인 정적 셸을 가지렀면 Suspense boundaryλ₯Ό μΆ”κ°€ν•΄μ•Ό ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ ν˜„μž¬ loading.jsλ₯Ό 이미 μ‚¬μš©ν•˜κ³  μžˆλ‹€λ©΄ μ΄λŠ” μ•”μ‹œμ μΈ Suspense boundrayμ΄λ―€λ‘œ 정적 셸을 μƒμ„±ν•˜λŠ” 데 변경이 ν•„μš”ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

메타데이터 κ°œμ„ 

뷰포트, 색 κ΅¬μ„±ν‘œ 및 ν…Œλ§ˆμ— λŒ€ν•œ μ€‘μš”ν•œ νŽ˜μ΄μ§€ μ½˜ν…μΈ λ₯Ό μ„œλ²„μ—μ„œ μŠ€νŠΈλ¦¬λ°ν•˜κΈ° 전에 λ¨Όμ € λΈŒλΌμš°μ €μ— 전솑해야 ν•˜λŠ” 메타데이터가 μžˆμŠ΅λ‹ˆλ‹€.

μ΄λŸ¬ν•œ meta νƒœκ·Έκ°€ 초기 νŽ˜μ΄μ§€ μ½˜ν…μΈ μ™€ ν•¨κ»˜ μ „μ†‘λ˜λ„λ‘ ν•˜λ©΄ μ›ν™œν•œ μ‚¬μš©μž κ²½ν—˜μ— 도움이 되며, ν…Œλ§ˆ 색상을 λ³€κ²½ν•˜κ±°λ‚˜ 뷰포트 λ³€κ²½μœΌλ‘œ 인해 λ ˆμ΄μ•„μ›ƒμ΄ μ΄λ™ν•˜μ—¬ νŽ˜μ΄μ§€κ°€ κΉœλ°•μ΄λŠ” 것을 방지할 수 μžˆμŠ΅λ‹ˆλ‹€.

Next.js 14μ—μ„œλŠ” blocking 및 non-blocking 메타데이터λ₯Ό λΆ„λ¦¬ν–ˆμŠ΅λ‹ˆλ‹€. 메타데이터 μ˜΅μ…˜μ˜ μž‘μ€ ν•˜μœ„ 집합(small subset)만 μ°¨λ‹¨λ˜λ©°, μš°λ¦¬λŠ” non-blocking 메타데이터가 λΆ€λΆ„μ μœΌλ‘œ pre-rendering된 νŽ˜μ΄μ§€κ°€ 정적 셸을 μ œκ³΅ν•˜λŠ” 것을 λ°©ν•΄ν•˜μ§€ μ•Šλ„λ‘ ν•˜κ³  μ‹ΆμŠ΅λ‹ˆλ‹€.

λ‹€μŒ 메타데이터 μ˜΅μ…˜μ€ 이제 더 이상 μ‚¬μš©λ˜μ§€ μ•ŠμœΌλ©° ν–₯ν›„ μ£Όμš” λ²„μ „μ˜ λ©”νƒ€λ°μ΄ν„°μ—μ„œ 제거(deprecated)될 μ˜ˆμ • 이라고 ν•©λ‹ˆλ‹€.

  • viewport: 뷰포트의 초기 ν™•λŒ€/μΆ•μ†Œ 및 기타 속성을 μ„€μ •ν•©λ‹ˆλ‹€.
  • colorScheme: 뷰포트의 지원 λͺ¨λ“œ(밝음/어두움)λ₯Ό μ„€μ •ν•©λ‹ˆλ‹€.
  • themeColor: 뷰포트 μ£Όλ³€μ˜ 크둬이 λ Œλ”λ§ν•΄μ•Ό ν•˜λŠ” 색상을 μ„€μ •ν•©λ‹ˆλ‹€. Next.js 14λΆ€ν„° μ΄λŸ¬ν•œ μ˜΅μ…˜μ„ λŒ€μ²΄ν•˜λŠ” μƒˆλ‘œμš΄ μ˜΅μ…˜ viewport 및 generateViewportκ°€ μžˆμŠ΅λ‹ˆλ‹€. λ‹€λ₯Έ λͺ¨λ“  메타데이터 μ˜΅μ…˜μ€ λ™μΌν•˜κ²Œ μœ μ§€λ©λ‹ˆλ‹€.

마치며

turobopackκ³Ό pre-rendering 은 κΈ°λŒ€κ°€ λ˜λŠ” 뢀뢄이긴 ν•˜μ§€λ§Œ, ServerActions은 μ‹¬ν”Œν•΄ 보이긴 ν•˜μ§€λ§Œ μ΄λ ‡κ²Œ μ“°λ©΄ μ½”λ“œκ°€ 지저뢄해 μ§ˆκ²ƒ 같은데 이게 λ§žλ‚˜ 싢은 λŠλ‚Œμ΄ 듀기도 ν•˜λ„€μš”. μ•„λ¬΄νŠΌ ν”„λ‘ νŠΈμ—”λ“œμ˜ λ³€ν™” μ†λ„λŠ” 정말 λΉ λ₯Έκ²ƒ κ°™λ‹€λŠ” μƒκ°μž…λ‹ˆλ‹€.

@winuss
Hello :) Developer notes!