๐ง 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 15+๋ก ๋์ด์ค๋ ๊ธฐ๋ณธ
fetch๊ฐ ์์ ์ฒ๋ผ ์์์ ์ค๋ ์บ์๋๋ค๊ณ ๊ฐ์ ํ๋ฉด ์ ๋๊ฒ ๋๋ผ๊ณ ์. ๋ฌด๊ฑฐ์ด ํต๊ณ ์ปดํฌ๋ํธ๋ ์ฌ์ฌ์ฉํ๊ณ ์ถ์๋ฐ, ์ด๋๊น์ง ์บ์ํด๋ ์์ ํ์ง ์ฝ๋์์ ๋ช ํํ ํํํ ๋ฐฉ๋ฒ์ด ์์๊น์?" - ์ํธ(๋ฆฌ๋): "๊ทธ๋ ๋ณด๋ ๊ฒ Cache Components์
'use cache'์์.next.config.ts์์cacheComponents: true๋ฅผ ์ผ๊ณ , ํจ์๋ ์ปดํฌ๋ํธ ์์์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ๊ณ์ฐ๋ง ์บ์ ๋์์ผ๋ก ์ ์ธํฉ๋๋ค. ๋จ,cacheLife,cacheTag,updateTag๊น์ง ๊ฐ์ด ์ค๊ณํด์ผ ์ด์์์ ์์ ํด์."
๐บ๏ธ ์ด ๋ฌธ์์ ํ๋ฆ
Next 15์ ์บ์ ์ ์ธํ ์๋ โ use cache์ ์ค์ฝํ ์ ์ฉ๋ฒ โ ๋๋ง์ PPR (์ ๋ฐ๋ง ํ์ ๋ง๋ค๊ธฐ) ๊ธฐ์
๐ฏ ์ด ๋ฌธ์๋ฅผ ๋ค ์ฝ์ผ๋ฉด ํ ์ ์๋ ๊ฒ
- ๋ฌด๊ฑฐ์ด ์จ๋ํํฐ SDK๋ ์ง์ ์ง ์ฌ๋ก์ฐ ์ฟผ๋ฆฌ ํจ์ ์์ฒด์
'use cache'์ง์์๋ฅผ ๋ฌ์ ์ปดํฌ๋ํธ ๋ ๋ฒจ์ ์บ์ฑ ์ํคํ ์ฒ๋ฅผ ์ธ๋ฐํ๊ฒ ์ง์กฐํ ์ ์๋ค. - ์ ์ ์ธ ๊ป๋ฐ๊ธฐ ์์ ์ค์๊ฐ ๋ฐ์ดํฐ๋ฅผ ํ์
<Suspense>๊ตฌ์ญ์ ์์ด, PPR ์ํคํ ์ฒ์ ํ๋ช ์ ์ธ TTFB(0.1์ด ์ด๊ธฐ ๋ก๋ฉ)๋ฅผ ๊ตฌํํ ์ ์๋ค.
๐ค ์ ์์์ผ ํ๋๊ฐ
์ฌํ 1์ฅ์์ ์ฐ๋ฆฌ๋ ์บ์ ์๋ช ์ ์ดํดํ์ง ๋ชปํ๋ฉด ํ๋ฉด์ด ๋ก์ ๋ฐ์ดํฐ๋ฅผ ๋ณด์ฌ์ค ์ ์๋ค๋ ๊ฑธ ๋ดค์ด. ์ต์ Next.js์์๋ Cache Components ๋ชจ๋ธ์ด ๊ฐํ๋๋ฉด์ "์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์กฐ๊ฐ์ ๊ฐ๋ฐ์๊ฐ ๋ช ์์ ์ผ๋ก ์ ์ธํ๊ณ , ๊ทธ ์กฐ๊ฐ์ ์๋ช ๊ณผ ๋ฌดํจํ ๊ฒฝ๋ก๋ ํจ๊ป ์ ๋๋ค" ๋ ๋ฐฉํฅ์ด ์ค์ํด์ก์ด.
์ด ์๋ก์ด ์ ์ธ์ ์บ์ฑ ์๋์์ ํ๋ก ํธ ์๋์ด๋ค์๊ฒ ๊ฐ์ฅ ํซํ ๋ฌด๊ธฐ๊ฐ ๋ฐ๋ก ์ฌํ 2์ฅ์ ์ฃผ์ ์ธ 'use cache' ์์ฝ์ด์ PPR(๋ถ๋ถ ๋ ๋๋ง) ์ด์ผ.
์ด๊ฑธ ๋ชจ๋ฅด๋ฉด ๋๋ Next 15 ์ธ์์์ ๋ชจ๋ ํ์ด์ง ์ปดํฌ๋ํธ๋ฅผ ๋น์ผ CPU ๋ ๋๋ง์ผ๋ก ๋๋ฆฌ๋ค๊ฐ ํด๋ผ์ฐ๋ ์๊ธ ๋ฌธ์ ์ ๋ง๊ณ ํ์ฌ์์ ์ง์ ์ธ๊ฒ ๋ ์ง๋ ๋ชฐ๋ผ. ํ๋ฉด ์ ์ฒด ๋จ์๊ฐ ์๋, "๋ด๊ฐ ์ํ๋ ์กฐ๊ฐ"๋ง ์ผ๋ ค๋ฒ๋ฆฌ๋ ์ต์ ํ์ ๊ธฐ๋ฒ์ ์ง๊ธ๋ถํฐ ๋ง์คํฐํ์.
๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
๐ง 5์ด์๊ฒ ์ค๋ช ํ๋ค๋ฉด? (์ํธ์ ๋น๊ฒฐ ๋ง๋ฒ์ฑ )
โ ๊ณผ๊ฑฐ 14๋ฒ์ (๋๋ณด๋ผ ๊ด์ญ ๋ง๋ฒ)
๋ง์ ์ ์ฒด(ํ์ด์ง ํต์งธ๋ก)์ ๋๋ณด๋ผ๋ฅผ ๋ด๋ ค์ ์จ ๋๋ค๋ฅผ ์ผ๋ ค๋ฒ๋ ธ์ด. ๋ค ๊ฝ๊ฝ ์ผ์ด๋ถ์ด์ ์ฅ์ฌ๋ ๋น ๋ฅธ๋ฐ ๋์ฌ๋์ด ์ ์์ง์ฌ์ ๋ต๋ตํ์ด.โ ํ์ฌ 15๋ฒ์ (ํ์ ๋น๊ฒฐ ๋ง๋ฒ
'use cache')
์ด์ ์ํธ ๋ฆฌ๋๊ฐ ์์ ์งํก์ด ํ๋๋ฅผ ๋ค๊ณ ๋ค๋ .
"์!" ํ๊ณ ์งํก์ด('use cache')๋ก ์น๋ ํน์ ์์(ํจ์, ์ปดํฌ๋ํธ)๋ง ๊ฝ๊ฝ ์ผ์ด๋ถ์ด์ (์บ์ ์๋ฒ์ ์ ์ฅ), ๋ค์๋ฒ ๋ถ๋ฅผ ๋ ์ฐ์ฐ 0์ด ๋ง์ ์์ ํ์ด ํ์ด๋์! ๋ง์ ์ฌ๋๋ค(๋๋จธ์ง ๋์ ์์)์ ์ถฅ์ง ์๊ณ ์ค์๊ฐ์ผ๋ก ๋ง ๋ฐ์ด๋ค๋ ์ ์์ง.
๐งฉ use cache ๋๋ ํฐ๋ธ: ํจ์ ์์ค์ ์๊ตฌ ๋๊ฒฐ ๐ข
'use client' ๋ 'use server' ์ฒ๋ผ ํ์ผ, ์ปดํฌ๋ํธ, ํจ์ ์ค์ฝํ ์ต์๋จ์ ๋ถ์ด๋ ์ง์์ด์ผ. ์ต์ ๊ณต์ ๋ฌธ์ ๊ธฐ์ค์ผ๋ก๋ Cache Components ๊ธฐ๋ฅ์ด๋ฉฐ, next.config.ts์ cacheComponents: true๋ฅผ ์ผ ๋ค ์ฌ์ฉํ๋ค.
๋จผ์ ํ๋ก์ ํธ ์ค์ ์ ์บ์ ์ปดํฌ๋ํธ๋ฅผ ๋ช ์ํ๋ค.
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
}
export default nextConfig์ด ์ค์ ์ ์ผ์ผ 'use cache', cacheLife, cacheTag๋ฅผ ํ๋์ ์บ์ ๋ชจ๋ธ๋ก ์ค๋ช
ํ ์ ์๋ค.
์ํฉ: ๋ฌด๊ฑฐ์ด ํต๊ณ ๋ถ์ ํจ์ (fetch๊ฐ ์๋!)
์์ฒ ์ด๊ฐ ์ง๋์ 5์ด์ง๋ฆฌ ์๊ฐ ์ง๊ณ ์ฟผ๋ฆฌ๊ฐ ์์ด. fetch๋ก ํธ์ถํ๋ ์ธ๋ถ API๊ฐ ์๋๋ผ, Node.js ํ๊ฒฝ์์ DB ํด๋ผ์ด์ธํธ(Prisma ๋ฑ)์ ๋ฌด๊ฑฐ์ด ๊ณ์ฐ์ ์ง์ ์ํํ๋ ํจ์๋ผ๊ณ ๊ฐ์ ํด๋ณด์.
// app/lib/stats.ts
// fetch๊ฐ ์๋๋ฏ๋ก "์ด๋ค ์๋ช
์ผ๋ก ์ฌ์ฌ์ฉํ ์ง"๋ฅผ ๋ณ๋๋ก ์ ์ธํด์ผ ํ๋ค.
export async function getHeavyMonthlyStats(year: number, month: number) {
const rawData = await db.query(...);
const result = heavyCPUCalculate(rawData); // ์ฌ๊ธฐ์ 5์ด๊ฐ ๋๋๋...
return result;
}โ
๋น๊ฒฐ ๋ถ์ ๋ถ์ด๊ธฐ ('use cache')
์บ์ ๊ฐ๋ฅํ ์ ๋ ฅ๊ณผ ๊ฐฑ์ ์กฐ๊ฑด์ด ๋ถ๋ช ํ๋ค๋ฉด ํจ์ ๋ด๋ถ์์ ์บ์ ์ ์ฑ ์ ๊ฐ์ด ์ ์ธํ๋ค.
// app/lib/stats.ts
import { cacheLife, cacheTag } from 'next/cache'
export async function getHeavyMonthlyStats(year: number, month: number) {
'use cache';
cacheLife('hours');
cacheTag(`monthly-stats-${year}-${month}`);
const rawData = await db.query(...);
const result = heavyCPUCalculate(rawData);
return result;
}'use cache'์ ์บ์ ํค์๋ ํจ์ ์์น, ๋น๋ ID, ์ง๋ ฌํ ๊ฐ๋ฅํ ์ธ์ ๋ฑ์ด ๋ค์ด๊ฐ๋ค. ๊ทธ๋์ ๊ฐ์ year, month๋ก ํธ์ถํ ๋ ์ฌ์ฌ์ฉํ ์ ์์ง๋ง, ๊ด๋ฆฌ์ ์ ์ฅ ์ก์
์์๋ ๊ฐ์ ํ๊ทธ๋ฅผ updateTag๋ revalidateTag๋ก ๊ฐฑ์ ํด์ผ ํ๋ค.
[์บ์์ ๋ถ์์ฉ (๋ฌดํจํ)]
์ด๊ฑฐ ์ฌํ 1์ฅ์ React.cache() (1์ด ๋จ๋ฐ์ฑ ์ง์ฐ๊ฐ) ๋ ์ฐฉ๊ฐํ๋ฉด ์ ๋ผ!
React.cache()๋ ๊ฐ์ ์์ฒญ ์์ ์ค๋ณต ํธ์ถ์ ์ค์ด๋ ๋ฉ๋ชจ์ด์ ์ด์
์ด๊ณ , 'use cache'๋ ์์ฒญ์ ๋์ด ์ฌ์ฌ์ฉ๋ ์ ์๋ ์บ์ ์ ์ฑ
์ด์ผ.
์ฆ, ๋๊ตฐ๊ฐ๊ฐ ์ด๋๋ฏผ ํ์ด์ง์์ ๋งค์ถ ๊ธฐ์ค์ ๊ฐฑ์ ํ๋ค๋ฉด ์ ์ฅ ์ก์
์์ ๊ฐ์ ํ๊ทธ๋ฅผ ๊ฐฑ์ ํด์ผ ์ ์ ๊ฐ ๋ก์ ํต๊ณ๋ฅผ ์ค๋ ๋ณด์ง ์๋๋ค.
๐ฑ ์ปดํฌ๋ํธ ๋จ์ ์บ์ฑ (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'); // ๐ฃ ์๋๋ผ๋ฉด ์ ์ฒด ๋ผ์ฐํธ๋ฅผ ๋์ ์ผ๋ก ๋ง๋ค ์ ์๋ ์์ฒญ ์์ API!!
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)์ผ๋ก ์๋ฉ ๊ตฌ์๋์!" ํ๊ณ ๋ถ๋ถ ํ์์ ๋ง๋ค์ด๋ด. - ์ ์ ๊ฐ ์ ์ํ๋ฉด? ๊ฐ์ด๋ฐ ๋์ ๊ตฌ์ญ์ fallback์ผ๋ก ๋จ๊ฒจ๋ ์ฑ ์ ์ ์ ธ(GNB + Footer + ์ฅ๋ฐ๊ตฌ๋ ๋ก๋ฉ์ค ๊ธ์จ)์ด ๋น ๋ฅธ TTFB๋ก ๋จผ์ ์ ๋ฌ๋๋ค.
- ๊ทธ์ ๋์์ ์บก์ ์์ชฝ์ ์ด์ด ๋ น๋ ๋์ ์ฅ๋ฐ๊ตฌ๋ ์ปดํฌ๋ํธ๊ฐ ๋ค๋ฆ๊ฒ ์ฟ ํค๋ฅผ ๊น๋ณด๊ณ ์๋ฒ ์ฐ์ฐ์ ๋ง์น ๋ค ๊ทธ ๋น ๊ตฌ๋ฉ์ ์ฑ์ฐ๋ฌ Stream ์ ์ก๋๋ค.
์ด๊ฒ์ด Next.js๊ฐ ๋ฐ์ด๋ถ์ด๋ ๊ถ๊ทน์ ์ํคํ ์ฒ, ๊ป๋ฐ๊ธฐ๋ ํ์์ผ๋ก! ์์ด์ ๋ฌดํํ ๋์ ๋ฐ์ดํฐ๋ก! "Partial Pre-Rendering(PPR)" ์ ์ค์ฒด์ผ.
๐ฅ ์๋ฌ ํด๊ฒฐ ์นดํ๋ก๊ทธ
์๋ฌ ๋ฉ์์ง๊ฐ ๋จ๋ฉด Ctrl+F๋ก ๊ฒ์ํด๋ด.
โ 'use cache'๋ฅผ ๋ถ์๋๋ฐ ์บ์๊ฐ ์ ์ฉ๋์ง ์์
์์ธ: next.config.ts์ cacheComponents: true๊ฐ ์๊ฑฐ๋, ์บ์ ํจ์์ ์ธ์๊ฐ ์ง๋ ฌํํ ์ ์๋ ๊ฐ(Class ์ธ์คํด์ค, ํจ์, Symbol ๋ฑ)์ ํฌํจํ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค.
ํด๊ฒฐ์ฑ
: ์ค์ ์ ๋จผ์ ํ์ธํ๊ณ , ์บ์ ์ค์ฝํ์๋ ์ง๋ ฌํ ๊ฐ๋ฅํ ์ธ์๋ง ๋๊ธด๋ค. ์์ฒญ ์์ ๊ฐ(cookies, headers)์ด ํ์ํ๋ค๋ฉด ์บ์ ๋ฐ์์ ์ฝ์ด ์ธ์๋ก ์ ๋ฌํ๊ฑฐ๋, ์ ๋ง ์ฌ์ฉ์๋ณ ์บ์๊ฐ ํ์ํ ๊ฒฝ์ฐ ๋ณ๋ ์ง์์ด๋ฅผ ๊ฒํ ํ๋ค.
โ Suspense ์บก์์ ์ ์์ ๋๋ฐ ๋น๋ํด๋ณด๋ฉด ์๊พธ ์์ด๊น์ง ๋ค ๊น๋ฐ๋ ค์ง ์ฑ ์ ์ฒด ํ์ด์ง๊ฐ Dynamic์ผ๋ก ๊ฐ๋ฑ๋นํด๋ฒ๋ ค์ ใ
ใ
์์ธ: ์์ฃผ ํํ ์ค์.
์ํํ ์ฝ๋(const t = cookies().get())๋ฅผ ์บก์์ ๋ฐ๊นฅ(๋ถ๋ชจ)์ธ page.tsx ๋ด๋ถ ์ค์ฝํ์ ๋ฉ๊ทธ๋ฌ๋ ์ ์ธํด ๋๊ณ , ๊ทธ๊ฑธ ์์ด ์ปดํฌ๋ํธ์ Props๋ก ๋ด๋ ค๋ณด๋ด๋ ์ฐ๋ฅผ ๋ฒํ๊ธฐ ๋๋ฌธ.
๋ถ๋ชจ๊ฐ ์์ฒญ ์์ API์ ๋
ธ์ถ๋ ์๊ฐ ๋ถ๋ชจ์ ๊ป๋ฐ๊ธฐ๋ ํจ๊ป ์ค์ผ๋์ด ์์ํ ํ์ ๊ธฐ๋ฅ์ ์์คํ๋ค.
ํด๊ฒฐ์ฑ
: ๋ฌด์กฐ๊ฑด ์์ด ์ปดํฌ๋ํธ ์ค์ฝํ ๋ด๋ถ ์์ชฝ์ผ๋ก cookies()๋ ๋ฆฌํ์คํธ ํค๋ ์ฝ๊ธฐ ๋ช
๋ น๋ฌธ์ ํต์งธ๋ก ๊ฒฉ๋ฆฌ/์ด์ ์์ผ์ผ PPR์ด ๋์ ๊ตฌ์ญ์ ์ ๋๋ก ๋ถ๋ฆฌํ๋ค.
๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
| ๊ธฐ์ ๋๊ตฌ | ํจ๊ณผ | ์ฅ๋จ์ ๋ฐ ์ฌ์ฉ์ฒ |
|---|---|---|
'use cache' | ๋๋ฆฐ API๋ ๋ฌด๊ฑฐ์ด ํจ์, ์ปดํฌ๋ํธ UI ๊ฒฐ๊ณผ๋ฅผ ์ฌ์ฌ์ฉํ๋ค | cacheComponents: true, cacheLife, cacheTag์ ํจ๊ป ์ค๊ณํด์ผ ํจ |
Suspense ์บก์ํ (PPR) | ๋ ๋๋ง ํ์ ํ์ ๊ธฐ์์ "์ฌ๊ธฐ๋ง ๊ตฌ๋ฉ ๋ซ์ด์ฃผ์ผ" ์์ . (์ฒด๊ฐ์๋ ๋น) | ๊ฒ๋ฐ์์ด ๋ ๋๋ง. ํ์ด์ง ํ๋์ ์ ์ ์บ์์ ๋์ ํต์ ์ด ์ถค์ ์ถ๋ Next.js ๊ถ๊ทน์ ๋ชจ๋ฒ ์ํคํ ์ฒ |
React.cache (๋ณต์ต) | ์ด๋ฒ ํ ๋ฒ ๋ฐฉ๋ฌธ ๋์ ๋๊ฐ์ ํธ์ถ ๋ฐฉ์ด (์บ์ ์ฐฝ๊ณ ์ ์ฅ X) | 1์ด์ด. Props Drilling ๋ฐฉ์ด์ฉ |
๐ก ์๋์ด์ ๋ฉํ ๋ชจ๋ธ
14๋ฒ์ ๊น์ง๋fetch์ ๋ฌ๋ฆฐ ํ๋ผ๋ฏธํฐ ์ต์ ์ ๋ชฉ์ ๋งธ๋ค๋ฉด, 15๋ฒ์ ๋ถํฐ๋ ํจ์ ๋ฌถ์์ด๋ UI ์กฐ๊ฐ ๋ฉ์ด๋ฆฌ ์์ฒด๋ฅผ ํ์ ์ผ๋ก ์ง์ด ์ผ๋ ค๋ฒ๋ฆฌ๋ ์ฐ์ํ ์๋์ ์ง์ ํ๋ค. ์ปดํฌ๋ํธ ์์์ ์ด๋ ๋ถ๋ถ๊น์ง ์ ์ ์ธ์ง ์บก์ ๊ตฌ๋ฉ ๋ซ๊ธฐ(Suspense)๋ง ์ํด๋ ๋น์ ์ ์ ์ ๊ฒฝ์ง!
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
Q1. 'use cache'๋ฅผ ํจ์ ์์ ๋ถ์์ ๋ ์บ์ ํค๋ฅผ ๊ฒฐ์ ํ๋ ํต์ฌ ์ ๋ ฅ์ ๋ฌด์์ธ๊ฐ?
โ ์ ๋ต: ํจ์์ ์ธ์์ ์บ์ ๊ฐ๋ฅํ ์คํ ์ปจํ ์คํธ๋ค.
๐ก ์์ธ ํด์ค: 'use cache'๋ "์ด ํจ์์ ๊ฒฐ๊ณผ๋ฅผ ์ฌ์ฌ์ฉํด๋ ๋๋ค"๊ณ ์ ์ธํ๋ ๋๋ ํฐ๋ธ๋ค. ๊ฐ์ ์ธ์๋ก ํธ์ถํ์ ๋ ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํด์ผ ์์ ํ๋ค. ์์ฒญ๋ง๋ค ๋ฌ๋ผ์ง๋ cookies(), headers() ๊ฐ์ ๊ฐ์ ๊ธฐ๋๋ ๋ก์ง์ ์บ์ ๋ฒ์์ ๋ฃ์ผ๋ฉด ์ ๋๋ค. ์ํธ๊ฐ ๋ณด๋ ๊ธฐ์ค์ ์์์ฑ์ด๋ค. ์ ๋ ฅ์ด ๊ฐ์ผ๋ฉด ๊ฒฐ๊ณผ๋ ๊ฐ๋ค๊ณ ๋งํ ์ ์์ด์ผ ํ๋ค.
Q2. ์์์ด ์๊ฐ ์ถ์ฒ ์ํ์ ์ด๋๋ฏผ์์ ๋ฐ๊ฟจ๋ค. 'use cache'๋ก ์บ์ํ getMonthlyPicks()๊ฐ ์๋ค๋ฉด ์ด๋ค ๋ณด๊ฐ์ด ํ์ํ ๊น?
โ ์ ๋ต: cacheTag('monthly-picks')์ฒ๋ผ ํ๊ทธ๋ฅผ ๋ถ์ด๊ณ , ์ ์ฅ ์ก์ ์์ revalidateTag ๋๋ ์ฆ์ ์ผ๊ด์ฑ์ด ํ์ํ๋ฉด updateTag๋ก ๊ฐฑ์ ํ๋ค.
๐ก ์์ธ ํด์ค: ์บ์๋ ๋ง๋๋ ์๊ฐ๋ณด๋ค ๊นจ๋ ์๊ฐ์ด ๋ ์ค์ํ๋ค. ์ต์ ๊ณต์ ๋ฌธ์ ๊ธฐ์ค์ผ๋ก Cache Components์์๋ cacheLife๋ก ์๊ฐ ์ ์ฑ ์, cacheTag๋ก ์จ๋๋งจ๋ ๊ฐฑ์ ๋์์ ์ ์ธํ๋ค. revalidateTag๋ stale-while-revalidate ์ฑ๊ฒฉ์ผ๋ก ๋ฐฐ๊ฒฝ ๊ฐฑ์ ์ ์ ํฉํ๊ณ , ์ฌ์ฉ์๊ฐ ๋ฐฉ๊ธ ์ด ๊ฒฐ๊ณผ๋ฅผ ์ฆ์ ๋ด์ผ ํ๋ read-your-own-writes ์ํฉ์ updateTag๊ฐ ๋ ๋ง๋ค.
Q3. ์์ฒ ์ด์ ํ ์คํธ ํ์: ๋์๋ณด๋ ์๋จ ์ค๋ช , ์ธ๊ธฐ ๊ธ ๋ชฉ๋ก, ๋ด ์๋ฆผ ์นด์ดํธ๋ฅผ ํ ํ์ด์ง์ ๋ฐฐ์นํ๋ค. 'use cache'๋ฅผ ์ด๋์ ์ ์ฉํด์ผ ํ ๊น?
โ ์ ๋ต: ์ค๋ช ๊ณผ ์ธ๊ธฐ ๊ธ์ฒ๋ผ ์ฌ์ฉ์์ ๋ฌด๊ดํ๊ณ ์ผ์ ์๊ฐ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ๋ถ๋ถ์๋ง ์ ์ฉํ๋ค.
๐ก ์์ธ ํด์ค: ๋ด ์๋ฆผ ์นด์ดํธ๋ ์ธ์ ๊ณผ ์ค์๊ฐ์ฑ์ ๋ฌถ์ฌ ์์ผ๋ฏ๋ก ์บ์ํ๋ฉด ๋ค๋ฅธ ์ฌ์ฉ์์ ๊ฐ์ด ์์ด๊ฑฐ๋ ๋ก์ ์ซ์๊ฐ ๋ณด์ผ ์ ์๋ค. ๋ฐ๋๋ก ๋ง์ผํ ์ค๋ช ๋ฌธ๊ตฌ๋ ์ธ๊ธฐ ๊ธ ๋ชฉ๋ก์ cacheLife('hours') ๊ฐ์ ์ ์ฑ ๊ณผ ํ๊ทธ ๊ฐฑ์ ์ ๋ถ์ด๋ฉด ๋น์ฉ์ ์ค์ผ ์ ์๋ค. PPR๊ณผ ํจ๊ป ์ธ ๋๋ ์ ์ ์ ธ๊ณผ ๋์ ๊ตฌ๋ฉ์ ์ฑ ์์ ๋ถ๋ฆฌํ๋ ๊ฒ ํต์ฌ์ด๋ค.
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์ค๋์ 'use cache'๋ฅผ "๋น ๋ฅด๊ฒ ํด์ฃผ๋ ์ฃผ๋ฌธ"์ด ์๋๋ผ "์ฌ์ฌ์ฉํด๋ ๋๋ ์ฝ์"์ผ๋ก ๋ค์ ๋ฐฐ์ ๋ค. ์บ์๋ฅผ ํจ์๋ ์ปดํฌ๋ํธ ๋จ์๋ก ๋ถ์ผ ์ ์๋ค๋ ๊ฑด ํธํ์ง๋ง, ๊ทธ๋งํผ ๋ด๊ฐ ์ด๋ค ๋ฐ์ดํฐ๋ฅผ ๊ณ ์ ์ํค๋์ง ๋ ๋ถ๋ช ํ ์์์ผ ํ๋ค.
๐ก "์บ์ ๊ฐ๋ฅํ ์กฐ๊ฐ์ ์ฌ์ฉ์ ์ ๋ ฅ์์ ๋ ๋ฆฝ์ ์ด๊ณ , ๊ฐฑ์ ์ด๋ฒคํธ๋ฅผ ์ค๋ช ํ ์ ์์ด์ผ ํ๋ค."
๋ค์ ์ฝ๋ ๋ฆฌ๋ทฐ์์๋ 'use cache'๊ฐ ๋ณด์ด๋ฉด ์ธ ๊ฐ์ง๋ฅผ ํ์ธํ๊ฒ ๋ค. ์์ฒญ ์์ API๋ฅผ ์ฝ๋์ง, cacheLife๊ฐ ์ค์ ์ ์ ๋ ์๊ตฌ์ ๋ง๋์ง, ๊ด๋ฆฌ์ ์ ์ฅ์ด๋ ์๋ฒ ์ก์ ์์ ํ๊ทธ ๊ฐฑ์ ์ด ์ฐ๊ฒฐ๋์ด ์๋์ง. ์ด์ ์ฑ๋ฅ์ ์ฌ๋ฆฌ๋ ์ ์๋ ์ด์์๊ฐ ๋ฐ์ดํฐ๋ฅผ ๋ฐ๊พธ๋ ์๊ฐ๊น์ง ํฌํจํด์ ๋งํ ์ ์์ ๊ฒ ๊ฐ๋ค.