Next.js์ ์ต์ ๋ฒ์ , Next.js 14์ ์ถ์๋ ํ๋ก ํธ์๋ ๊ฐ๋ฐ์ ์ธ๊ณ์ ์๋ก์ด ๋ณํ์ ๋ฐ๋์ ๋ถ๋ฌ์ผ์ผํค๊ณ ์์ต๋๋ค. ์ด ์ ๋ฐ์ดํธ๋ ๊ฐ๋ฐ ์๋์ ์ฌ์ฉ์ ๊ฒฝํ์ ๊ทน์ ์ผ๋ก ๊ฐ์ ํ๋ ๋ช ๊ฐ์ง ์ค์ํ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค. ์ด ๊ธ์์๋ Next.js 14์ ํต์ฌ ๊ธฐ๋ฅ๊ณผ ๊ทธ๊ฒ๋ค์ด ์น ์ฑ ๊ฐ๋ฐ์ ์ด๋ค ์ํฅ์ ๋ฏธ์น ์ง์ ๋ํด ์ด์ผ๊ธฐ ํด๋ณด๊ฒ ์ต๋๋ค.
Next.js 14์ ํ์ : ๋น ๋ฅด๊ณ ํจ์จ์ ์ธ ๊ฐ๋ฐ ๊ฒฝํ
Next.js๋ ์น ์ฑ ๊ฐ๋ฐ์ ์ํ ์ต๊ณ ์ ํ๋ ์์ํฌ ์ค ํ๋๋ก ์๋ฆฌ ์ก์์ผ๋ฉฐ, Next.js 14๋ ์ด๋ฌํ ์์น๋ฅผ ๋์ฑ ๊ณต๊ณ ํ ํฉ๋๋ค. ์ด ๋ฒ์ ์์๋ ํนํ ์ธ ๊ฐ์ง ์ฃผ์ ๊ธฐ๋ฅ์ด ๋์ ๋๋๋ค.
- Turbopack: ์๋์ ํจ์จ์ฑ์ ์๋ก์ด ๊ธฐ์ค Turbopack์ ๋ก์ปฌ ์๋ฒ ์์ ์๊ฐ์ ์ต๋ 53%๊น์ง ๋จ์ถํ๊ณ , ์ฝ๋ ์ ๋ฐ์ดํธ ์๊ฐ์ 94%๋ ์ค์ฌ์ค๋๋ค. ์ด๊ฒ์ ๋๊ท๋ชจ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ํ ์ค์ ๋ฒค์น๋งํฌ ๊ฒฐ๊ณผ๋ก, Next.js์ ์ฑ๋ฅ ํฅ์์ ๋ช ํํ ๋ณด์ฌ์ค๋๋ค.
- Server Actions: ๊ฐ๊ฒฐํ๊ณ ํจ์จ์ ์ธ ๋ฐฑ์๋ ํตํฉ Server Actions๋ ๋ฐฑ์๋ ์๋ํฌ์ธํธ ๊ตฌ์ถ์ ๊ฐ์ํํ๊ณ , ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํต๋๋ค. ์ด๋ฅผ ํตํด ๊ฐ๋ฐ์๋ API ๊ฒฝ๋ก๋ฅผ ์๋์ผ๋ก ์์ฑํ ํ์ ์์ด, React ์ปดํฌ๋ํธ ๋ด์์ ์ง์ ์๋ฒ์์ ์คํ๋๋ ํจ์๋ฅผ ์ ์ํ ์ ์์ต๋๋ค.
- ๋ถ๋ถ์ 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์ ์ฌํํด ๋ณด์ด๊ธด ํ์ง๋ง ์ด๋ ๊ฒ ์ฐ๋ฉด ์ฝ๋๊ฐ ์ง์ ๋ถํด ์ง๊ฒ ๊ฐ์๋ฐ ์ด๊ฒ ๋ง๋ ์ถ์ ๋๋์ด ๋ค๊ธฐ๋ ํ๋ค์. ์๋ฌดํผ ํ๋ก ํธ์๋์ ๋ณํ ์๋๋ ์ ๋ง ๋น ๋ฅธ๊ฒ ๊ฐ๋ค๋ ์๊ฐ์ ๋๋ค.