Follow AiTechWorlds on LinkedIn for professional AI content!Follow Now →
16 minLesson 13 of 33
Next.js App Router

Next.js 15 Architecture Overview

Next.js 15 Architecture Overview

Next.js 15 is not just a React framework with routing — it's a full rendering pipeline that lets you choose exactly how each part of your application is built, cached, and delivered. Understanding the architecture is what separates developers who fight against the framework from developers who use it as intended.

The Three Rendering Models

Next.js lets you use three rendering strategies in the same application, per route:

Static Generation (SSG) — Pages rendered to HTML at build time. The HTML is stored on a CDN and served instantly to every user. Best for content that doesn't change often: blog posts, documentation, marketing pages.

Server-Side Rendering (SSR) — Pages rendered to HTML on every request. The server fetches fresh data, renders HTML, and sends it to the user. Best for content that changes frequently or depends on the request: user profiles, search results, dashboards.

Client-Side Rendering (CSR) — Pages render in the browser using JavaScript. No HTML from the server. Best for highly interactive UIs that need real-time updates: live editors, chat interfaces, dashboards with user-specific data.

The App Router defaults to static generation and lets you opt into SSR per fetch call. You rarely have to choose manually.

The App Router vs Pages Router

If you've seen older Next.js code, it may use the pages/ directory. That's the Pages Router — still supported but no longer recommended for new projects.

The App Router (src/app/) is the modern approach. Key differences:

Pages RouterApp Router
File conventionspages/index.tsxapp/page.tsx
Data fetchinggetServerSideProps, getStaticPropsasync Server Components
Server ComponentsNoYes (default)
Layouts_app.tsxlayout.tsx (nested)
StreamingLimitedBuilt-in with Suspense

All new Next.js features are App Router only.

The Rendering Pipeline

When a request comes in:

  1. Route matching — Next.js matches the URL to a file in src/app/

  2. Component tree evaluation — Server Components run on the server. React evaluates the tree from the root layout down to the page, fetching any data along the way.

  3. HTML generation — React serializes the Server Component output to HTML and streams it to the browser.

  4. Hydration — The browser receives the HTML (fast first paint). React loads the JavaScript, attaches event handlers to the existing HTML. Client Components become interactive.

  5. Caching — The result may be cached at various layers depending on fetch options.

The Cache Layers

Next.js 15 has multiple independent caches:

Request Memoization — Within a single server render, identical fetch() calls with the same URL are deduplicated. Call getUser(id) in 5 different components — only one HTTP request goes out.

Data Cachefetch() responses are cached by default (persists across requests and deployments). Override with cache: "no-store" or next: { revalidate: 60 }.

Full Route Cache — Fully static routes (HTML + RSC payload) are cached on disk. Serves without running any server code.

Router Cache — Browser-side cache of page data already visited. Going "back" is instant.

User request
    ↓
Router Cache (browser) — hit? serve instantly
    ↓ miss
Full Route Cache (server) — hit? serve pre-rendered HTML
    ↓ miss
Server Component renders — calls fetch()
    ↓
Data Cache — hit? return cached response
    ↓ miss
External data source (DB, API)

Server Actions

Next.js 15 introduces Server Actions — functions that run on the server but can be called directly from client code, without building an API route:

// src/app/actions.ts
"use server";

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

export async function createPost(formData: FormData) {
    const title = formData.get("title") as string;
    const content = formData.get("content") as string;
    
    await db.post.create({ data: { title, content } });
    
    revalidatePath("/blog");   // invalidate the blog list cache
}
// src/app/blog/new/page.tsx
import { createPost } from "@/app/actions";

export default function NewPostPage() {
    return (
        // Action is called directly — no fetch, no API route, no useEffect
        <form action={createPost}>
            <input name="title" required />
            <textarea name="content" required />
            <button type="submit">Publish</button>
        </form>
    );
}

The form submits directly to the Server Action. React handles progressive enhancement — the form works even before JavaScript loads.

Partial Prerendering (PPR)

Next.js 15's new rendering mode — static HTML with dynamic holes:

import { Suspense } from "react";

// Static parts render at build time
export default function ProductPage({ params }: { params: { id: string } }) {
    return (
        <div>
            {/* Static — renders at build time */}
            <StaticHeader />
            
            {/* Dynamic — renders per request, streams in */}
            <Suspense fallback={<PriceSkeleton />}>
                <DynamicPrice productId={params.id} />
            </Suspense>
        </div>
    );
}

The static HTML arrives instantly from the CDN. The dynamic price streams in per-request. Best of both worlds.

File Conventions Summary

src/app/
├── layout.tsx          → Wraps all pages at this level
├── page.tsx            → The route's content
├── loading.tsx         → Shown while page data loads (Suspense boundary)
├── error.tsx           → Shown when page throws an error
├── not-found.tsx       → Shown when notFound() is called
├── route.ts            → API endpoint (no page.tsx at same level)
├── template.tsx        → Like layout but re-mounts on navigation
└── middleware.ts       → Runs before every request (auth checks, redirects)

Next.js Config

// next.config.ts
import type { NextConfig } from "next";

const config: NextConfig = {
    images: {
        remotePatterns: [
            { protocol: "https", hostname: "example.com" },
        ],
    },
    experimental: {
        ppr: true,   // Enable Partial Prerendering
    },
};

export default config;

Next lesson: Layouts and nested layouts — building consistent UI shells across your app.

📱

Get this course's notes on Telegram!

Free cheat sheets, summaries & practice exercises

Get Notes Free →
!