All topics
Backend · Learning hub

Next.js notes for developers

Master Next.js with a curated set of 5 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore Backend notes
Next.js

Performance & Optimization

Next.js Performance & Optimization Image Optimization import Image from 'next/image'; // Optimized images with automatic sizing export default function Page() {

Next.js Performance & Optimization

Image Optimization

import Image from 'next/image';

// Optimized images with automatic sizing
export default function Page() {
  return (
    <div>
      {/* Local image */}
      <Image
        src="/hero.jpg"
        width={800}
        height={600}
        alt="Hero"
        priority  // Load eagerly for LCP
      />
      
      {/* Remote image */}
      <Image
        src="https://example.com/image.jpg"
        width={400}
        height={300}
        alt="Remote"
      />
      
      {/* Fill parent */}
      <div style={{ position: 'relative', width: '100%', height: '400px' }}>
        <Image
          src="/bg.jpg"
          fill
          style={{ objectFit: 'cover' }}
          alt="Background"
        />
      </div>
      
      {/* Placeholder */}
      <Image
        src="/photo.jpg"
        width={600}
        height={400}
        placeholder="blur"
        blurDataURL="data:image/png;base64,..."  
        alt="Photo"
      />
    </div>
  );
}

Font Optimization

// app/layout.tsx
import { Inter, Roboto_Mono } from 'next/font/google';
import localFont from 'next/font/local';

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
});

const robotoMono = Roboto_Mono({
  subsets: ['latin'],
  weight: ['400', '700'],
});

const customFont = localFont({
  src: './fonts/MyFont.woff2',
  display: 'swap',
});

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className={inter.className}>
      <body>{children}</body>
    </html>
  );
}

Metadata & SEO

// Static metadata
import type { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'My Page',
  description: 'Page description',
  keywords: ['next.js', 'react'],
  openGraph: {
    title: 'My Page',
    description: 'Page description',
    images: ['/og-image.jpg'],
  },
};

// Dynamic metadata
export async function generateMetadata(
  { params }: { params: { id: string } }
): Promise<Metadata> {
  const post = await fetchPost(params.id);
  
  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      images: [post.image],
    },
  };
}
Next.js

Server Components & App Router

Next.js Server Components & App Router The App Router (introduced in Next.js 13) brings a new paradigm with React Server Components, streaming, and simplified d

Next.js Server Components & App Router

The App Router (introduced in Next.js 13) brings a new paradigm with React Server Components, streaming, and simplified data fetching.

File-based Routing

// App Router structure
app/
  layout.tsx              → Root layout (required)
  page.tsx                → / (home page)
  about/page.tsx          → /about
  blog/
    page.tsx              → /blog
    [slug]/page.tsx       → /blog/:slug
  dashboard/
    layout.tsx            → Layout for /dashboard/*
    page.tsx              → /dashboard
    settings/page.tsx     → /dashboard/settings
  api/
    users/route.ts        → /api/users (API route)

// app/page.tsx (home page)
export default function HomePage() {
  return (
    <div>
      <h1>Welcome to Next.js</h1>
    </div>
  );
}

// app/blog/[slug]/page.tsx (dynamic route)
export default function BlogPost({ params }: { params: { slug: string } }) {
  return (
    <article>
      <h1>Post: {params.slug}</h1>
    </article>
  );
}

Server Components vs Client Components

Server Components (default) render on the server, reducing JavaScript sent to client. Client Components use "use client" directive and can use hooks, events, and browser APIs.

// Server Component (default) - no "use client"
export default async function ServerComponent() {
  // Can access backend directly
  const data = await fetch('https://api.example.com/data');
  const posts = await data.json();
  
  // Can import server-only code
  import { db } from '@/lib/database';
  const users = await db.users.findMany();
  
  return (
    <div>
      <h1>Server Component</h1>
      {posts.map(post => (
        <div key={post.id}>{post.title}</div>
      ))}
    </div>
  );
}

// Client Component - needs "use client"
'use client';

import { useState, useEffect } from 'react';

export default function ClientComponent() {
  const [count, setCount] = useState(0);
  
  // Can use hooks, event handlers, browser APIs
  useEffect(() => {
    console.log('Count changed:', count);
  }, [count]);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

// Composition: Server Component using Client Component
import Counter from './Counter';  // Client component

export default async function Page() {
  const data = await fetchData();  // Server-side data fetch
  
  return (
    <div>
      <h1>{data.title}</h1>
      <Counter />  {/* Client component for interactivity */}
    </div>
  );
}

Layouts

// app/layout.tsx (root layout - required)
import type { Metadata } from 'next';
import './globals.css';

export const metadata: Metadata = {
  title: 'My Next.js App',
  description: 'Built with Next.js',
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <header>
          <nav>{/* Global nav */}</nav>
        </header>
        <main>{children}</main>
        <footer>{/* Global footer */}</footer>
      </body>
    </html>
  );
}

// app/dashboard/layout.tsx (nested layout)
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="dashboard-layout">
      <aside>
        <nav>{/* Dashboard nav */}</nav>
      </aside>
      <div className="content">
        {children}
      </div>
    </div>
  );
}

Loading UI & Streaming

// app/dashboard/loading.tsx
export default function Loading() {
  return <div>Loading dashboard...</div>;
}

// app/error.tsx
'use client';

export default function Error({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <p>{error.message}</p>
      <button onClick={reset}>Try again</button>
    </div>
  );
}

// Streaming with Suspense
import { Suspense } from 'react';

export default function Page() {
  return (
    <div>
      <h1>Dashboard</h1>
      <Suspense fallback={<p>Loading stats...</p>}>
        <Stats />  {/* Async component */}
      </Suspense>
      <Suspense fallback={<p>Loading posts...</p>}>
        <RecentPosts />
      </Suspense>
    </div>
  );
}

async function Stats() {
  const stats = await fetchStats();
  return <div>{stats.total} users</div>;
}
Next.js

Data Fetching Patterns

Next.js Data Fetching Patterns Next.js provides multiple patterns for fetching data: Server Components, Route Handlers, Server Actions, and client-side fetching

Next.js Data Fetching Patterns

Next.js provides multiple patterns for fetching data: Server Components, Route Handlers, Server Actions, and client-side fetching.

Server Component Data Fetching

// app/posts/page.tsx
export default async function PostsPage() {
  // Fetch directly in Server Component
  const response = await fetch('https://api.example.com/posts', {
    cache: 'force-cache'  // Default: cache
  });
  const posts = await response.json();
  
  return (
    <div>
      {posts.map((post: any) => (
        <div key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
        </div>
      ))}
    </div>
  );
}

// With database
import { db } from '@/lib/database';

export default async function UsersPage() {
  const users = await db.user.findMany();
  
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Caching & Revalidation

// Cache strategies

// 1. Force cache (static generation)
fetch(url, { cache: 'force-cache' });

// 2. No cache (always fresh)
fetch(url, { cache: 'no-store' });

// 3. Revalidate every N seconds
fetch(url, { next: { revalidate: 60 } });  // ISR

// 4. Tag-based revalidation
fetch(url, { next: { tags: ['posts'] } });

// Revalidate by tag
import { revalidateTag } from 'next/cache';
await revalidateTag('posts');

// Revalidate by path
import { revalidatePath } from 'next/cache';
await revalidatePath('/blog');

// Route segment config
export const revalidate = 60;  // Revalidate every 60 seconds
export const dynamic = 'force-dynamic';  // Always dynamic
export const dynamic = 'force-static';   // Always static
export const runtime = 'edge';  // Edge runtime

Server Actions

// app/actions.ts
'use server';

import { revalidatePath } from 'next/cache';
import { db } from '@/lib/database';

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string;
  const content = formData.get('content') as string;
  
  // Validate
  if (!title || !content) {
    return { error: 'Title and content required' };
  }
  
  // Save to database
  await db.post.create({
    data: { title, content }
  });
  
  // Revalidate
  revalidatePath('/posts');
  
  return { success: true };
}

export async function deletePost(id: string) {
  await db.post.delete({ where: { id } });
  revalidatePath('/posts');
}

// app/posts/new/page.tsx
import { createPost } from '@/app/actions';

export default function NewPost() {
  return (
    <form action={createPost}>
      <input name="title" required />
      <textarea name="content" required />
      <button type="submit">Create Post</button>
    </form>
  );
}

// With Client Component for enhanced UX
'use client';

import { useFormStatus, useFormState } from 'react-dom';
import { createPost } from '@/app/actions';

export default function NewPostForm() {
  const [state, formAction] = useFormState(createPost, null);
  
  return (
    <form action={formAction}>
      <input name="title" />
      <textarea name="content" />
      {state?.error && <p className="error">{state.error}</p>}
      <SubmitButton />
    </form>
  );
}

function SubmitButton() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Creating...' : 'Create Post'}
    </button>
  );
}

Parallel Data Fetching

// Sequential (slower)
export default async function Page() {
  const user = await fetchUser();
  const posts = await fetchPosts(user.id);  // Waits for user
  
  return <div>{/* render */}</div>;
}

// Parallel (faster)
export default async function Page() {
  const [user, posts] = await Promise.all([
    fetchUser(),
    fetchPosts()
  ]);
  
  return <div>{/* render */}</div>;
}

// Streaming with Suspense
import { Suspense } from 'react';

export default function Page() {
  return (
    <div>
      <Suspense fallback={<UserSkeleton />}>
        <UserInfo />
      </Suspense>
      <Suspense fallback={<PostsSkeleton />}>
        <PostsList />
      </Suspense>
    </div>
  );
}
Next.js

API Routes & Middleware

Next.js API Routes & Middleware Next.js allows you to build API endpoints as part of your application using Route Handlers in the App Router. Route Handlers //

Next.js API Routes & Middleware

Next.js allows you to build API endpoints as part of your application using Route Handlers in the App Router.

Route Handlers

// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/database';

// GET /api/users
export async function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams;
  const query = searchParams.get('query');
  
  const users = await db.user.findMany({
    where: query ? { name: { contains: query } } : undefined
  });
  
  return NextResponse.json(users);
}

// POST /api/users
export async function POST(request: NextRequest) {
  const body = await request.json();
  
  const user = await db.user.create({
    data: body
  });
  
  return NextResponse.json(user, { status: 201 });
}

// Dynamic route: app/api/users/[id]/route.ts
export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const user = await db.user.findUnique({
    where: { id: params.id }
  });
  
  if (!user) {
    return NextResponse.json(
      { error: 'User not found' },
      { status: 404 }
    );
  }
  
  return NextResponse.json(user);
}

export async function PATCH(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const body = await request.json();
  
  const user = await db.user.update({
    where: { id: params.id },
    data: body
  });
  
  return NextResponse.json(user);
}

export async function DELETE(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  await db.user.delete({
    where: { id: params.id }
  });
  
  return NextResponse.json({ success: true });
}

Middleware

// middleware.ts (root of project)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // Check authentication
  const token = request.cookies.get('token')?.value;
  
  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
  
  // Add custom headers
  const response = NextResponse.next();
  response.headers.set('x-custom-header', 'value');
  
  return response;
}

// Configure which paths middleware runs on
export const config = {
  matcher: ['/dashboard/:path*', '/api/:path*']
};
Next.js

Interview Questions

Next.js Interview Questions Fundamentals 1. What is Next.js? Next.js is a React framework for building full-stack web applications. It provides features like fi

Next.js Interview Questions

Fundamentals

1. What is Next.js?

Next.js is a React framework for building full-stack web applications. It provides features like file-based routing, Server Components, API routes, optimized images, and flexible rendering strategies (SSR, SSG, ISR).

2. What is the App Router?

App Router (Next.js 13+) is a new routing system built on React Server Components. It uses the app/ directory with new conventions for layouts, loading states, and error handling.

3. Server Components vs Client Components?

Server Components (default) render on server, reducing client bundle. Client Components ("use client") run in browser, can use hooks and event handlers.

4. What is ISR (Incremental Static Regeneration)?

ISR allows you to update static pages after deployment without rebuilding the entire site. Use revalidate option to set how often to regenerate pages.

5. What are Server Actions?

Server Actions are async functions that run on the server. They can be called from Server or Client Components to handle form submissions and data mutations.

Rendering & Performance

6. What is Streaming?

Streaming allows you to progressively render UI from server. Use Suspense boundaries to stream parts of the page as they become ready.

7. What is Partial Prerendering?

Experimental feature that combines static and dynamic rendering in the same route. Static shell with dynamic content holes.

8. How does Next.js optimize images?

Next.js Image component automatically optimizes images: lazy loading, responsive sizes, modern formats (WebP/AVIF), prevents layout shift.

9. What is the difference between Link and router.push?

Link is for declarative navigation with automatic prefetching. router.push is for programmatic navigation. Both provide client-side navigation.

10. What is middleware in Next.js?

Middleware runs before request is completed. Used for authentication, redirects, rewrites, setting headers. Runs on Edge runtime.

Keep your Next.js knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever