Next.js Cheatsheet
By Ankita Kulkarni
Routing
Use [slug] for single, predictable URL segments and Use [...slug] for multiple/nested segments
// Dynamic Routes:
// matches: /blog/my-post
`app/blog/[slug]/page.tsx`
// Catch-all Routes:
// matches: /blog/tech/nextjs/routing
`app/blog/[...slug]/page.tsx`
Server Components vs Actions
Server Components never re-render on client and Server Actions get exposed as API endpoints
Server Components:
Useful for GET requests
Server Actions:
Useful for POST/PUT/DELETE requests
Image Optimization
Always use next/image instead of img, Provide width and height to prevent layout shift and Use priority for above-the-fold images
<Image
width={500}
height={300}
sizes="(max-width: 768px) 100vw, (max-width: 900px) 50vw, 33vw"
priority={true}
/>Security Best Practices
Avoid NEXT_PUBLIC_ for sensitive vars, use "server-only" to prevent server code exposure, and add authentication to exposed Server Action endpoints
import "server-only"; // Prevent server code exposureData Sanitization
Use Zod for form validation and sanitization, define strict schemas for your data, and validate both client and server side
const schema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().min(18)
});Server-side Redirects
Use redirect to navigate to a new path and place outside try-catch blocks as redirect throws an error so the Router can route.
import { redirect } from "next/navigation";
try {
redirect('/new-path');
} catch (error) {
console.error('Error redirecting:', error);
}Prevent Accidental Leakage of Sensitive Data
Use "removeConsole" to exclude console logs in production while keeping specific console methods like errors
compiler: {
removeConsole: {
exclude: ["error"]
}
}Cache Components
Next.js 16 makes components dynamic by default. Use "use cache" directive to opt-in to caching for pages, components, or functions.
"use cache";
export async function getData() {
// This function will be cached
const data = await fetch(url);
return data.json();
}"use cache" - Cache Components - Private
Allows you to use runtime APIs like cookies, headers, or search params and cache them.
"use cache";
export async function getRecommendations(productId: string) {
'use cache: private';
const sessionId = cookies().get('sessionId')?.value;
return getPersonalizedRecommendations(sessionId);
}"use cache" - Cache Components - Remote
Enables caching of remote data when you use dynamic data.
async function getProductPrice(productId: string) {
'use cache: remote';
cacheTag(`product-price-${productId}`);
cacheLife({ expire: 3600 }); // 1 hour
// This database query is cached and shared across all users
return db.products.getPrice(productId);
}Improved Caching APIs
revalidateTag() now requires a cacheLife profile as the second argument for stale-while-revalidate behavior
import { revalidateTag } from 'next/cache';
// Use built-in cacheLife profile
revalidateTag('blog-posts', 'max');
// Or custom revalidation time
revalidateTag('products', {
revalidate: 3600
});Async APIs
In Next.js, params, searchParams, cookies(), headers(), must now be awaited
import { cookies, headers } from 'next/headers';
export async function Page({
params,
searchParams
}) {
const { id } = await params;
const { query } = await searchParams;
const cookieStore = await cookies();
const headersList = await headers();
}Turbopack
Turbopack is now the default bundler for new Next.js projects, offering faster builds and improved Fast Refresh
// Turbopack is now default
// No configuration needed!
// Faster production builds
// Improved Fast Refresh
// Better performanceMove Repeated Components to the Layout
Layout.tsx is a great place to put repeated components that are used on every page. This way, the components will be rendered once and then cached for the entire application.
export default function Layout({ children }) {
const lessons = getLessons();
return (
<div>
<LessonsSideNav lessons={lessons} />
{children}
</div>
);
}Server Actions Usage
Use form action for traditional form submissions or await serverAction() for programmatic calls with additional logic
<form action={serverAction}>
<button type="submit">Submit</button>
</form>
// Function call method:
await serverAction();Instant Updates
Use 'use server' directive to mark server-only functions, implement update logic, and call revalidatePath to ensure consistent data
'use server';
async function updateCart() {
// Update cart logic
revalidatePath('/');
}
// Ensures consistent dataafter()
Run server-side code in the background. Use after() to execute expensive operations without blocking the response
import { after } from 'next/server';
after(async () => {
try {
await Promise.all([
awardXP(),
checkLevelUp()
]);
} catch (error) {
console.error(error);
}
});
// XP gets awarded in the backgroundTag the Cache for Better Performance
Tag your cache so you can delete it later. Use tags to selectively invalidate specific cached data otherwise it will be expensive to delete the entire cache.
import { cache } from 'next/cache';
const cachedXPInfo = cache(
async () => getXPInfo(studentId),
['xp-info', studentId],
{
tags: [`xp-info:student:${studentId}`]
}
);Clear the Tagged Cache
Delete tagged cache selectively. Use revalidateTag() to bust specific cache tags without clearing the entire page. This is more efficient than clearing the entire cache.
import { revalidateTag } from 'next/cache';
if (studentId) {
revalidateTag(`course-progress:student:${studentId}`);
revalidateTag(`xp-info:student:${studentId}`);
revalidateTag(`streaks:student:${studentId}`);
}
// Only bust tagged cache, not the entire pageuseOptimistic()
Show optimistic state of completion while expensive work is done on the server for better UX
const initialState = {
isCompleted: false,
isProcessing: false
};
const [optimisticState, addOptimistic] = useOptimistic(
initialState,
(state, optimisticUpdate) => ({
...state,
...optimisticUpdate
})
);