전 Next.js 의 미래가 마음에 들어요 : 정말 멋있어지고 있어요
(tigerabrodi.blog)- Next.js의 미래 방향이 흥미로움
 - Server Actions에 대한 이슈가 있었지만 React 19의 
useOptimistic,useFormStatus로 개선 가능성이 보임- Remix의 
useFetcher방식도 좋은 DX를 제공함 
 - Remix의 
 - Next.js의 PPR(Partial Pre-rendering)과 새로운 granular 캐시 시스템이 특히 돋보임
 - 전반적으로 매우 긍정적인 인상을 받음
 
The Big Picture
- 
next.config.js에서 새로운 캐시 시스템을 실험적으로 활성화할 수 있음 - 캐시 프로필을 정의해 다양한 만료 시간과 재검증 주기를 설정할 수 있음
 
// next.config.js  
const config = {  
  experimental: {  
    // 새로운 캐싱 시스템 활성화. 이제 코드에서 `use cache` 사용 가능   
    dynamicIO: true,   
    // 선택사항: 캐시 프로파일 설정   
    cacheLife: {  
      blog: {  
        stale: 3600, // 클라이언트 캐시 유지: 1시간  
        revalidate: 900, // 서버에서 새로고침: 15분  
        expire: 86400, // 최대 수명: 1일  
      },  
    },  
  },  
};  
use cache 기본 사용법
- 파일, 컴포넌트, 함수 수준에서 
"use cache"선언을 통해 캐싱 가능함 - 코드 예시에서 
use cache를 추가해 쉽게 캐시를 적용할 수 있음 - 
cacheTag,revalidateTag등을 활용해 원하는 시점에 캐시 무효화가 가능함 
// 1. 파일 단위 캐싱  
"use cache";  
export default function Page() {  
  return <div>Cached Page</div>;  
}  
  
// 2. 컴포넌트 단위 캐싱  
export async function PriceDisplay() {  
  "use cache";  
  const price = await fetchPrice();  
  return <div>${price}</div>;  
}  
  
// 3. 함수 단위 캐싱  
export async function getData() {  
  "use cache";  
  return await db.query();  
}  
태그 기반 캐싱
import { unstable_cacheTag as cacheTag, revalidateTag } from 'next/cache';  
  
// 특정 데이터 그룹 캐싱  
export async function ProductList() {  
  'use cache';  
  cacheTag('products');  
  const products = await fetchProducts();  
  return <div>{products}</div>;  
}  
  
// 데이터 변경 시 캐시 무효화  
export async function addProduct() {  
  'use server';  
  await db.products.add(...);  
  revalidateTag('products');  
}  
사용자 정의 Cache 프로필
- 
unstable_cacheLife를 사용해next.config.js에서 정의한 캐시 프로필을 불러올 수 있음 - 코드 내부에서 선언된 프로필명(예: 
"blog")을 사용해 캐시 정책을 적용함 
import { unstable_cacheLife as cacheLife } from "next/cache";  
  
export async function BlogPosts() {  
  "use cache";  
  cacheLife("blog"); // 미리 정의한 블로그 캐시 프로필 사용  
  return await fetchPosts();  
}  
중요하지만 간과할 수 있는 사항
캐시 키 자동 생성
- 컴포넌트의 
props와arguments가 자동으로 캐시 키에 포함됨 - 직렬화 불가능한 값(함수 등)은 "수정 불가능한 참조" 형태로 처리됨
 
export async function UserCard({ id, onDelete }) {  
  "use cache";  
  // id는 캐시 키에 포함  
  // onDelete는 전달되지만 캐싱에는 영향을 주지 않음  
  const user = await fetchUser(id);  
  return <div onClick={onDelete}>{user.name}</div>;  
}  
동적 콘텐츠와 캐시 콘텐츠의 혼합
- 캐시된 콘텐츠 내부에 동적 콘텐츠를 자식으로 전달해 혼합해 사용할 수 있음
 - 
cacheTag배열을 지정해 여러 태그를 동시에 적용하고 무효화할 수 있음 
export async function CachedWrapper({ children }) {  
  "use cache";  
  const header = await fetchHeader();  
  return (  
    <div>  
      <h1>{header}</h1>  
      {children} {/* 동적 콘텐츠는 그대로 유지 */}  
    </div>  
  );  
}  
export async function ProductPage({ id }) {  
  "use cache";  
  cacheTag(["products", `product-${id}`, "featured"]);  
  // 이 태그들 중 어떤 것을 사용해도 무효화 가능  
}  
캐싱 계층 구조
- 최상위 레벨에서 
"use cache"를 선언하면 해당 영역 전체가 캐시됨 - 특정 부분(예: Suspense를 사용한 동적 섹션)은 캐싱 영역에서 제외할 수 있음
 
"use cache";  
export default async function Page() {  
  return (  
    <div>  
      <CachedHeader />  
      <div>  
        <Suspense fallback={<Loading />}>  
          <DynamicFeed /> {/* 동적 콘텐츠 */}  
        </Suspense>  
      </div>  
    </div>  
  );  
}  
타입 안전성
- 캐시 키와 캐시 프로필 등 문자열을 상수로 관리해 매직 스트링 사용을 줄일 수 있음
 - React Query의 패턴처럼 태그를 생성해주는 방식을 사용하면 편리함
 
// 상수로 캐시 프로필 키를 관리  
export const CACHE_LIFE_KEYS = {  
  blog: "blog",  
} as const;  
  
const config = {  
  experimental: {  
    cacheLife: {  
      [CACHE_LIFE_KEYS.blog]: {  
        stale: 3600,  
        revalidate: 900,  
        expire: 86400,  
      },  
    },  
  },  
};  
캐싱 태그를 효율적으로 관리하는 방법
- React Query 스타일의 태그 팩토리 패턴 적용
 
export const CACHE_TAGS = {  
  blog: {  
    all: ["blog"] as const,  
    list: () => [...CACHE_TAGS.blog.all, "list"] as const,  
    post: (id: string) => [...CACHE_TAGS.blog.all, "post", id] as const,  
    comments: (postId: string) =>  
      [...CACHE_TAGS.blog.all, "post", postId, "comments"] as const,  
  },  
} as const;  
  
// 캐싱 태그 설정  
function tagCache(tags: string[]) {  
  cacheTag(...tags);  
}  
  
// 사용 예제  
export async function BlogList() {  
  "use cache";  
  tagCache(CACHE_TAGS.blog.list());  
}  
SEO 가 중요해서 SSR 이 필요한 상황에서만 Next.js 또는 Remix 같은 framework 를 사용하는게 좋은 것 같습니다.
특히 B2B 비지니스 제품이나 back office 같이 SEO 가 중요하지 않은 서비스에 Next.js 를 도입하는 것은 신중할 필요가 있다고 생각합니다. Next.js 가 강제하는 인터페이스나 복잡도가 개발 생산성을 낮출 수 있기 때문입니다.
개인적으로 SEO 가 불필요한 경우는 Vite + React 가 개발 생산성과 유연성 등에서 훨씬 좋다고 생각합니다.
캐시가 왜 필요한지 이해 불가. 고전적 방식에서 캐시따윈 필요 없었고, 캐시를 사용 해야할 상황이 극히 제한적인데, 쓸데없이 캐시가지고 목숨거는데?