๐ Next.js ์ฌํ 2์ฅ: `use cache`์ ๋ถ๋ถ ์บ์ฑ (Next.js 15+ ์ต์ ํธ๋ ๋)
๐ ๊ฐ์
Next.js 15+์ use cache ์ง์์ด์ ๋ถ๋ถ ์บ์ฑ์ผ๋ก ์ธ๋ฐํ ์บ์ฑ ์ ๋ต์ ๊ตฌ์ฑํฉ๋๋ค.
๐ ๋ชฉ์ฐจ
- ๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
- ๐ค ์ ์์์ผ ํ๋๊ฐ
- ๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
- ๐งฉ
use cache๋๋ ํฐ๋ธ: ํจ์ ์์ค์ ์๊ตฌ ๋๊ฒฐ ๐ข - ๐ฑ ์ปดํฌ๋ํธ ๋จ์ ์บ์ฑ (Cache Components) ๐ก
- ๐ก๏ธ PPR (Partial Pre-Rendering): ์ ์ ๊ป๋ฐ๊ธฐ + ๋์ ์์ด ๐ด
- ๐ฅ ์๋ฌ ํด๊ฒฐ ์นดํ๋ก๊ทธ
- ๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
- ๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
- ๐ ๋ ์์๋ณด๊ธฐ
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: 15๋ถ (์ ์ฒด) / ํต์ฌ ํํธ๋ง: 8๋ถ
๐บ๏ธ ์ด ๋ฌธ์์ ๋ฐฐ๊ฒฝ ์ธ๊ณ๊ด: '์์๋ค ์ปค๋ฎค๋ํฐ'
- ์์ฒ (์ ์
): "Next.js 14์์ 15๋ก ๋ฒ์ ์ ์ฌ๋ ธ๋๋, ์๋ ์ฒ๋ผ ๋ฌด์ํ๊ฒ ํ์ ์บ์ฑ์ ์ ํด์ค์ ์ฌ์ดํธ๋ ์ ์ ํด์ก๋๋ฐ ๋๋น ํต์ ๋น์ฉ์ด ๋๋ฌด ๋์์! ๋ฌด๊ฑฐ์ด ํต๊ณ ์ปดํฌ๋ํธ๋ ๋ค์ 14 ์์ ์ฒ๋ผ ์๋ฒ์ ์ผ๋ ค๋๊ณ ์ถ์๋ฐ,
fetch์ต์ ์ ๋์ง๋์งcache: 'force-cache'๋ถ์ด๋ ๊ฑฐ ๋ง๊ณ ์ข ์ฐ์ํ ๋ฐฉ๋ฒ ์์๊น์?" - ์ํธ(๋ฆฌ๋): "์์ฒ ๋... Vercel ๋๋ค์ด 15๋ฒ์ ๋ถํฐ ๋์ ธ์ค ๊ฐ๋ ฅํ ๋ง๋ฒ,
'use cache'๋๋ ํฐ๋ธ๋ฅผ ์จ๋ณด์ธ์! ํ์ผ ๋งจ ์, ํจ์ ๋ฐ๋ก ์, ์ฌ์ง์ด ํน์ ์ปดํฌ๋ํธ ๋ธ๋ก ์์ ์ ์ธํ๋ ์๊ฐ ๊ทธ ์กฐ๊ฐ ๋ฉ์ด๋ฆฌ๋ง ํ์ ์ผ๋ก ์ง์ด์ ์์ํ ๋นํ ์(Data Cache)์ ํ์์ผ๋ก ๋ง๋ค์ด๋ฒ๋ฆฐ๋ค๊ณ ์!"
๐บ๏ธ ์ด ๋ฌธ์์ ํ๋ฆ
Next 15์ ์บ์ ์ ์ธํ ์๋ โ use cache์ ์ค์ฝํ ์ ์ฉ๋ฒ โ ๋๋ง์ PPR (์ ๋ฐ๋ง ํ์ ๋ง๋ค๊ธฐ) ๊ธฐ์
๐ฏ ์ด ๋ฌธ์๋ฅผ ๋ค ์ฝ์ผ๋ฉด ํ ์ ์๋ ๊ฒ
- ๋ฌด๊ฑฐ์ด ์จ๋ํํฐ SDK๋ ์ง์ ์ง ์ฌ๋ก์ฐ ์ฟผ๋ฆฌ ํจ์ ์์ฒด์
'use cache'์ง์์๋ฅผ ๋ฌ์ ์ปดํฌ๋ํธ ๋ ๋ฒจ์ ์บ์ฑ ์ํคํ ์ฒ๋ฅผ ์ธ๋ฐํ๊ฒ ์ง์กฐํ ์ ์๋ค. - ์ ์ ์ธ ๊ป๋ฐ๊ธฐ ์์ ์ค์๊ฐ ๋ฐ์ดํฐ๋ฅผ ํ์
<Suspense>๊ตฌ์ญ์ ์์ด, PPR ์ํคํ ์ฒ์ ํ๋ช ์ ์ธ TTFB(0.1์ด ์ด๊ธฐ ๋ก๋ฉ)๋ฅผ ๊ตฌํํ ์ ์๋ค.
๐ค ์ ์์์ผ ํ๋๊ฐ
์ฌํ 1์ฅ์์ ์ฐ๋ฆฌ๋ ๋์ฐํ ์บ์ ์ง์ฅ์ ๋ดค์ด. Next.js 13, 14 ๋ฒ์ ์ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ชจ๋ fetch๋ฅผ ํ์ ์บ์ฑํด๋ฒ๋ฆฌ๋ ๊ฑฐ๋ํ ํญ๊ตฐ์ด์๊ธฐ ๋๋ฌธ์ด์ง.
์์ฑ์ด ๋๋ฌด ๋น๋ฐ์น์, Next.js 15๋ฒ์ ๋ถํฐ๋ ํจ๋ฌ๋ค์์ "๊ธฐ๋ณธ์ ๋งค๋ฒ ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๋์ (Dynamic) ํต์ ์ด๋ค! ๋๊ฐ ์ง์ ์ผ๋ก ์ํ ๋๋ง ์บ์๋ฅผ ์ ์ธํด๋ผ(opt-in)!" ๋ก ์์ 180๋ ๋ค์ง์ด๋ฒ๋ ธ์ด.
์ด ์๋ก์ด ์ ์ธ์ ์บ์ฑ ์๋์์ ํ๋ก ํธ ์๋์ด๋ค์๊ฒ ๊ฐ์ฅ ํซํ ๋ฌด๊ธฐ๊ฐ ๋ฐ๋ก ์ฌํ 2์ฅ์ ์ฃผ์ ์ธ 'use cache' ์์ฝ์ด์ PPR(๋ถ๋ถ ๋ ๋๋ง) ์ด์ผ.
์ด๊ฑธ ๋ชจ๋ฅด๋ฉด ๋๋ Next 15 ์ธ์์์ ๋ชจ๋ ํ์ด์ง ์ปดํฌ๋ํธ๋ฅผ ๋น์ผ CPU ๋ ๋๋ง์ผ๋ก ๋๋ฆฌ๋ค๊ฐ ํด๋ผ์ฐ๋ ์๊ธ ํญํ์ ๋ง๊ณ ํ์ฌ์์ ์ง์ ์ธ๊ฒ ๋ ์ง๋ ๋ชฐ๋ผ. ํ๋ฉด ์ ์ฒด ๋จ์๊ฐ ์๋, "๋ด๊ฐ ์ํ๋ ์กฐ๊ฐ"๋ง ์ผ๋ ค๋ฒ๋ฆฌ๋ ์ต์ ํ์ ๊ธฐ๋ฒ์ ์ง๊ธ๋ถํฐ ๋ง์คํฐํ์.
๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
๐ง 5์ด์๊ฒ ์ค๋ช ํ๋ค๋ฉด? (์ํธ์ ๋น๊ฒฐ ๋ง๋ฒ์ฑ )
โ ๊ณผ๊ฑฐ 14๋ฒ์ (๋๋ณด๋ผ ๊ด์ญ ๋ง๋ฒ)
๋ง์ ์ ์ฒด(ํ์ด์ง ํต์งธ๋ก)์ ๋๋ณด๋ผ๋ฅผ ๋ด๋ ค์ ์จ ๋๋ค๋ฅผ ์ผ๋ ค๋ฒ๋ ธ์ด. ๋ค ๊ฝ๊ฝ ์ผ์ด๋ถ์ด์ ์ฅ์ฌ๋ ๋น ๋ฅธ๋ฐ ๋์ฌ๋์ด ์ ์์ง์ฌ์ ๋ต๋ตํ์ด.โ ํ์ฌ 15๋ฒ์ (ํ์ ๋น๊ฒฐ ๋ง๋ฒ
'use cache')
์ด์ ์ํธ ๋ฆฌ๋๊ฐ ์์ ์งํก์ด ํ๋๋ฅผ ๋ค๊ณ ๋ค๋ .
"์!" ํ๊ณ ์งํก์ด('use cache')๋ก ์น๋ ํน์ ์์(ํจ์, ์ปดํฌ๋ํธ)๋ง ๊ฝ๊ฝ ์ผ์ด๋ถ์ด์ (์บ์ ์๋ฒ์ ์ ์ฅ), ๋ค์๋ฒ ๋ถ๋ฅผ ๋ ์ฐ์ฐ 0์ด ๋ง์ ์์ ํ์ด ํ์ด๋์! ๋ง์ ์ฌ๋๋ค(๋๋จธ์ง ๋์ ์์)์ ์ถฅ์ง ์๊ณ ์ค์๊ฐ์ผ๋ก ๋ง ๋ฐ์ด๋ค๋ ์ ์์ง.
๐งฉ use cache ๋๋ ํฐ๋ธ: ํจ์ ์์ค์ ์๊ตฌ ๋๊ฒฐ ๐ข
'use client' ๋ 'use server' ์ฒ๋ผ ํ์ผ์ด๋ ํจ์ ์ค์ฝํ ์ต์๋จ์ ๋ง๋ฒ์ ๋ถ์ ์ ๋ถ์ด๋ ๋ฐฉ์์ด์ผ.
์ฃผ์: ์์ง Next.js ๊ณต์ ์คํ์ค(Experimental) ์ต์
์ ์ผ์ผ ์์ ํ ๋์ํ๋ ์ต์ฒจ๋จ ์คํ์ด๋ค.
์ํฉ: ๋ฌด๊ฑฐ์ด ํต๊ณ ๋ถ์ ํจ์ (fetch๊ฐ ์๋!)
์์ฒ ์ด๊ฐ ์ง๋์ ๋์ฐํ๊ฒ ๋๋ฆฐ 5์ด์ง๋ฆฌ ์๊ฐ ์ง๊ณ ์ฟผ๋ฆฌ๊ฐ ์์ด. fetch๋ก ์ฐ๋ฅด๋ ์ธ๋ถ API๊ฐ ์๋๋ผ, ์๊ธฐ๊ฐ ์ง์ Node.js ํ๊ฒฝ์์ DB ํด๋ผ์ด์ธํธ ๋ชจ๋(Prisma ๋ฑ)๊ณผ ๊ฑฐ๋ํ for ๋ฐ๋ณต๋ฌธ์ผ๋ก ์ง ํจ์์ง.
// app/lib/stats.ts
// ๐ฃ ์ด๋์ fetch๊ฐ ์๋๋ผ์ ๊ธฐ๋ณธ Next Data Cache ์์ด์ ๋ฐ์ ์ ์์ด ๋งค๋ฒ 5์ด๊ฐ ๊ฑธ๋ฆผ!
export async function getHeavyMonthlyStats(year: number, month: number) {
const rawData = await db.query(...);
const result = heavyCPUCalculate(rawData); // ์ฌ๊ธฐ์ 5์ด๊ฐ ๋๋๋...
return result;
}โ
๋น๊ฒฐ ๋ถ์ ๋ถ์ด๊ธฐ ('use cache')
๋๋๊ฒ๋ ์ฝ๋ ๋จ ํ ์ค ์ด๋ฉด เฆเฆ 5์ด์ง๋ฆฌ ์ง์ฅ์ 0.01์ด(ํ์ ๊บผ๋ด๊ธฐ)๋ก ๋จ์ถํ ์ ์์ด!
// app/lib/stats.ts
export async function getHeavyMonthlyStats(year: number, month: number) {
'use cache'; // ๐ง ๋น๊ฒฐ ๋ง๋ฒ ๋ฐ๋! ์ด์ ๋ถํฐ ์ด ํจ์์ ๋ฆฌํด๊ฐ์ ํ๋ผ๋ฏธํฐ๋ฅผ ํค๊ฐ ์ผ์ Data Cache ๋ ๋ฒจ๋ก ์์ํ ์ผ๋ ค์ง!!
const rawData = await db.query(...);
const result = heavyCPUCalculate(rawData);
return result;
}[๋ง๋ฒ์ ๋ถ์์ฉ (๋ถ์๊ธฐ)]
์ด๊ฑฐ ์ฌํ 1์ฅ์ React.cache() (1์ด ๋จ๋ฐ์ฑ ์ง์ฐ๊ฐ) ๋ ์ฐฉ๊ฐํ๋ฉด ์ ๋ผ!
'use cache'๋ ์๊ตฌ ๋ณด์กด๋๋ 3๋จ๊ณ Data Cache ์ฅ๋ฒฝ์ ํ์์ ํ๋ฌป๋ ํ์์ผ!
์ฆ, ๋๊ตฐ๊ฐ๊ฐ ์ด๋๋ฏผ ํ์ด์ง์์ ๋ฐฑ๋ ์ฒ๋ ๋งค์ถ์ ๊ฐฑ์ ํด๋ ์ ์ ๋ ์์ํ 5์ด ์ ์ (์ผ๋ ค์ง) ํต๊ณ๋ง ๋ณด๊ฒ ๋ผ.
ํ์์ ๋ถ์๋ ค๋ฉด ์ด๋ป๊ฒ? ์ฐ๋ฆฌ๊ฐ ๋ฐฐ์ธ ๊ฐ๋ ฅํ ์ ๊ฒฉ์ด, revalidateTag ๋ฅผ ํจ์์ ๋ช
์ฐฐ๋ก ๋ฌ์์ฃผ๊ณ ์ด๋ฒ๋ ค์ผ ํด. (์ด๊ฑด ์ฌํ 3์ฅ์์ ๋ ๊น์ด ๋ค๋ฃฐ ์์ )
๐ฑ ์ปดํฌ๋ํธ ๋จ์ ์บ์ฑ (Cache Components) ๐ก
ํจ์ ๋ง๊ณ , HTML ํ๊ทธ๋ฅผ ๋ฟ์ด๋ด๋ '๋ฆฌ์กํธ ์ปดํฌ๋ํธ(export default function...)' ์์ฒด๋ ํต์งธ๋ก ์ผ๋ ค๋ฒ๋ฆด ์ ์์ด!
์ํฉ: DB์์ ๊ฐ์ ธ์จ 1๋ง ๊ฐ ๋ชฉ๋ก ๊ทธ๋ฆฌ๊ธฐ
// app/components/MegaList.tsx
// ๐ง ์ด ์ปดํฌ๋ํธ ๋ฉ์ด๋ฆฌ๊ฐ ๋ฟ์ด๋ด๋ "HTML ๊ตฌ์กฐ" ๊ฒฐ๊ณผ๋ฌผ ์์ฒด๋ฅผ ํต์งธ๋ก ์ผ๋ ค๋ฒ๋ฆฐ๋ค!
export default async function MegaList({ categoryId }: { categoryId: string }) {
'use cache';
// 1๋ง ๊ฐ ๋ ์ฝ๋๋ฅผ ๊ฐ์ ธ์์ ์๋ง ๊ฐ์ <li> ํ๊ทธ๋ก ์กฐ๋ฆฝํ๋ ๊ฑฐ๋ํ CPU ์ฐ์ฐ
const data = await db.getItems(categoryId);
return (
<ul>
{data.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
)
}์ด์ page.tsx ๋ฑ ์๋ฌด ๊ณณ์์๋ ์ <MegaList /> ๋ฅผ ์๋ฐฑ ๋ฒ ๋ ๋๋งํ๋๋ก ํธ์ถํด๋ด๋, ์๋ฒ๋ HTML ํ๊ทธ ๋ฌธ์์ด ์กฐ๋ฆฝ์ ์์ ์๋ตํ๊ณ ๋์คํฌ ์ด๋๊ฐ ์บ์ ์ฐฝ๊ณ ์์ ๊บผ๋ธ ์์ฑํ ํ
์คํธ ๋ฉ์ด๋ฆฌ๋ฅผ 0.01์ด ๋ง์ ๋ธ๋ผ์ฐ์ ์ ๋ฐฐ๋ฌํด์ค.
ํ๋ก ํธ ์๋ฒ ์๊ธ์ ํ๊ธฐ์ ์ผ๋ก ๋ฎ์ถ๋ 2026 ํธ๋ ๋์ ํต์ฌ ๊ธฐ๋ฒ์ด์ง.
๐ก๏ธ PPR (Partial Pre-Rendering): ์ ์ ๊ป๋ฐ๊ธฐ + ๋์ ์์ด ๐ด
์, ์ด์ ๊ถ๊ทน์ ํ์คํธ. "Next.js ๋ ๋๋ง ์ต์ ์ ์ ๊ธฐ์ "๋ก ๋์ด๊ฐ์.
๋ชจ์๋๋ ์๊ตฌ์ฌํญ
๊ธฐํ์๊ฐ ์์ด.
"๊ฐ๋ฐ์๋! ์ฐ๋ฆฌ ์ผํ๋ชฐ ๋ฉ์ธ์ ์๋จ GNB(๋ฉ๋ด)๋ ์ ์ฒด ํ์ ์๋ฌด๋ ์ ๊ฑด๋๋ฆฌ๋๊น ๋น ๋ฑ๋น ๋ฑ ๊ด์(Static)์ผ๋ก ๋จ๊ฒ ํด์ฃผ์๊ณ ์.
๋์ ํ๊ฐ์ด๋ฐ ๋ฐํ ์๋ [์ฌ์ฉ์ ์ฅ๋ฐ๊ตฌ๋ ์์ญ]์ ๋ฐฉ๋ฌธ๊ฐ ์ด๋ฆ์ด๋ ๊ฐ์ธ ์ฟ ํค๋ง๋ค ์ ๋ถ ๋ค๋ฅด๊ฒ(Dynamic) ๋ ์ผ ํด์!"
์ฌํ 1์ฅ์์ ๋ฐฐ์ ์ง? "๋จ ํ ์ค์ ์ค์๊ฐ ์ฟ ํค๋ผ๋ ์ฝ์ผ๋ฉด ๊ทธ ํ์ด์ง์ ํ์(Static)์ ์์ํ ๋ฐ์ด ๋๊ณ ์ ์ฒด๊ฐ SSR(Dynamic)๋ก ๊ฐ๋ฑ๋๋ค" ๊ณ !
โ PPR (๋ถ๋ถ ์ฌ์ ๋ ๋๋ง) ์ด๋ผ๋ ์ด๋จ์
Vercel ์ฒ์ฌ๋ค์ด ์ด๊ฑธ ๊ทน๋ณตํด๋์ด!
์ฐ๋ฆฌ๊ฐ ๊ฐ์ด๋ 7์ฅ์์ ๋ฐฐ์ด <Suspense> ๊ตฌ๋ฉ ๋ซ๊ธฐ ๊ธฐ์ ๊ณผ ๊ฒฐํฉ ๋ ๊ดด๋ฌผ ์ฌ์์ด์ผ.
// app/page.tsx
// โ ๏ธ ์ด ํ์ด์ง ์์ ์ค์๊ฐ ์ ๋ณด(์ฟ ํค ๋ฑ)๊ฐ ์์ง๋ง ๋ ์ ๋ ์๋ ๊ฐ๋ฑ์ ๋นํ์ง ์์ ํ
๋ค!
import { Suspense } from 'react'
import { cookies } from 'next/headers'
import StaticHeader from './StaticHeader'
// 1. ์ฌ๊ธฐ์ ๋์ (๋งค ์ ์๋ง๋ค ๋ฌ๋ผ์ง๋ ๊ฐ์ธํ) ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ๋ ์ปดํฌ๋ํธ ๋ฉ์ด๋ฆฌ๋ฅผ ๋ถ๋ฆฌํ๋ค!
async function PersonalCart() {
const token = (await cookies()).get('token'); // ๐ฃ ์๋๋ผ๋ฉด ์ ์ฒด ํ์ด์ง ํ์์ ๊นจ๋ถ์๋ ํญํ!!
const cartInfo = await fetchCart(token);
return <div>๋ด ์ฅ๋ฐ๊ตฌ๋: {cartInfo.count}๊ฐ</div>
}
// 2. ์ ์ฒด ์กฐํฉํ๋ ๋ฉ์ธ ์ปดํฌ๋ํธ ๋ ์ด์์
export default function HomePage() {
return (
<main>
{/* ๐ ์ฌ๊ธฐ๋ ๋น๋ ํ์์ ํ์ ๋ณด์กด๋๋ ํ์(Static)์ผ๋ก ๊นก๊นก ์ผ์ด์ 0.1์ด ๋ง์ ์๋น๋จ! */}
<StaticHeader />
{/* ๐ ๋์ ํญํ ๊ตฌ์ญ์ Suspense ์บก์๋ก ๊ฒฉ๋ฆฌํด ๊ฐ๋ฌ๋ฒ๋ ธ๋ค! */}
<Suspense fallback={<div>์ฅ๋ฐ๊ตฌ๋ ๋ก๋ฉ์ค...</div>}>
<PersonalCart />
</Suspense>
{/* ๐ ์ฌ๊ธฐ๋ Static! */}
<Footer />
</main>
)
}[๋ง๋ฒ์ ์๋ ์๋ฆฌ (PPR ์บ์ฑ ๊ฒฐํฉ)]
- ๋น๋ ๋ช
๋ น(
next build) ์, ๋ฅ์คํธ ๋ด์ด ํธ๋ฆฌ๋ฅผ ์ํํ๋ค๊ฐSuspense์บก์์ ๋ง๋๋ฉด "์ํ! ์ด ๊ตฌ๋ฉ(Hole) ์์ชฝ์ ํญํ ์ง๋๋๊น ๋ด๊ฐ ๊ฑด๋๋ฆฌ๋ฉด ์ ๋๊ฒ ๊ตฐ. ๋์ ์บก์ ๋ฐ๊นฅ์ ๊ป๋ฐ๊ธฐ ๋ ์ด์์๋ค์ ๋ชจ์กฐ๋ฆฌ ์ ์ ํ์ ์ค์ผ๋ ํค(Static)์ผ๋ก ์๋ฉ ๊ตฌ์๋์!" ํ๊ณ ๋ถ๋ถ ํ์์ ๋ง๋ค์ด๋ด. - ์ ์ ๊ฐ ์ ์ํ๋ฉด? 0.1์ด ๋ง์ ๊ทธ ๊ฐ์ด๋ฐ์ ๊ตฌ๋ฉ์ด ๋ปฅ ๋ซ๋ฆฐ ํ์ ๊ป๋ฐ๊ธฐ(GNB + Footer + ์ฅ๋ฐ๊ตฌ๋ ๋ก๋ฉ์ค ๊ธ์จ)๊ฐ ๋ฌด์กฐ๊ฑด ์ฆ๊ฐ ์๋น(TTFB ํญ๋ฐ)๋จ!!
- ๊ทธ์ ๋์์ ์บก์ ์์ชฝ์ ์ด์ด ๋ น๋ ๋์ ์ฅ๋ฐ๊ตฌ๋ ์ปดํฌ๋ํธ๊ฐ ๋ค๋ฆ๊ฒ ์ฟ ํค๋ฅผ ๊น๋ณด๊ณ ์๋ฒ ์ฐ์ฐ์ ๋ง์น ๋ค ๊ทธ ๋น ๊ตฌ๋ฉ์ ์ฑ์ฐ๋ฌ Stream ์ ์ก๋๋ค.
์ด๊ฒ์ด Next.js๊ฐ ๋ฐ์ด๋ถ์ด๋ ๊ถ๊ทน์ ์ํคํ ์ฒ, ๊ป๋ฐ๊ธฐ๋ ํ์์ผ๋ก! ์์ด์ ๋ฌดํํ ๋์ ๋ฐ์ดํฐ๋ก! "Partial Pre-Rendering(PPR)" ์ ์ค์ฒด์ผ.
๐ฅ ์๋ฌ ํด๊ฒฐ ์นดํ๋ก๊ทธ
์๋ฌ ๋ฉ์์ง๊ฐ ๋จ๋ฉด Ctrl+F๋ก ๊ฒ์ํด๋ด.
โ 'use cache' is an experimental feature...
์์ธ: next.config.ts ๊ณ๊ธฐํ์ ์์ง ์คํ์ค ์ต์
์ ์ ์ผ๊ณ ์ด 15๋ฒ์ ์ ๋ฌํจ์ ์ฐ๋ ค๊ณ ์๋ํจ.
ํด๊ฒฐ์ฑ
: ๊ณ๊ธฐํ ์ค์ ํ์ผ์ ๋ค์ด๊ฐ experimental: { dynamicIO: true } ์ค์ ๋ฑ ๊ด๋ จ Next.js 15+ ์ต์ ๋ฆด๋ฆฌ์ฆ ๋
ธํธ๋ฅผ ์ฐธ๊ณ ํด ํ๋๊ทธ๋ฅผ ์๋ ์ ๊ฒํด์ผ ํ๋ค.
โ Suspense ์บก์์ ์ ์์ ๋๋ฐ ๋น๋ํด๋ณด๋ฉด ์๊พธ ์์ด๊น์ง ๋ค ๊น๋ฐ๋ ค์ง ์ฑ ์ ์ฒด ํ์ด์ง๊ฐ Dynamic์ผ๋ก ๊ฐ๋ฑ๋นํด๋ฒ๋ ค์ ใ
ใ
์์ธ: ์์ฃผ ํํ ์ค์.
ํญํ ์ฝ๋(const t = cookies().get())๋ฅผ ์บก์์ ๋ฐ๊นฅ(๋ถ๋ชจ)์ธ page.tsx ๋ด๋ถ ์ค์ฝํ์ ๋ฉ๊ทธ๋ฌ๋ ์ ์ธํด ๋๊ณ , ๊ทธ๊ฑธ ์์ด ์ปดํฌ๋ํธ์ Props๋ก ๋ด๋ ค๋ณด๋ด๋ ์ฐ๋ฅผ ๋ฒํ๊ธฐ ๋๋ฌธ.
๋ถ๋ชจ๊ฐ ์ฟ ํค ํญํ์ ๋
ธ์ถ๋ ์๊ฐ ๋ถ๋ชจ์ ๊ป๋ฐ๊ธฐ๋ ํจ๊ป ์ค์ผ๋์ด ์์ํ ํ์ ๊ธฐ๋ฅ์ ์์คํ๋ค.
ํด๊ฒฐ์ฑ
: ๋ฌด์กฐ๊ฑด ์์ด ์ปดํฌ๋ํธ ์ค์ฝํ ๋ด๋ถ ์์ชฝ์ผ๋ก cookies()๋ ๋ฆฌํ์คํธ ํค๋ ์ฝ๊ธฐ ๋ช
๋ น๋ฌธ์ ํต์งธ๋ก ๊ฒฉ๋ฆฌ/์ด์ ์์ผ์ผ PPR์ด ํญํ์ ์ ๋๋ก ํผํด ๊ฐ๋ค.
๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
| ๊ธฐ์ ๋๊ตฌ | ํจ๊ณผ | ์ฅ๋จ์ ๋ฐ ์ฌ์ฉ์ฒ |
|---|---|---|
'use cache' | ๋๋ฆฐ API๋ ๋ฌด๊ฑฐ์ด ํจ์, ์ปดํฌ๋ํธ UI๋ฅผ ํต์งธ๋ก ์ผ๋ฆฐ๋ค (DB ๋ถํ 0) | 15+ ์ต์ ๋ฒ์ ์๋ง ํต์ฐฐ๋๋ฉฐ, ๋ก์ ๋ฐ์ดํฐ๋ฅผ ์ธ์ ๋ถ์ ์ง(Invalidate)์ ๋ํ ์๋ฒฝํ ์ค๊ณ๊ฐ ์ ํ๋์ด์ผ ํจ |
Suspense ์บก์ํ (PPR) | ๋ ๋๋ง ํ์ ํ์ ๊ธฐ์์ "์ฌ๊ธฐ๋ง ๊ตฌ๋ฉ ๋ซ์ด์ฃผ์ผ" ์์ . (์ฒด๊ฐ์๋ ๋น) | ๊ฒ๋ฐ์์ด ๋ ๋๋ง. ํ์ด์ง ํ๋์ ์ ์ ์บ์์ ๋์ ํต์ ์ด ์ถค์ ์ถ๋ Next.js ๊ถ๊ทน์ ๋ชจ๋ฒ ์ํคํ ์ฒ |
React.cache (๋ณต์ต) | ์ด๋ฒ ํ ๋ฒ ๋ฐฉ๋ฌธ ๋์ ๋๊ฐ์ ํธ์ถ ๋ฐฉ์ด (์บ์ ์ฐฝ๊ณ ์ ์ฅ X) | 1์ด์ด. Props Drilling ๋ฐฉ์ด์ฉ |
๐ก ์๋์ด์ ๋ฉํ ๋ชจ๋ธ
14๋ฒ์ ๊น์ง๋fetch์ ๋ฌ๋ฆฐ ํ๋ผ๋ฏธํฐ ์ต์ ์ ๋ชฉ์ ๋งธ๋ค๋ฉด, 15๋ฒ์ ๋ถํฐ๋ ํจ์ ๋ฌถ์์ด๋ UI ์กฐ๊ฐ ๋ฉ์ด๋ฆฌ ์์ฒด๋ฅผ ํ์ ์ผ๋ก ์ง์ด ์ผ๋ ค๋ฒ๋ฆฌ๋ ์ฐ์ํ ์๋์ ์ง์ ํ๋ค. ์ปดํฌ๋ํธ ์์์ ์ด๋ ๋ถ๋ถ๊น์ง ์ ์ ์ธ์ง ์บก์ ๊ตฌ๋ฉ ๋ซ๊ธฐ(Suspense)๋ง ์ํด๋ ๋น์ ์ ์ ์ ๊ฒฝ์ง!
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
๋ฐฐ์ ์ผ๋ฉด ํ ๋ฒ ๋นํ์ด์ ํ์ธํด๋ด์ผ ํด.
Q1. ์์ฒ ์ด๊ฐ ๋ง์ผํ
๋ถ์์ ์์ฒญ์ ๋ฐ์ ํ ๋ฌ ๋ด๋ด ๋๊ฐ์ ์ด๋ฌ์ ์ํ ๋ชฉ๋ก(๋ฌด๊ฑฐ์ด DB ํต์ ) ์ถ์ฒ ์์ ฏ์ธ <MonthlyPicks> ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์๋ค. ์ด๋ ์์๋ fetch ๋ ์ฐ์ง ์๊ณ Drizzle ORM์ผ๋ก ๋น์ผ ์ฟผ๋ฆฌ๊ฐ ๋ ์๊ฐ๋ค. ๊ทธ๋ฆฌ๊ณ ๊ทธ ์ปดํฌ๋ํธ ์ต์๋จ์ ๋น๋นํ 'use cache' ๋ฅผ ๋ฐ์ ๋ฃ์๋ค! ๊ทธ๋ฐ๋ฐ ์ด๋ ๋ ๋ง์ผํ
ํ์ฅ์ด ์ ํ๋ฅผ ๊ฑธ์ด "์์ฒ ์จ, ๋ด๊ฐ ๋ฐฉ๊ธ ์ด๋๋ฏผ์์ ์ด๋ฌ์ ์ํ 1์๋ฅผ ๋ฐ๊ฟ ๋จ๋๋ฐ ์๋ก๊ณ ์นจํด๋ ์ด์ ์ํ๋ง ๋จ์์! ๋นจ๋ฆฌ ๊ณ ์ณ๋!" ํ๊ณ ์ค์ฐํ
์ ๋ ๋ ธ๋ค. ์์ฒ ์ด๋ ์ด ๋ก์ ์ปดํฌ๋ํธ ์ผ์์กฐ๊ฐ์ ์ด๋ป๊ฒ, ์ด๋ ํ์ด๋ฐ์ ๋ฐ์ด ๋ด์ด ๊ฐฑ์ ํด์ผ ํ ๊น?
โ
์ ๋ต: ๋ค์ ์ฅ(์ฌํ 3์ฅ)์์ ๋ฐฐ์ธ ์บ์ ํญํ ์ง์์์ธ revalidateTag ๋ฅผ ํ์ฉํด์ผ๋ง ํ๋ค.
๐ก ์์ธ ํด์ค: 'use cache'์ ๋ช
์ฐฐ(cacheTag: 'monthly')์ ๋ถ์ด๋, ๋ง์ผํ
ํ์ฅ์ด ๊ด๋ฆฌ์ ํ์ด์ง(์ด๋๋ฏผ)์์ ์ํ [์์ /์ ์ฅ ์๋ฃ ๋ฒํผ]์ ๋๋ฅด๋ ์ก์
์ ์บ์นํ์ฌ ์นํ
์ผ๋ก ์๋ฒ ๋จ์์ revalidateTag('monthly') ์ง์๋ฅผ ๋ด๋ฆฌ๊ฒ ์ํคํ
์ฒ๋ฅผ ์ธํ
ํด์ผ ํ๋ค. ์ฆ, ํ์์ ์์ํ ์ผ๋ ค๋ ์ฑ ๋ฒผ๋ฃฉ์ ๋๋ฆฌ๋ค๊ฐ ์ด๋ฒคํธ ๋๋ฆฌ๋ธ ํ๊ฒฝ(DB ์
๋ฐ์ดํธ ์ฐฐ๋)์์๋ง ํ ๋ฐฉ! ๋ถ์๋ ๊ฒ์ด ํ์
์บ์ฑ์ ๊ต๊ณผ์์ด๋ค.
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์ค๋์ ์ ๋ง ๋ฅ์คํธ 15์ ์ ๊ธฐ์ ์ธ 'use cache' ์ PPR ์ ๋งค๋ ฅ์ ํน ๋น ์ง ๋ ์ด์ผ! ์์ ์ ํ์ด์ง ํ๋๋ฅผ ํต์งธ๋ก ์ ์ ์ผ๋ก ๋ง๋ค์ง ๋ง์ง ๊ณ ๋ฏผํ๋๋ฐ, ์ด์ ๋ ๋ด๊ฐ ์ํ๋ ์กฐ๊ฐ๋ง ํ์
์ผ๋ก ์ง์ด์ ์ผ๋ ค๋ฒ๋ฆด ์ ์๋ค๋ ๊ฒ ์ผ๋ง๋ ๋ง๋ฒ ๊ฐ์์ง ๋ชฐ๋ผ.
๐ก ์ค๋์ ๊ตํ: "๋ณต์กํ ์บ์ฑ ์ค์ ์ ๋งค๋ฌ๋ฆฌ์ง ๋ง๊ณ ,
'use cache'์Suspense๋ฅผ ์กฐํฉํด '๊ป๋ฐ๊ธฐ๋ ์ ์ (Static), ์์ด์ ๋์ (Dynamic)' ์ธ ๊ถ๊ทน์ ์ฑ๋ฅ์ ๊ตฌํํ์!"
์ํธ ๋ฆฌ๋ ๋์ด '๊ป๋ฐ์์ด(PPR)' ๋น์ ๋ฅผ ๋ค์ด ์ค๋ช ํด ์ฃผ์ค ๋, ์ฌ์ฉ์์๊ฒ 0.1์ด ๋ง์ ํ๋ฉด์ ๋ณด์ฌ์ฃผ๋ ๊ฒ ์ ๊ทธ๋ ๊ฒ ์ค์ํ์ง ๋ค์ ํ๋ฒ ๋๊ผ์ด. ์ค๋ ๋จธ๋ฆฌ๋ฅผ ๋๋ฌด ๋ง์ด ์ผ๋๋ ๋ฐฐ๊ฐ ๊ณ ํ๋ค. ์ง์ ๊ฐ๋ ๊ธธ์ ๊ฒ์ ๋ฐ์ญํ๊ณ ์์ ์ด์ดํ ์นํจ ํ ๋ง๋ฆฌ ์ฌ ๋ค๊ณ ๊ฐ์ ์ ํ ์ถํฉ ํํฐ๋ฅผ ํด์ผ์ง! ๋ด์ผ์ ๋ '์ฑ๋ฅ์ ์ง์ฌ์ธ' ์์ฒ ์ด๊ฐ ๋ ๊ฑฐ์ผ! ๐ฃ