| name | jikime-framework-nextjs@16 |
| description | Next.js 16 upgrade guide with breaking changes from 15. 'use cache' directive, PPR stable, updateTag, enhanced streaming. |
| tags | ["framework","nextjs","version","use-cache","ppr","updateTag"] |
| triggers | {"keywords":["nextjs 16","next.js 16","use cache","PPR","updateTag","cacheLife","cacheTag"],"phases":["run"],"agents":["frontend"],"languages":["typescript"]} |
| progressive_disclosure | {"enabled":true,"level1_tokens":"~100","level2_tokens":"~2611"} |
| type | version |
| framework | nextjs |
| version | 16 |
| previous_version | 15 |
| user-invocable | false |
Next.js 16 Upgrade Guide (from 15)
Next.js 15에서 16으로 업그레이드 시 필요한 새로운 기능과 마이그레이션 패턴을 정의합니다.
Version Info
| 항목 | 값 |
|---|
| Version | 16.0.0+ (canary) |
| Release Date | 2025 (Expected) |
| Node.js | 20+ |
| React | 19+ |
Base Conventions (from Next.js 14)
다음 규칙은 Next.js 14부터 동일하게 적용됩니다. 상세 내용은 jikime-framework-nextjs@14를 참조하세요.
| 규칙 | 요약 |
|---|
| 프로젝트 구조 | src/app/ 기반 App Router, src/app/api/[endpoint]/route.ts API 라우트 |
| 네이밍 규칙 | 폴더/파일: kebab-case, 컴포넌트 export: PascalCase |
| UI 라이브러리 | shadcn/ui 필수 사용, lucide-react 아이콘 |
| 스타일링 | Tailwind CSS + CSS variables 기반 테마 |
Project Initialization (Next.js 16)
새 프로젝트를 시작할 때는 다음 순서로 생성합니다:
npx create-next-app@latest my-app --typescript --tailwind --eslint --app --src-dir
cd my-app
npx shadcn@latest init
npx shadcn@latest add button card input form table
CRITICAL: npx shadcn@latest init은 기존 Next.js 프로젝트에서만 실행합니다. 프로젝트 생성은 반드시 create-next-app으로 먼저 해야 합니다.
New Features Summary
| 기능 | 상태 | 설명 |
|---|
'use cache' | Stable | 세분화된 캐싱 제어 |
cacheLife() | Stable | 캐시 수명 제어 |
cacheTag() | Stable | 태그 기반 캐시 관리 |
updateTag() | Stable | 즉시 캐시 무효화 |
| PPR | Stable | Partial Prerendering |
| Turbopack | Stable (prod) | 프로덕션 빌드 지원 |
1. 'use cache' Directive (NEW)
Before (Next.js 15) - unstable_cache
import { unstable_cache } from 'next/cache'
const getCachedUser = unstable_cache(
async (id: string) => {
return await db.user.findUnique({ where: { id } })
},
['user'],
{ revalidate: 3600, tags: ['user'] }
)
After (Next.js 16) - 'use cache'
async function getUser(id: string) {
'use cache'
return await db.user.findUnique({ where: { id } })
}
async function UserProfile({ userId }: { userId: string }) {
'use cache'
const user = await db.user.findUnique({ where: { id: userId } })
return <div>{user.name}</div>
}
With cacheLife and cacheTag
import { cacheLife, cacheTag } from 'next/cache'
async function getProduct(id: string) {
'use cache'
cacheLife('hours')
cacheTag(`product-${id}`)
return await db.product.findUnique({ where: { id } })
}
cacheLife Options
cacheLife('seconds')
cacheLife('minutes')
cacheLife('hours')
cacheLife('days')
cacheLife('weeks')
cacheLife('max')
cacheLife({ stale: 300, revalidate: 60 })
2. updateTag vs revalidateTag
revalidateTag (Existing - Background)
import { revalidateTag } from 'next/cache'
export async function updateProduct(id: string, data: ProductData) {
await db.product.update({ where: { id }, data })
revalidateTag(`product-${id}`)
}
updateTag (NEW - Immediate)
import { updateTag } from 'next/cache'
export async function updateProduct(id: string, data: ProductData) {
await db.product.update({ where: { id }, data })
updateTag(`product-${id}`)
}
When to Use
| 시나리오 | 권장 |
|---|
| 사용자가 즉시 결과를 봐야 함 | updateTag |
| 백그라운드 갱신 OK | revalidateTag |
| 대량 데이터 갱신 | revalidateTag (성능) |
| 단일 레코드 갱신 | updateTag |
3. Partial Prerendering (PPR) - Stable
Enable PPR
const nextConfig = {
experimental: {
ppr: true,
}
}
PPR Pattern
import { Suspense } from 'react'
export default async function ProductPage({
params
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
return (
<div>
{/* Static: Pre-rendered at build time */}
<Header />
<ProductInfo id={id} />
{/* Dynamic: Streamed at request time */}
<Suspense fallback={<ReviewsSkeleton />}>
<DynamicReviews productId={id} />
</Suspense>
{/* Static */}
<Footer />
</div>
)
}
async function DynamicReviews({ productId }: { productId: string }) {
const reviews = await fetch(`/api/reviews/${productId}`, {
cache: 'no-store'
}).then(r => r.json())
return <ReviewList reviews={reviews} />
}
4. Server Actions Enhancements
Immediate UI Update Pattern
'use client'
import { useOptimistic, useTransition } from 'react'
import { updateProduct } from './actions'
export function ProductEditor({ product }) {
const [optimisticProduct, setOptimisticProduct] = useOptimistic(product)
const [isPending, startTransition] = useTransition()
async function handleUpdate(formData: FormData) {
const newName = formData.get('name') as string
setOptimisticProduct({ ...product, name: newName })
startTransition(async () => {
await updateProduct(product.id, { name: newName })
})
}
return (
<form action={handleUpdate}>
<input name="name" defaultValue={optimisticProduct.name} />
<button disabled={isPending}>Save</button>
</form>
)
}
Server Action with updateTag
'use server'
import { updateTag } from 'next/cache'
export async function updateProduct(id: string, data: Partial<Product>) {
await db.product.update({ where: { id }, data })
updateTag(`product-${id}`)
updateTag('products')
}
5. Turbopack Production (Stable)
next dev --turbo
next build --turbo
const nextConfig = {
experimental: {
turbo: {
// Turbopack config
}
}
}
6. Enhanced Streaming
Streaming with Suspense Boundaries
import { Suspense } from 'react'
export default function Dashboard() {
return (
<div className="grid grid-cols-3 gap-4">
{/* Each streams independently */}
<Suspense fallback={<CardSkeleton />}>
<RevenueCard />
</Suspense>
<Suspense fallback={<CardSkeleton />}>
<UsersCard />
</Suspense>
<Suspense fallback={<CardSkeleton />}>
<OrdersCard />
</Suspense>
</div>
)
}
async function RevenueCard() {
'use cache'
cacheLife('minutes')
const revenue = await getRevenue()
return <Card title="Revenue" value={revenue} />
}
Migration from Next.js 15
Step 1: Update Dependencies
npm install next@16 react@19 react-dom@19
Step 2: Update Caching Strategy
import { unstable_cache } from 'next/cache'
const getData = unstable_cache(async () => {...}, ['key'], { revalidate: 60 })
async function getData() {
'use cache'
cacheLife('minutes')
cacheTag('data')
return ...
}
Step 3: Update Invalidation
revalidateTag('products')
updateTag('products')
revalidateTag('products')
Step 4: Enable PPR (Optional)
const nextConfig = {
experimental: {
ppr: true,
}
}
Migration Checklist
Caching
Performance
Testing
Common Patterns
Cached Data with Immediate Invalidation
import { cacheLife, cacheTag } from 'next/cache'
export async function getProducts() {
'use cache'
cacheLife('hours')
cacheTag('products')
return await db.product.findMany()
}
export async function getProduct(id: string) {
'use cache'
cacheLife('hours')
cacheTag(`product-${id}`)
cacheTag('products')
return await db.product.findUnique({ where: { id } })
}
'use server'
import { updateTag } from 'next/cache'
export async function createProduct(data: ProductData) {
await db.product.create({ data })
updateTag('products')
}
export async function updateProduct(id: string, data: Partial<Product>) {
await db.product.update({ where: { id }, data })
updateTag(`product-${id}`)
updateTag('products')
}
PPR with Auth
import { Suspense } from 'react'
import { auth } from '@/lib/auth'
export default async function DashboardPage() {
return (
<div>
{/* Static shell */}
<DashboardHeader />
{/* Dynamic: User-specific content */}
<Suspense fallback={<WelcomeSkeleton />}>
<WelcomeMessage />
</Suspense>
{/* Static */}
<DashboardNav />
{/* Dynamic: Real-time data */}
<Suspense fallback={<StatsSkeleton />}>
<LiveStats />
</Suspense>
</div>
)
}
async function WelcomeMessage() {
const session = await auth()
return <h1>Welcome, {session?.user?.name}</h1>
}
Related Skills
| 스킬 | 용도 |
|---|
jikime-framework-nextjs@14 | Next.js 14 App Router 기본 패턴, 프로젝트 구조, 네이밍 규칙, shadcn/ui |
jikime-framework-nextjs@15 | Next.js 15 업그레이드 가이드 (async params, fetch caching) |
jikime-platform-vercel | Vercel 배포, Edge Functions, ISR |
jikime-library-shadcn | shadcn/ui 컴포넌트 라이브러리 (Next.js 필수) |
Version: 1.1.0
Last Updated: 2026-01-23
Previous Version: See jikime-framework-nextjs@15