Introduction to Next.js

React for production. Routing, server rendering, and full-stack capabilities built in.

Why Next.js

React is a library. Next.js is the framework that makes it production-ready.

// React alone gives you:
// - Components and state management
// - Virtual DOM diffing
// ...and that's it. You still need:
// - Routing (which page to show)
// - Server rendering (SEO, performance)
// - Data fetching patterns
// - Code splitting
// - Image optimization
// - Deployment pipeline

// Next.js gives you ALL of that, pre-configured:
// ✓ File-based routing (folders = URLs)
// ✓ Server-side rendering (SSR) + static generation (SSG)
// ✓ Server Components (less JS shipped to browser)
// ✓ API routes (full-stack in one project)
// ✓ Image optimization (next/image)
// ✓ Font optimization (next/font)
// ✓ Built-in TypeScript support
// ✓ Middleware, redirects, rewrites
// ✓ One-command deployment (Vercel)

// Create a Next.js project:
// $ pnpm create next-app my-app
// $ cd my-app && pnpm dev

// This site (visualjs.in) is built with Next.js!

App Router

Folders become URLs. Files become pages.

// File-based routing:
// app/
// ├── page.tsx           →  /
// ├── about/
// │   └── page.tsx      →  /about
// ├── blog/
// │   ├── page.tsx      →  /blog
// │   └── [slug]/
// │       └── page.tsx  →  /blog/my-first-post
// └── layout.tsx         →  wraps ALL pages

// page.tsx — the page component (required for route to exist):
export default function HomePage() {
  return <h1>Welcome to my site</h1>;
}

// layout.tsx — shared wrapper (header, footer, providers):
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <nav>...</nav>
        {children}   {/* page content goes here */}
        <footer>...</footer>
      </body>
    </html>
  );
}

// Dynamic routes — [param] folder:
// app/blog/[slug]/page.tsx
export default async function BlogPost({ params }) {
  const { slug } = await params;
  // slug = "my-first-post" for /blog/my-first-post
  const post = await getPost(slug);
  return <article>{post.content}</article>;
}

Create a folder → it becomes a URL. Add page.tsx → it becomes a route. Dynamic segments use [brackets].

1 / 2

Server Components

Components that run on the server. Zero JavaScript sent to the browser.

// In Next.js App Router, ALL components are Server Components by default.
// They run on the server and send HTML to the browser.

// Server Component (default — no directive needed):
async function UserProfile({ userId }) {
  // This runs on the server — direct database access!
  const user = await db.query("SELECT * FROM users WHERE id = $1", [userId]);

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}
// ✓ Can use async/await directly
// ✓ Can access databases, file system, secrets
// ✓ Zero JS sent to browser for this component
// ✗ Cannot use useState, useEffect, onClick

// Client Component — add "use client" directive:
"use client";
import { useState } from "react";

function LikeButton() {
  const [likes, setLikes] = useState(0);
  return (
    <button onClick={() => setLikes(likes + 1)}>
      ♥ {likes}
    </button>
  );
}
// ✓ Can use state, effects, event handlers
// ✓ Can use browser APIs
// ✗ Cannot be async
// ✗ JS is sent to browser (adds to bundle)

// The pattern: Server Components for data + layout,
// Client Components for interactivity (keep them small!)
// page.tsx (Server) → fetches data, renders layout
//   └── LikeButton (Client) → handles clicks
//   └── CommentForm (Client) → handles input

Data Fetching

Fetch data where you need it — directly in components.

// Fetch data in Server Components (simplest pattern):
async function ProductPage({ params }) {
  const { id } = await params;
  const product = await fetch(
    "https://api.example.com/products/" + id
  ).then(r => r.json());

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.price}</p>
    </div>
  );
}

// Parallel data fetching:
async function Dashboard() {
  // These run in parallel (not waterfall):
  const [user, posts, stats] = await Promise.all([
    getUser(),
    getPosts(),
    getStats(),
  ]);

  return (
    <div>
      <UserCard user={user} />
      <PostList posts={posts} />
      <StatsPanel stats={stats} />
    </div>
  );
}

// Server Actions — mutations from the client:
// app/actions.ts:
"use server";

async function addTodo(formData) {
  const text = formData.get("text");
  await db.insert({ text, done: false });
  revalidatePath("/todos"); // refresh the page data
}

// In a Client Component:
function TodoForm() {
  return (
    <form action={addTodo}>
      <input name="text" placeholder="New todo..." />
      <button type="submit">Add</button>
    </form>
  );
}
// The form submits to the server action — no API route needed!
// Works with and without JavaScript (progressive enhancement)

Full Stack

Frontend + backend in one project. Deploy anywhere.

// Next.js is full-stack:
// - React pages (frontend)
// - Server Components (server-rendered UI)
// - Server Actions (mutations)
// - Route Handlers (REST API endpoints)
// - Middleware (auth, redirects)
// All in ONE project, ONE deployment.

// Route Handler (API endpoint):
// app/api/users/route.ts:
import { NextResponse } from "next/server";

export async function GET() {
  const users = await db.query("SELECT * FROM users");
  return NextResponse.json(users);
}

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

// Middleware (runs before every request):
// middleware.ts (in project root):
import { NextResponse } from "next/server";

export function middleware(request) {
  const token = request.cookies.get("session");
  if (!token && request.nextUrl.pathname.startsWith("/dashboard")) {
    return NextResponse.redirect(new URL("/login", request.url));
  }
}

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

// Deployment:
// $ pnpm build   → optimized production build
// Deploy to: Vercel (zero-config), AWS, Docker, anywhere Node runs
// Vercel: git push → auto-deploys in ~30 seconds

// What you've learned in this course prepares you for Next.js:
// ✓ JavaScript fundamentals → write React components
// ✓ DOM + events → understand what React abstracts
// ✓ Async/await → data fetching in components
// ✓ ES modules → imports/exports everywhere
// ✓ Design patterns → component architecture
// ✓ Build tools → understand the pipeline
// You're ready. Go build something.

FAQ

Common questions about Next.js.