
Nextjs V16
- 250 installs
- 63 repo stars
- Updated July 18, 2026
- bobmatnyc/claude-mpm-skills
Build or migrate Next.js v16 apps with App Router, RSC boundaries, caching, metadata, route handlers, and deployment-ready patterns for marketing sites and SaaS UIs.
About
Teaches Next.js v16 best practices for App Router projects: RSC vs client boundaries, async server components, caching and revalidation, metadata APIs, image/font optimization, and route handlers so Claude ships correct, performant pages.
- App Router and React Server Components
- Caching, revalidation, and fetch semantics
- Metadata, sitemaps, and SEO-oriented defaults
- Route handlers and server actions patterns
- Migration notes from prior Next releases
Nextjs V16 by the numbers
- 250 all-time installs (skills.sh)
- Ranked #803 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 1, 2026 (Skillselion catalog sync)
npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill nextjs-v16Add your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 250 |
|---|---|
| repo stars | ★ 63 |
| Last updated | July 18, 2026 |
| Repository | bobmatnyc/claude-mpm-skills ↗ |
What it does
Build or migrate Next.js v16 apps with App Router, RSC boundaries, caching, metadata, route handlers, and deployment-ready patterns for marketing sites and SaaS UIs.
Files
Next.js 16
- Async
params/cookies/headers; opt-in caching via"use cache"; Turbopack default.
Anti-patterns:
- ❌ Sync request APIs; ✅
awaitparams,cookies(), andheaders(). - ❌ Keep
middleware.ts; ✅ useproxy.tsandexport function proxy. - ❌
revalidateTag("posts"); ✅revalidateTag("posts", "max")or{ expire: ... }.
References: references/migration-checklist.md, references/cache-components.md, references/turbopack.md
{
"name": "nextjs-v16",
"version": "1.1.0",
"category": "toolchain",
"toolchain": "nextjs",
"framework": "nextjs",
"tags": [
"nextjs",
"nextjs-16",
"migration",
"async-request-apis",
"turbopack",
"cache-components",
"use-cache",
"proxy"
],
"entry_point_tokens": 186,
"full_tokens": 5289,
"requires": ["nextjs-core"],
"author": "claude-mpm-skills",
"updated": "2025-12-17",
"source_path": "toolchains/nextjs/nextjs-v16/SKILL.md",
"license": "MIT",
"repository": "https://github.com/bobmatnyc/claude-mpm-skills"
}
Cache Components in Next.js 16
Deep dive into the new "use cache" directive, cacheLife profiles, and invalidation patterns.
Architectural Shift
Next.js 16 moves from implicit caching to explicit opt-in caching:
| Next.js 15 | Next.js 16 |
|---|---|
| fetch() cached by default | fetch() dynamic by default |
Opt-out with no-store | Opt-in with "use cache" |
| Implicit data cache | Explicit Cache Components |
Enabling Cache Components
// next.config.ts
const nextConfig = {
cacheComponents: true, // Replaces experimental.ppr and dynamicIO
};
export default nextConfig;Basic Usage
Cached Component
"use cache"
export default async function ProductList() {
// This entire component is cached
const products = await db.products.findMany();
return (
<ul>
{products.map(product => (
<li key={product.id}>
<ProductCard product={product} />
</li>
))}
</ul>
);
}Cached Function
"use cache"
async function getExpensiveData(category: string) {
// Function result is cached based on arguments
const data = await heavyComputation(category);
return data;
}
// Different arguments = different cache entries
await getExpensiveData('electronics'); // Cache miss, computed
await getExpensiveData('electronics'); // Cache hit
await getExpensiveData('clothing'); // Cache miss, computedPartial Caching
import { Suspense } from 'react';
// Uncached wrapper
export default async function Page() {
const user = await getCurrentUser(); // Dynamic, not cached
return (
<div>
<Header user={user} />
<Suspense fallback={<Loading />}>
{/* Cached component */}
<CachedProductList />
</Suspense>
</div>
);
}
"use cache"
async function CachedProductList() {
const products = await getProducts();
return <ProductGrid products={products} />;
}cacheLife Profiles
Built-in Profiles
| Profile | Duration | Use Case |
|---|---|---|
'max' | Long-term | Static content, rarely changes |
'days' | 1-7 days | Semi-static content |
'hours' | 1-24 hours | Frequently updated content |
"use cache"
cacheLife('max');
export default async function StaticContent() {
const content = await getStaticContent();
return <div>{content}</div>;
}Custom Profiles
// next.config.ts
const nextConfig = {
cacheComponents: true,
experimental: {
cacheLife: {
// Custom profile definitions
'short': { expire: 300 }, // 5 minutes
'medium': { expire: 3600 }, // 1 hour
'long': { expire: 86400 }, // 1 day
'session': { expire: 1800 }, // 30 minutes
},
},
};Usage:
"use cache"
cacheLife('short');
async function FrequentlyUpdated() {
return await getLatestData();
}Cache Invalidation
revalidateTag (Updated API)
import { revalidateTag } from 'next/cache';
// Now requires cacheLife profile as second argument
revalidateTag('products', 'max');
revalidateTag('blog-posts', 'hours');
revalidateTag('user-data', { expire: 3600 });updateTag (New: Read-Your-Writes)
Immediate cache update within the same request:
'use server';
import { updateTag } from 'next/cache';
export async function updateUserProfile(userId: string, data: ProfileData) {
// Update database
await db.users.update({
where: { id: userId },
data,
});
// Immediately update cache - subsequent reads see new data
updateTag(`user-${userId}`);
// Any component reading this tag in the same request
// will see the updated data
return { success: true };
}refresh (New: Force Uncached)
Force a fresh, uncached data fetch:
import { refresh } from 'next/cache';
export async function forceRefresh() {
refresh(); // Clear all cached data for this request
}Tagging Strategies
Component-Level Tags
"use cache"
cacheTag('products', 'homepage');
export default async function FeaturedProducts() {
const products = await getFeaturedProducts();
return <ProductGrid products={products} />;
}
// Invalidate both tags
revalidateTag('products', 'max'); // All product caches
revalidateTag('homepage', 'max'); // All homepage cachesEntity-Based Tags
"use cache"
cacheTag(`product-${productId}`);
export default async function ProductDetail({ productId }: Props) {
const product = await getProduct(productId);
return <ProductView product={product} />;
}
// Invalidate specific product
export async function updateProduct(productId: string, data: ProductData) {
await db.products.update({ where: { id: productId }, data });
revalidateTag(`product-${productId}`, 'max');
}Hierarchical Tags
"use cache"
cacheTag('catalog', `category-${categoryId}`, `product-${productId}`);
// Invalidation hierarchy:
revalidateTag('catalog', 'max'); // All catalog data
revalidateTag(`category-${categoryId}`, 'max'); // Category and its products
revalidateTag(`product-${productId}`, 'max'); // Single productPatterns
Cached API Layer
// lib/cache.ts
"use cache"
cacheTag('api');
export async function cachedFetch<T>(
url: string,
tags: string[]
): Promise<T> {
tags.forEach(tag => cacheTag(tag));
const response = await fetch(url);
return response.json();
}
// Usage
"use cache"
export async function getProducts() {
return cachedFetch<Product[]>('/api/products', ['products']);
}Time-Based Segments
// Short-lived data (news, prices)
"use cache"
cacheLife('hours');
cacheTag('prices');
export async function LivePrices() {
return await getPrices();
}
// Long-lived data (product catalog)
"use cache"
cacheLife('days');
cacheTag('catalog');
export async function ProductCatalog() {
return await getCatalog();
}User-Specific Caching
// Cache per user, but still cached
"use cache"
export async function UserDashboard({ userId }: { userId: string }) {
cacheTag(`user-${userId}`, 'dashboard');
cacheLife('hours');
const data = await getUserDashboardData(userId);
return <Dashboard data={data} />;
}Debugging
Cache Headers
Check response headers for cache status:
x-nextjs-cache: HIT- Served from cachex-nextjs-cache: MISS- Cache miss, computedx-nextjs-cache: STALE- Stale cache, revalidating
Development Mode
Development mode disables caching by default. Enable caching for testing:
// next.config.ts
const nextConfig = {
cacheComponents: true,
experimental: {
// Enable cache in dev for testing
cacheHandlerPath: './cache-handler.js',
},
};Migration from fetch() cache
Before (Next.js 15)
// Implicit caching
const data = await fetch(url); // Cached
// Opt-out
const fresh = await fetch(url, { cache: 'no-store' });
// Time-based
const timed = await fetch(url, { next: { revalidate: 3600 } });After (Next.js 16)
// Default: no cache
const data = await fetch(url); // Not cached
// Explicit cache with "use cache"
"use cache"
async function getCachedData() {
cacheLife('hours');
return await fetch(url).then(r => r.json());
}Best Practices
1. Cache at boundaries - Cache at component level, not individual fetches 2. Use meaningful tags - Entity-based tags enable surgical invalidation 3. Profile appropriately - Match cacheLife to data volatility 4. Combine with Suspense - Stream uncached shells with cached content 5. Invalidate precisely - Avoid broad invalidation when specific tags work 6. Monitor hit rates - Track cache effectiveness in production
Next.js 16 Migration Checklist
Step-by-step migration process from Next.js 15 to 16.
Pre-Migration Checklist
Environment Requirements
- [ ] Node.js 20.9.0+ installed
- [ ] TypeScript 5.1.0+ in dependencies
- [ ] Target browsers support Chrome 111+, Safari 16.4+
- [ ] CI/CD environments updated to Node.js 20+
Dependency Audit
# Check current versions
npm ls next react react-dom typescript
# Update to Next.js 16
npm install next@16 react@latest react-dom@latestBackup
# Create migration branch
git checkout -b nextjs-16-migration
# Commit current state
git add -A && git commit -m "Pre-migration snapshot"Migration Steps
Step 1: Run Automated Codemod
npx @next/codemod@canary upgrade latestThis handles:
- Async params/searchParams conversion
- cookies()/headers() async calls
- Import updates
- Basic middleware rename
Step 2: Async Request APIs
The codemod converts most cases, but manually verify:
// Before (Next.js 15)
export default function Page({ params }: { params: { id: string } }) {
const { id } = params;
return <div>{id}</div>;
}
// After (Next.js 16)
export default async function Page({
params
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params;
return <div>{id}</div>;
}Manual review needed for:
// Helper functions receiving params
function processParams(params: { id: string }) { /* ... */ }
// Must be updated to:
async function processParams(params: Promise<{ id: string }>) {
const { id } = await params;
// ...
}
// Conditional access
if (someCondition) {
const cookie = await cookies(); // Each access must be awaited
}Step 3: Middleware to Proxy
npx @next/codemod@latest middleware-to-proxyRename manually if codemod misses:
mv middleware.ts proxy.tsUpdate the export:
// Before
export function middleware(request: NextRequest) { }
// After
export function proxy(request: NextRequest) { }Step 4: Update Caching Calls
Search for revalidateTag and update:
// Before
revalidateTag('posts');
// After - requires cacheLife profile
revalidateTag('posts', 'max');
// Or with custom duration
revalidateTag('posts', { expire: 3600 });Step 5: Remove Deprecated Features
Search and remove:
# Find AMP usage
grep -r "useAmp\|amp: true" --include="*.tsx" --include="*.ts"
# Find runtime configs
grep -r "serverRuntimeConfig\|publicRuntimeConfig" --include="*.ts"
# Find next lint usage
grep -r "next lint" package.json .github/Replace:
- AMP → Remove or use external AMP generator
- Runtime configs → Environment variables
next lint→ Direct ESLint/Biome commands
Step 6: Parallel Routes Default Files
Add default.js to all parallel routes:
// app/@modal/default.tsx
export default function Default() {
return null;
}Step 7: Image Configuration
If relying on old defaults, explicitly set:
// next.config.ts
const nextConfig = {
images: {
minimumCacheTTL: 60, // Restore old default if needed
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
qualities: [75], // Or restore range if needed
},
};Step 8: Test Build
# Standard build (uses Turbopack)
npm run build
# If build fails with custom webpack, try:
next build --webpackStep 9: Run Type Generation
npx next typegenStep 10: Test Application
npm run dev
npm run build
npm start
# Run test suite
npm testCommon Issues
Issue: "params is not a Promise"
Cause: Component not converted to async
Fix:
// Add async and await
export default async function Page({ params }) {
const { id } = await params;
}Issue: "cookies() is not a function"
Cause: Missing await on cookies()
Fix:
const cookieStore = await cookies();Issue: Turbopack build failures
Cause: Incompatible webpack loaders
Fix:
# Temporary: use webpack
next build --webpack
# Permanent: migrate loaders
# @svgr/webpack → @svgr/rollup or inline SVGs
# custom loaders → Turbopack equivalentsIssue: "middleware" export not found
Cause: File not renamed
Fix:
mv middleware.ts proxy.ts
# Update export name to "proxy"Anti-patterns
- ❌ Merge the upgrade without updating Node/TypeScript in CI; ✅ upgrade CI runtime first.
- ❌ Leave sync request API usage; ✅
awaitparams,searchParams,cookies(), andheaders(). - ❌ Keep
middleware.ts; ✅ move toproxy.tsand exportproxy. - ❌ Keep old
revalidateTag("tag"); ✅ pass a profile ("max","hours") or{ expire: ... }. - ❌ Treat Turbopack failures as “later”; ✅ validate
next buildearly and use--webpackonly as a temporary bridge.
Issue: revalidateTag type error
Cause: Missing second argument
Fix:
revalidateTag('tag', 'max');Post-Migration Verification
- [ ] All pages render correctly
- [ ] Server Actions work
- [ ] Authentication flows complete
- [ ] API routes respond correctly
- [ ] Images load and optimize
- [ ] Build completes without errors
- [ ] Tests pass
- [ ] No console errors in browser
- [ ] Performance metrics acceptable
Rollback Plan
If critical issues found:
# Revert to pre-migration
git checkout main
npm install
# Or pin to Next.js 15
npm install next@15CI/CD Updates
Update workflows:
# .github/workflows/ci.yml
- uses: actions/setup-node@v4
with:
node-version: '20' # Was '18'
- run: npm ci
- run: npm run build
- run: npm test
# Remove next lint if used
# - run: npm run lint
# Replace with:
- run: npx eslint .Turbopack in Next.js 16
Configuration, loader migration, and performance optimization for the new default bundler.
Overview
Turbopack is now the default bundler in Next.js 16, replacing Webpack for both development and production builds.
Performance Gains
| Metric | Webpack | Turbopack | Improvement |
|---|---|---|---|
| Cold start | ~15s | ~3s | 5x faster |
| Fast Refresh | ~500ms | ~50ms | 10x faster |
| Production build | ~60s | ~20s | 3x faster |
Configuration
Basic Setup
// next.config.ts
const nextConfig = {
// Turbopack options moved from experimental to top-level
turbopack: {
// Alias modules
resolveAlias: {
'old-package': 'new-package',
},
// Resolve extensions
resolveExtensions: ['.tsx', '.ts', '.jsx', '.js'],
},
};
export default nextConfig;Experimental Features
const nextConfig = {
turbopack: {
resolveAlias: {},
},
experimental: {
// Persist build artifacts between restarts
turbopackFileSystemCacheForDev: true,
// Enable persistent caching in production
turbopackPersistentCache: true,
},
};Loader Migration
SVG Handling
Webpack:
// next.config.js (Webpack)
module.exports = {
webpack: (config) => {
config.module.rules.push({
test: /\.svg$/,
use: ['@svgr/webpack'],
});
return config;
},
};Turbopack alternatives:
Option 1: Inline SVG Component
// components/icons.tsx
export function HomeIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 24 24" {...props}>
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" />
</svg>
);
}Option 2: @svgr/rollup with manual setup
npm install @svgr/rollupOption 3: next/image for static SVGs
import Image from 'next/image';
import logo from '@/assets/logo.svg';
export function Logo() {
return <Image src={logo} alt="Logo" />;
}CSS Modules
Works identically - no migration needed:
// components/button.tsx
import styles from './button.module.css';
export function Button({ children }) {
return <button className={styles.button}>{children}</button>;
}Sass/SCSS
npm install sass// next.config.ts
const nextConfig = {
sassOptions: {
includePaths: ['./styles'],
prependData: `@import "variables.scss";`,
},
};PostCSS
Works identically - postcss.config.js is respected:
// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};MDX
npm install @next/mdx @mdx-js/loader @mdx-js/react// next.config.ts
import createMDX from '@next/mdx';
const withMDX = createMDX({
options: {
remarkPlugins: [],
rehypePlugins: [],
},
});
export default withMDX({
pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
});Webpack Fallback
When to Use Webpack
- Custom loaders without Turbopack equivalents
- Complex webpack plugins
- Build-time code generation
- Legacy configurations
Opt-out Commands
# Development
next dev --webpack
# Production build
next build --webpackConditional Configuration
// next.config.ts
const nextConfig = {
webpack: (config, { isServer }) => {
// Only applied when using --webpack flag
config.module.rules.push({
test: /\.svg$/,
use: ['@svgr/webpack'],
});
return config;
},
turbopack: {
// Turbopack-specific config
},
};Performance Optimization
Development
const nextConfig = {
experimental: {
// Cache compilation results
turbopackFileSystemCacheForDev: true,
},
};Production
const nextConfig = {
experimental: {
// Incremental builds
turbopackPersistentCache: true,
},
// Output optimization
output: 'standalone', // Minimal production output
// Compression
compress: true,
};Monitoring Build Performance
# Analyze build
ANALYZE=true next build
# Verbose timing
next build --profileTroubleshooting
Issue: Build Fails with Custom Loader
Symptom:
Error: Could not find loader for .xyz filesSolutions:
1. Check if Turbopack supports the file type natively 2. Find Turbopack-compatible alternative 3. Use next build --webpack temporarily 4. Convert to native solution
Issue: Module Not Found
Symptom:
Error: Cannot find module 'xyz'Fix: Add to resolveAlias:
turbopack: {
resolveAlias: {
'xyz': './node_modules/xyz/dist/index.js',
},
},Issue: CSS Not Loading
Symptom: Styles missing in development
Fix: Ensure CSS imports are at component level:
// ✅ Correct
import './styles.css';
// ❌ Avoid dynamic imports for CSS
const styles = await import('./styles.css');Issue: Slow Initial Build
Fix: Enable filesystem cache:
experimental: {
turbopackFileSystemCacheForDev: true,
},Issue: Memory Issues
Fix: Increase Node.js memory:
NODE_OPTIONS="--max-old-space-size=8192" next buildMigration Checklist
Pre-Migration
- [ ] List all custom webpack loaders
- [ ] Identify webpack plugins in use
- [ ] Document build-time transformations
- [ ] Note any postcss/sass customizations
Migration Steps
1. [ ] Remove or migrate SVG loaders 2. [ ] Update MDX configuration 3. [ ] Test CSS/SCSS compilation 4. [ ] Verify PostCSS plugins work 5. [ ] Check all asset imports 6. [ ] Test development server 7. [ ] Run production build 8. [ ] Compare bundle sizes 9. [ ] Verify all features work
Post-Migration
- [ ] Enable persistent caching
- [ ] Remove webpack-specific code (if not needed)
- [ ] Update CI/CD to remove --webpack flags
- [ ] Monitor build times
- [ ] Document any workarounds
Native Loaders
Turbopack natively supports:
| File Type | Support |
|---|---|
| JavaScript/TypeScript | ✅ Full |
| JSX/TSX | ✅ Full |
| CSS | ✅ Full |
| CSS Modules | ✅ Full |
| Sass/SCSS | ✅ Full |
| JSON | ✅ Full |
| Images (png, jpg, gif, webp, svg) | ✅ Full |
| Fonts (woff, woff2, ttf, otf) | ✅ Full |
| MDX | ✅ Via @next/mdx |
Best Practices
1. Start fresh - Try without webpack config first 2. Use native solutions - Prefer Turbopack-native features 3. Enable caching - Significant build time reduction 4. Monitor metrics - Compare build times and bundle sizes 5. Keep webpack fallback - For edge cases only 6. Test thoroughly - Especially CSS and asset handling 7. Update dependencies - Ensure compatibility with Turbopack