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.remotePatterns — images.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