Skip to content

1. App Router

The App Router (stable since Next.js 13.4) is the default and recommended router. The Pages Router is in maintenance mode.


1. File-System Routing

A folder becomes a route. A page.tsx inside it makes that route publicly accessible.

app/
├── layout.tsx          →  root layout (required)
├── page.tsx            →  /
├── about/
│   └── page.tsx        →  /about
├── blog/
│   ├── page.tsx        →  /blog
│   └── [slug]/
│       └── page.tsx    →  /blog/:slug
└── shop/
    └── [...categories]/
        └── page.tsx    →  /shop/a/b/c

Dynamic Segment Types

PatternMatchesparams
[id]/posts/1{ id: "1" }
[...slug]/shop/a/b{ slug: ["a","b"] }
[[...slug]]/shop and /shop/a/b{ slug: undefined | ["a","b"] }

2. Special Files

FilePurpose
page.tsxThe route UI — makes the route accessible
layout.tsxShared shell that wraps children; preserves state on navigation
template.tsxLike layout, but remounts on every navigation
loading.tsxSuspense fallback for the segment
error.tsxError boundary for the segment (must be a Client Component)
global-error.tsxCatches errors in the root layout
not-found.tsxUI for notFound() and unmatched routes
route.tsAPI endpoint (cannot coexist with page.tsx in the same folder)
default.tsxFallback for parallel routes — required in Next 16

Interview Point

layout vs template: a layout does not re-render when you navigate between its children, so scroll position and state survive. A template creates a new instance every time — use it when you need an enter animation or per-route state reset.


3. Root Layout

Required. It must render <html> and <body>.

tsx
// app/layout.tsx
export const metadata = {
  title: "DDocs",
  description: "Developer learning notes",
};

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

4. Server Components vs Client Components

The single most important App Router concept. Everything in app/ is a Server Component by default.

Server Component

tsx
// No directive needed — this is the default
async function Users() {
  const users = await db.user.findMany();  // direct DB access
  return <ul>{users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
}
  • Runs on the server only
  • Ships zero JavaScript to the browser
  • Can await directly — no useEffect, no loading state
  • Can read secrets, env vars, the filesystem, the database
  • Cannot use useState, useEffect, event handlers or browser APIs

Client Component

tsx
"use client";

import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
  • Prerendered on the server to HTML, then hydrated in the browser
  • Its JavaScript ships to the client
  • Can use hooks, event handlers, window, localStorage

Comparison

Server ComponentClient Component
Directivenone (default)"use client"
JS shippedNoneYes
HooksNoYes
Event handlersNoYes
Direct DB / secretsYesNever
async componentYesNo
Browser APIsNoYes

When You Need "use client"

  • useState, useEffect, useReducer, useContext
  • onClick, onChange, any event handler
  • window, document, localStorage
  • Browser-only libraries (most chart, map and animation libraries)

Interview Point

"use client" marks a boundary, not a file. Every component imported by a Client Component also becomes client-side. Push the directive as far down the tree as possible — one "use client" in your root layout ships your whole app to the browser.


5. Composition Pattern

A Client Component cannot import a Server Component. But it can render one passed as children.

tsx
// ❌ Wrong — ServerThing becomes a Client Component
"use client";
import ServerThing from "./ServerThing";

// ✅ Right — pass it in as a slot
// app/page.tsx (Server Component)
import ClientWrapper from "./ClientWrapper";
import ServerThing from "./ServerThing";

export default function Page() {
  return (
    <ClientWrapper>
      <ServerThing />   {/* stays a Server Component */}
    </ClientWrapper>
  );
}

This is the most common "how do I mix them" interview answer.


6. Async params and searchParams (Next.js 16 Breaking Change)

In Next.js 15+ params and searchParams are promises. You must await them.

tsx
// Next.js 16
export default async function Page({
  params,
  searchParams,
}: {
  params: Promise<{ slug: string }>;
  searchParams: Promise<{ page?: string }>;
}) {
  const { slug } = await params;
  const { page } = await searchParams;
  return <h1>{slug} — page {page ?? 1}</h1>;
}
tsx
// Next.js 14 and earlier — sync, now removed
export default function Page({ params }: { params: { slug: string } }) {
  return <h1>{params.slug}</h1>;
}

Same change applies to cookies(), headers() and draftMode() — all async now:

tsx
const cookieStore = await cookies();
const theme = cookieStore.get("theme")?.value;

Interview Point

The reason is Partial Prerendering. Making them promises lets Next.js prerender the static shell without knowing the request, and resolve the request-specific parts later.


7. Navigation

tsx
import Link from "next/link";

<Link href="/blog">Blog</Link>
<Link href={`/blog/${slug}`} prefetch={false}>Post</Link>

<Link> automatically prefetches routes when they enter the viewport. Next 16 added layout deduplication (a shared layout is downloaded once for 50 links, not 50 times) and incremental prefetching.

Programmatic

tsx
"use client";
import { useRouter } from "next/navigation";  // NOT next/router

const router = useRouter();
router.push("/dashboard");
router.replace("/login");
router.back();
router.refresh();   // re-fetch server data, keep client state

Server-Side Redirect

tsx
import { redirect, notFound } from "next/navigation";

export default async function Page({ params }) {
  const { id } = await params;
  const post = await getPost(id);

  if (!post) notFound();
  if (post.archived) redirect("/archive");

  return <Article post={post} />;
}

Client Navigation Hooks

tsx
"use client";
import { usePathname, useSearchParams, useParams } from "next/navigation";

const pathname = usePathname();          // "/blog/hello"
const searchParams = useSearchParams();  // read-only URLSearchParams
const params = useParams();              // { slug: "hello" }

Interview Point

next/router is the Pages Router. next/navigation is the App Router. Importing the wrong one is one of the most common migration bugs.


8. Loading UI and Streaming

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

Next.js automatically wraps page.tsx in a <Suspense> boundary with this fallback. The shared layout renders instantly while the page streams in.

Manual Suspense for Finer Control

tsx
export default function Page() {
  return (
    <>
      <Header />                          {/* instant */}
      <Suspense fallback={<Skeleton />}>
        <SlowFeed />                      {/* streams in */}
      </Suspense>
    </>
  );
}

Streaming means the server sends HTML in chunks as it becomes ready, instead of blocking the whole page on the slowest query.


9. Error Handling

tsx
// app/blog/error.tsx
"use client";   // error boundaries must be Client Components

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <div>
      <h2>Something went wrong</h2>
      <button onClick={() => reset()}>Try again</button>
    </div>
  );
}
  • error.tsx catches errors in that segment and below — but not in its own layout
  • global-error.tsx catches errors in the root layout; it must render its own <html> and <body>
  • not-found.tsx renders for notFound()

10. Route Groups and Private Folders

Route Group — (folder)

Organises files without adding a URL segment.

app/
├── (marketing)/
│   ├── layout.tsx      →  marketing layout
│   ├── page.tsx        →  /
│   └── about/page.tsx  →  /about
└── (shop)/
    ├── layout.tsx      →  different layout
    └── cart/page.tsx   →  /cart

Two completely different layouts, no (marketing) in the URL.

Private Folder — _folder

Opts a folder out of routing. app/_components/Button.tsx is never a route.


11. Parallel and Intercepting Routes

Parallel Routes — @folder

Render several pages in the same layout simultaneously.

app/dashboard/
├── layout.tsx
├── @analytics/page.tsx
├── @team/page.tsx
└── @analytics/default.tsx   ← required in Next 16
tsx
export default function Layout({ children, analytics, team }) {
  return (
    <>
      {children}
      {analytics}
      {team}
    </>
  );
}

Next 16 breaking change: every parallel slot now needs an explicit default.tsx, or the build fails. Return null or call notFound() for the old behaviour.

Intercepting Routes — (.)folder

Show a route in a modal when navigated to from within the app, but as a full page on direct visit or refresh. This is how Instagram-style photo modals work.

PatternMatches
(.)photosame level
(..)photoone level up
(...)photofrom the app root

12. Metadata and SEO

Static

tsx
export const metadata = {
  title: "Blog | DDocs",
  description: "Developer notes",
  openGraph: { title: "Blog", images: ["/og.png"] },
};

Dynamic

tsx
export async function generateMetadata({ params }) {
  const { slug } = await params;
  const post = await getPost(slug);

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: { images: [post.coverImage] },
  };
}

Title Template

tsx
// app/layout.tsx
export const metadata = {
  title: { default: "DDocs", template: "%s | DDocs" },
};

Child pages setting title: "Blog" render as Blog | DDocs.

File Conventions

Drop these in app/ and Next.js wires them up: favicon.ico, icon.png, apple-icon.png, opengraph-image.png, robots.ts, sitemap.ts.

ts
// app/sitemap.ts
export default async function sitemap() {
  const posts = await getPosts();
  return [
    { url: "https://ddocs.dev", lastModified: new Date() },
    ...posts.map((p) => ({ url: `https://ddocs.dev/blog/${p.slug}` })),
  ];
}

13. Built-in Optimisations

next/image

tsx
import Image from "next/image";

<Image src="/hero.png" alt="Hero" width={800} height={400} priority />
<Image src={url} alt="" fill sizes="(max-width: 768px) 100vw, 50vw" />

Gives you automatic WebP/AVIF conversion, responsive srcset, lazy loading, and a reserved layout box that prevents cumulative layout shift.

Next 16 defaults changed: minimumCacheTTL is now 4 hours (was 60s), qualities defaults to [75], redirects are capped at 3, and local IP optimisation is blocked unless you opt in. Use images.remotePatternsimages.domains is deprecated.

next/font

tsx
import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"], display: "swap" });

<body className={inter.className}>

Fonts are downloaded at build time and self-hosted — no request to Google, and zero layout shift.

next/script

tsx
<Script src="https://analytics.example.com/s.js" strategy="lazyOnload" />

Strategies: beforeInteractive, afterInteractive (default), lazyOnload, worker.


14. Proxy (formerly Middleware)

Next.js 16 renamed middleware.ts to proxy.ts and moved it to the Node.js runtime.

ts
// proxy.ts (project root)
import { NextResponse, type NextRequest } from "next/server";

export default function proxy(request: NextRequest) {
  const token = request.cookies.get("token");

  if (!token && request.nextUrl.pathname.startsWith("/dashboard")) {
    return NextResponse.redirect(new URL("/login", request.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: ["/dashboard/:path*", "/admin/:path*"],
};

Runs before the request is completed. Use it for auth redirects, A/B tests, geo-routing, bot blocking and header rewriting.

middleware.ts still works for Edge runtime cases but is deprecated and will be removed.

Interview Point

Keep proxy logic light — it runs on every matched request. Do a cheap cookie/JWT presence check there and the real database session lookup in the page or Route Handler.


15. Common Migration Bugs

SymptomCause
useRouter is not a functionImported from next/router instead of next/navigation
params.slug is undefinedForgot await params in Next 15/16
useState is not definedMissing "use client"
Whole app is client-side"use client" sits too high in the tree
Build fails on parallel routesMissing default.tsx (Next 16)
window is not definedBrowser API used in a Server Component
Hydration mismatchDate.now(), Math.random(), or localStorage during render

© 2025 DDocs · Dipak's Documentation Guide