Next.js Cheatsheet

By Ankita Kulkarni

Routing

Use [slug] for single, predictable URL segments and Use [...slug] for multiple/nested segments

Dynamic Routes
 // 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 Component
 <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

Security
 import "server-only"; // Prevent server code exposure

Data Sanitization

Use Zod for form validation and sanitization, define strict schemas for your data, and validate both client and server side

Zod Schema
 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.

Redirects
 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

Console Configuration
 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 Directive
 "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 Directive
 "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.

Use Cache Directive
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

Revalidate Tag
 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

Async APIs
 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 Bundler
 // Turbopack is now default
// No configuration needed!

// Faster production builds
// Improved Fast Refresh
// Better performance

Move 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.

Move Repeated Components to the Layout
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

Server Actions
 <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

Instant Updates
 'use server';

 async function updateCart() {
   // Update cart logic
   revalidatePath('/');
 }
 // Ensures consistent data

after()

Run server-side code in the background. Use after() to execute expensive operations without blocking the response

after()
 import { after } from 'next/server';

after(async () => {
  try {
    await Promise.all([
      awardXP(),
      checkLevelUp()
    ]);
  } catch (error) {
    console.error(error);
  }
});
// XP gets awarded in the background

Tag 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.

Tag the 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.

Clear the 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 page

useOptimistic()

Show optimistic state of completion while expensive work is done on the server for better UX

useOptimistic()
 const initialState = {
  isCompleted: false,
  isProcessing: false
};

const [optimisticState, addOptimistic] = useOptimistic(
  initialState,
  (state, optimisticUpdate) => ({
    ...state,
    ...optimisticUpdate
  })
);

Ready to Dive Deeper into the Next.js Hot Tips Course?

Sign up for the Modern Full Stack Next.js Course here