Next.js 16, 캐시 구성 요소 및 React 컴파일러: 실제로 변경되는 사항
· 5 min read · Filippo Spinella · Next.js, React, Frontend, Performance
수년간 Next.js에서 가장 짜증나는 질문 중 하나는 "이 페이지가 정적인가요, 아니면 동적인가요?"였습니다. cookies()에 대한 호출, 다양한 옵션이 있는 fetch, 데이터베이스 클라이언트, CMS, 장바구니 또는 사용자 정의 컨텐츠를 추가하기 전까지는 간단한 질문처럼 보입니다.
Next.js 16은 이 대화를 덜 신비롭게 만들려고 노력한다는 점에서 흥미롭습니다. 복잡성을 제거하지는 않지만 정신 모델을 전환합니다. 경로는 기본적으로 동적이며, 캐시는 필요한 경우 자체적으로 선언되며, Suspense은 최신 상태를 유지하는 부분으로 빠른 셸을 구성하는 자연스러운 방법이 됩니다.
이해해야 할 기능은 캐시 구성 요소입니다. 안정적인 Turbopack, React Compiler, proxy.ts 및 새로운 무효화 API는 중요하지만 프레임워크가 배후에서 결정한 것을 추측할 필요 없이 빠른 앱을 구축한다는 동일한 문제를 중심으로 진행됩니다.
이게 중요하니까
실제 앱에는 "정적 페이지"와 "동적 페이지"만 있는 것이 아닙니다. 요구 사항이 다른 다양한 부품이 있습니다.
제품 시트는 하루에 몇 번씩 변경될 수 있습니다. 가격은 더 자주 변경될 수 있습니다. 가용성은 거의 실시간이어야 합니다. 사용자 이름은 개인적인 것입니다. 리뷰를 스트리밍할 수 있습니다. 사이드바가 안정적일 수 있습니다. 카트는 그렇지 않습니다.
모든 것을 하나의 단위로 취급하면 항상 두 가지 극단 중 하나에 이르게 됩니다.
- 공격적인 캐싱 및 오래된 데이터를 볼 위험이 있습니다.
- 모든 곳에서 동적 렌더링이 이루어지고 성능이 필요 이상으로 저하됩니다.
캐시 구성 요소는 이러한 잘못된 선택을 방지하는 역할을 합니다.
실제 모델
cacheComponents: true을 사용하면 "use cache"을 사용하여 캐시 가능한 항목을 선언할 수 있습니다. 그런 다음 기간과 태그를 cacheLife() 및 cacheTag()과 연결할 수 있습니다. 동적 부분은 동적으로 유지되며 Suspense를 사용하여 격리할 수 있습니다.
설정은 작습니다.
// next.config.ts import type { NextConfig } from 'next'; const nextConfig: NextConfig = { cacheComponents: true, }; export default nextConfig;
큰 변화는 구성에 없습니다. 구성 요소 작성을 시작하는 방법에 있습니다.
// app/products/[slug]/page.tsx import { Suspense } from 'react'; import { cacheLife, cacheTag } from 'next/cache'; async function getProduct(slug: string) { 'use cache'; cacheLife('hours'); cacheTag(`product:${slug}`); return db.product.findUnique({ where: { slug } }); } async function ProductDetails({ slug }: { slug: string }) { const product = await getProduct(slug); return ( <section> <h1>{product.name}</h1> <p>{product.description}</p> </section> ); } async function LiveInventory({ slug }: { slug: string }) { const inventory = await db.inventory.findFirst({ where: { slug } }); return <p>{inventory.quantity} pezzi disponibili</p>; } export default async function ProductPage({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params; return ( <> <ProductDetails slug={slug} /> <Suspense fallback={<p>Controllo disponibilità...</p>}> <LiveInventory slug={slug} /> </Suspense> </> ); }
페이지가 모두 캐시되거나 모두 동적일 필요는 없습니다. 제품 시트는 빠르고 재사용이 가능합니다. 재고를 최신 상태로 유지할 수 있습니다. 사용자는 가장 느린 부분을 기다리지 않고 바로 무언가를 보게 됩니다.
use cache은 실행 가능한 문서입니다.
"use cache"에 대해 내가 좋아하는 점은 의도를 명시적으로 나타내도록 강요한다는 것입니다. 함수를 읽으면 누군가가 "이 데이터를 재사용할 수 있다"고 결정했다는 것을 즉시 이해할 수 있습니다.
fetch을 사용하지 않을 때 특히 유용합니다. 많은 앱이 Prisma, Drizzle, 내부 SDK, CMS 클라이언트 또는 서비스 기능에서 데이터를 읽습니다. 이러한 경우 fetch 옵션에만 기반한 기존 추론으로는 충분하지 않았습니다.
경험 법칙:
- 캐시아 함량은 상대적으로 안정적입니다.
- 세분화된 태그를 사용합니다.
- 동적 권한, 세션, 장바구니, 알림 및 거래 상태를 유지합니다.
Suspense안에 느린 부분을 넣으세요;- “성능을 향상시켰다”라고 말하기 전에 먼저 측정해 보세요.
모든 것을 버리지 않고 무효화
캐시는 정확하게 업데이트할 수 있는 경우에만 유용합니다. 여기서 cacheTag, revalidateTag 및 updateTag가 중요해집니다.
예:
'use server'; import { updateTag } from 'next/cache'; export async function updateProductName(productId: string, name: string) { await db.product.update({ where: { id: productId }, data: { name }, }); updateTag(`product:${productId}`); }
중요한 세부 사항은 태그입니다. product:${productId}은 정확한 경계를 알려줍니다. products은 거대한 양동이를 알려줍니다. 처음에는 거대한 양동이가 편안합니다. 몇 달 후에는 제목을 변경하기 위해 앱 절반을 무효화하는 이유가 됩니다.
안정적인 터보팩: 매일 듣는 부분
Next.js 16은 Turbopack을 개발 및 빌드의 중심으로 가져왔습니다. 가장 시적인 기능은 아니지만 작업하는 동안 느끼는 기능입니다. 더 일찍 시작되는 서버, 더 빠른 새로 고침, 강제로 커피를 마시는 듯한 느낌을 멈추는 빌드입니다.
그렇긴 하지만, 나는 눈을 감고 커스텀 플러그인으로 가득 찬 코드베이스를 마이그레이션하지 않을 것입니다. 나는 다음을 확인하겠다:
- 로컬 빌드;
- 비표준 수입;
- MDX, SVG 및 CSS;
- Webpack 플러그인이 남았습니다.
- 중요한 페이지;
- 빌드 시간의 차이.
새 프로젝트의 경우 기본값부터 시작합니다. 성숙한 경우에는 측정된 마이그레이션을 수행합니다.
React Compiler: 생각이 아닌 노이즈 제거
React Compiler 1.0은 안정적이며 Next.js 16은 reactCompiler을 통해 이를 지원합니다. 약속은 수동 메모를 많이 줄이는 것입니다. "안전을 위해" 사용되는 memo, useMemo, useCallback을 줄입니다.
// next.config.ts import type { NextConfig } from 'next'; const nextConfig: NextConfig = { reactCompiler: true, }; export default nextConfig;
나는 그것을 마법의 가루처럼 취급하지 않을 것입니다. 컴파일러는 코드가 React 규칙을 잘 따를 때 도움이 됩니다. 구성 요소에 이상한 부작용, 숨겨진 돌연변이 또는 잘못 사용된 후크가 있는 경우 이를 먼저 수정해야 합니다.
그것을 시도하는 건강한 방법:
eslint-plugin-react-hooks업데이트;- 실제 위반 사항을 수정합니다.
- 통제된 구역에서 활성화하십시오.
- 빌드 시간과 동작을 측정합니다.
- 더 이상 필요하지 않은 경우에만 수동 메모를 제거하십시오.
목표는 useMemo마다 삭제하는 것이 아닙니다. 렌더링이 두려워 예방 메모 작성을 중단하는 것이 목표입니다.
proxy.ts 및 네트워크 경계
이전 middleware.ts은(는) proxy.ts이 됩니다. 이름이 변경되었지만 의미가 있습니다. 해당 파일은 요청 경계에 있고 전통적인 백엔드 스타일의 일반 미들웨어가 아닙니다.
// proxy.ts import { NextRequest, NextResponse } from 'next/server'; export default function proxy(request: NextRequest) { const isLoggedIn = Boolean(request.cookies.get('session')); if (!isLoggedIn && request.nextUrl.pathname.startsWith('/dashboard')) { return NextResponse.redirect(new URL('/login', request.url)); } return NextResponse.next(); }
여기서의 규칙은 간단합니다. 작게 유지하는 것입니다. 리디렉션, 인증 라우팅, 헤더, 필수 재작성. 두 번째 백엔드처럼 느껴지기 시작하면 아마도 너무 많은 일을 하고 있는 것일 수 있습니다.
실제로 마이그레이션하는 방법
모든 기능을 한 번에 활성화하지는 않습니다. 나는 이렇게 할 것이다:
- Next, React 및 React DOM을 업데이트합니다.
- 공식 codemod를 시작합니다.
params,searchParams,cookies(),headers()및draftMode()에 대한 주요 변경 사항을 수정합니다.middleware.ts을(를)proxy.ts(으)로 마이그레이션합니다.- 빌드와 중요 페이지를 확인합니다.
- 현재 캐시가 마찰을 일으키는 섹션에서 캐시 구성 요소를 활성화합니다.
- 나는 태그와 무효화에 대한 규칙을 정의합니다.
- 저는 React Compiler를 별도로 사용해 봅니다.
- 측정 전과 후의 비교.
좋은 마이그레이션은 새로운 기능을 모두 사용하는 것이 아닙니다. 이것이 앱의 동작을 더 읽기 쉽게 만드는 것입니다.
생각의 방식이 바뀌는 것
Next.js 16의 가장 유용한 점은 의도의 이름을 더 잘 지정하도록 한다는 것입니다. 기능은 단지 "데이터베이스에서 제품을 가져오는 것"이 아닙니다. "제품을 구입하고 몇 시간 동안 캐시할 수 있으며 이 태그로 무효화합니다"입니다. 구성 요소는 단순한 "페이지 렌더링"이 아닙니다. "이것은 빠른 셸이고 이 작품은 개인적인 것이며 스트리밍으로 제공됩니다."
처음에는 더 많은 일처럼 보입니다. 그러면 그것은 평온한 형태가 됩니다. 성능 결정은 더 이상 기본값, 경험적 방법, 부족 기억의 조합에 숨겨져 있지 않습니다. 그것들은 코드에 있습니다.