
await 를 사용해야 합니다.import { cookies } from 'next/headers';
// [변경 포인트 1] Props 타입 정의가 Promise로 감싸져야 합니다.
// 이전: { params: { slug: string } }
// 이후: { params: Promise<{ slug: string }> }
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
// [변경 포인트 2] params는 이제 Promise 객체이므로 await로 풀어야 합니다.
// 만약 await 없이 params.slug로 접근하면 에러가 발생합니다.
const { slug } = await params;
// [변경 포인트 3] cookies() 함수 호출 결과도 Promise입니다.
// 데이터베이스 조회 등 다른 비동기 작업과 병렬로 처리될 수 있습니다.
const cookieStore = await cookies();
const theme = cookieStore.get('theme');
return (
<div className={theme?.value}>
<h1>Post Slug: {slug}</h1>
</div>
);
}'use cache' 디렉티브를 도입했습니다.next.config.js 설정이나 fetch 옵션을 건드릴 필요가 없습니다.
캐싱하고 싶은 함수나 컴포넌트 상단에 한 줄만 추가하면 됩니다.import { db } from '@/lib/db';
// symbol(종목코드)을 인자로 받아 가격을 조회하는 함수
export async function StockPrice({ symbol }: { symbol: string }) {
// [핵심] 이 디렉티브를 선언하면 함수 실행 결과가 자동으로 캐싱됩니다.
'use cache';
// 캐시 키(Cache Key)는 자동으로 관리됩니다.
// 인자로 들어온 'symbol' 값이 같다면, 아래 DB 조회 로직은 실행되지 않고
// 저장된 캐시 값을 즉시 반환합니다. (Memoization 원리)
const price = await db.stock.findUnique({
where: { symbol },
select: { price: true }
});
return (
<div className="p-4 border rounded">
<h3>{symbol}</h3>
<p>Current Price: ${price}</p>
</div>
);
}symbol)에 대해 'use server';
import { revalidateTag, updateTag, refresh } from 'next/cache';
import { db } from '@/lib/db';
// 상황 1: 관리자가 상품 정보를 대량으로 수정했을 때
// 사용자는 1~2초 정도 옛날 가격을 봐도 큰 문제가 없는 경우
export async function updateBulkProducts() {
await db.product.updateMany({ ... });
// [SWR] 백그라운드에서 캐시를 갱신합니다.
// 'max'는 캐시 수명을 최대로 설정하겠다는 프로필입니다.
revalidateTag('all-products', 'max');
}
// 상황 2: 사용자가 자신의 프로필을 수정했을 때
// 저장 버튼을 누르자마자 바뀐 이름이 보여야 함
export async function updateProfile(userId: string, data: any) {
await db.user.update({ where: { id: userId }, data });
// [Read-Your-Writes] 캐시를 즉시 만료(Purge)시키고 새로 데이터를 가져옵니다.
updateTag(`user-${userId}`);
}
// 상황 3: 사용자가 알림을 읽었을 때
// 알림 목록 데이터 자체는 갱신할 필요가 없고, 헤더의 '뱃지 숫자'만 바꾸고 싶을 때
export async function readNotification(id: string) {
await db.notification.markAsRead(id);
// 서버 캐시를 건드리지 않고, 클라이언트 라우터만 새로고침(Refresh)합니다.
refresh();
}middleware.ts 파일의 이름이 프록시 패턴 (Proxy Pattern)프록시는 클라이언트와 실제 서버 사이에서 중계자 역할을 하는 디자인 패턴입니다. Next.js의Proxy는 모든 요청의 최전선(Edge)에서 인증을 확인하거나, 경로를 재작성(Rewrite)하는보안 문지기역할을 수행합니다.
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
// [변경] 파일명이 middleware.ts -> proxy.ts로 변경되었습니다.
// [변경] 함수 이름도 export function middleware -> proxy로 변경해야 합니다.
export function proxy(request: NextRequest) {
// 기존 미들웨어 로직은 그대로 사용 가능합니다.
// 예: /dashboard로 들어오는 요청에 인증 토큰이 없으면 로그인 페이지로 리다이렉트
if (request.nextUrl.pathname.startsWith('/dashboard')) {
if (!request.cookies.has('token')) {
return NextResponse.redirect(new URL('/login', request.url));
}
}
return NextResponse.next();
}@folder)에 대응하는 UI가 없으면 404 에러가 나거나 암묵적으로 처리되었지만, 이제는 default.js 파일이 없으면 // 슬롯에 보여줄 기본 컨텐츠가 없는 경우라도 파일은 반드시 존재해야 합니다.
export default function Default() {
// 아무것도 렌더링하지 않음을 명시적으로 선언합니다.
return null;
}'use cache' 로 개발자의 캐싱 의도를 코드에 명확히 드러내며, Proxy 로 네트워크 역할을 정의했습니다.
이러한 변화들은 처음에는 코드를 수정해야 해서 번거로울 수 있지만, 장기적으로는 애플리케이션의 다음 3부에서는 이 모든 변경 사항을 실제 프로젝트에 적용하는 마이그레이션 가이드로 찾아오겠습니다.