Lesson 1/101

Tutorials Next.js Tutorial

Next.js Complete Beginner's Guide

Free Next.js guide: App Router, Server Components, SEO, data fetching, auth, deployment, and interview prep — build LearnHub step by step.

On this page

Next.js Tutorial (LearnHub) · Start Here

Next.js Complete Beginner's Guide

App Router · Server Components · SEO · Data fetching · Production patterns · Interview prep · ~50 min read

Welcome to LearnHub: Your Next.js Journey Starts Here

Picture this: Toolliyo Academy just hired you to build LearnHub, an online learning platform where students browse courses, watch lessons, take quizzes, and earn certificates. The product team wants pages that load in under two seconds on a budget Android phone, show up on Google when someone searches "React course online," and feel as smooth as Netflix when switching between lessons.

You could wire up React, React Router, Webpack, a Node server, SEO helpers, and image pipelines yourself. Or you could reach for Next.js 15 — a React framework that ships routing, server rendering, caching, and deployment as first-class citizens.

This guide is your single roadmap. We will build LearnHub step by step, from your first app/page.tsx to enterprise-grade enrollment flows with authentication and Server Actions. Every example uses TypeScript and the App Router — the modern way to write Next.js in 2025 and beyond.

Learning Objectives

By the end of this tutorial, you will be able to:

  • Explain why Next.js exists and how it differs from a plain React SPA
  • Scaffold a Next.js 15 project and navigate the App Router folder structure
  • Build Server Components, Client Components, layouts, and dynamic routes for an LMS
  • Fetch data using SSR, SSG, ISR, and Server Actions with clear trade-offs
  • Implement authentication, protected routes, and form handling for course enrollment
  • Optimize performance, SEO, and security for production LearnHub deployments
  • Avoid common mistakes that break hydration, caching, or auth flows
  • Answer interview questions from fresher to architect level with confidence

Figuring out why each piece exists matters more than memorizing syntax. When you understand the problem, the API names start to make sense on their own.

Prerequisites

Before you start, you should be comfortable with:

SkillWhy LearnHub Needs It
HTML & CSSCourse cards, lesson layouts, responsive dashboards
JavaScript (ES6+)Async/await, destructuring, modules
React basicsComponents, props, state, useEffect, useState
TypeScript fundamentalsTypes for course data, user roles, API responses
GitBranching while building features
Node.js 20 LTSRunning create-next-app, npm scripts, local dev server

You do not need prior Next.js experience. You do not need to know backend frameworks — we introduce Server Actions and API routes gradually.

Install Node.js from nodejs.org and verify:

node --version   # v20.x or v22.x recommended
npm --version    # 10.x or higher

The Problem with React SPAs

A classic React Single Page Application (SPA) ships one HTML shell and loads JavaScript in the browser. React then fetches JSON from APIs and paints the UI. This works — until LearnHub scales.

Pain points for an LMS like LearnHub

1. Slow first paint — Students on 3G see a blank screen while a large JS bundle downloads. Course catalog pages feel broken before React even starts. 2. Poor SEO — Search engines struggle with empty HTML shells. "Best TypeScript course" never ranks if Google sees <div id="root"></div> and a script tag. 3. Waterfall data loading — Browser loads JS → JS mounts → JS calls /api/courses → finally shows cards. Three round trips where one would suffice. 4. Auth complexity — Tokens in localStorage, refresh logic, and protected routes are all DIY. One mistake exposes instructor dashboards. 5. No sensible defaults — Routing, code splitting, image optimization, and caching require manual configuration and ongoing maintenance. 6. Bundle bloat — Importing a date library on the catalog page can accidentally ship it to the lesson player unless you split carefully.

Next.js does not replace React. It organizes React so the server can do heavy work before the browser ever asks a question.

Real-World Analogy: A Restaurant, Not a Vending Machine

Think of a React SPA as a vending machine in an empty lobby. You walk in, wait for the machine to boot, press buttons, and hope the snack arrives. If the machine is broken, you stare at a blank panel.

Think of Next.js as a restaurant with a kitchen and a dining room:

  • The kitchen (Node.js server) prepares dishes (HTML + data) before guests arrive.
  • The menu (routes) is organized by section — appetizers, mains, desserts map to /courses, /lessons, /profile.
  • Some dishes are pre-made (static pages cached at build time).
  • Some are cooked to order (SSR per request).
  • The waiter (hydration) brings interactivity — you can still customize your order at the table (Client Components with state).

LearnHub's course catalog is the daily special board: mostly the same for everyone, refreshed on a schedule. The student dashboard is cooked to order: unique progress, unique certificates. Next.js lets you pick the right kitchen workflow per page.

Alternatively, imagine a library:

  • SSG = books printed and shelved ahead of time (course landing pages).
  • SSR = librarian fetches your requested book when you ask (personalized dashboard).
  • ISR = popular books reprinted every night with updated chapters (catalog with nightly refresh).
  • Client Components = the reading lamp at your desk — only you control it (video player controls, quiz timer).

What Is Next.js?

Simple definition

Next.js is a React framework for building full-stack web applications. You write React components in TypeScript. Next.js decides which components run on the server, which run in the browser, how URLs map to files, and how HTML reaches the user.

Technical definition

Next.js 15 extends React 19 with:

  • App Router — File-system routing under app/ with nested layouts, loading UI, and error boundaries
  • React Server Components (RSC) — Components that execute on the server, never shipping their logic to the client bundle
  • Server Actions — Type-safe server functions invoked from forms and client code without hand-written REST endpoints
  • Built-in optimizations — Image, font, and script optimization; automatic code splitting per route
  • Flexible rendering — Static generation, server rendering, incremental static regeneration, and streaming
  • Route Handlers — API endpoints as route.ts files alongside pages

LearnHub uses all of these: static marketing pages, dynamic course pages, server-fetched catalogs, and enrollment forms that mutate the database on the server.

Why Next.js Is Needed for LearnHub

RequirementWithout Next.jsWith Next.js
Fast catalog loadManual SSR setupServer Components + caching
SEO for course pagesreact-helmet hacksgenerateMetadata, static HTML
Protected instructor areaCustom auth middlewareMiddleware + session libraries
Enrollment formsSeparate Express APIServer Actions
Image thumbnailsManual <img> tagsnext/image with lazy load
Deploy to productionConfigure Webpack, Node, CDNnext build + Vercel or Docker

Toolliyo Academy chose Next.js because one codebase serves students, instructors, and admins — with less glue code and fewer moving parts in production.

Real-World Applications of Next.js

IndustryExample UseNext.js Feature Used
EdTech (LearnHub)Course catalog, lesson player, certificatesSSR, ISR, Server Actions
E-commerceProduct pages, checkoutSSG + dynamic cart (Client Components)
SaaS dashboardsAnalytics, billingServer Components, Route Handlers
Marketing sitesLanding pages, blogsSSG, Metadata API
News & mediaArticle pages with adsISR, streaming
Internal toolsHR portals, admin panelsAuth middleware, API routes
DocumentationDeveloper docs (MDX)Static generation, route groups

If your app is mostly public content with occasional personalization — like LearnHub — Next.js is a strong default.

How Next.js Works Internally

When a student visits learnhub.toolliyo.com/courses/react-fundamentals, this is the journey:

Step 1: Browser sends request

The browser asks for a URL. DNS resolves to your hosting provider (Vercel, AWS, or a VPS running Node).

Step 2: Next.js server receives request

The Node.js process matches the URL to a route in app/courses/[slug]/page.tsx. Middleware may run first (auth checks, redirects, locale detection).

Step 3: React Server Components render

Server Components fetch course data from PostgreSQL or a CMS. They render to a special RSC payload — a compact stream describing UI structure and serialized data. Sensitive logic (database queries, API keys) never leaves the server.

Step 4: HTML streams to the browser

Next.js sends HTML immediately for the static shell, then streams dynamic sections as they resolve. The student sees the course title and syllabus before the "Related courses" sidebar finishes loading.

Step 5: Hydration attaches interactivity

Client Components — marked with "use client" — download their JavaScript bundles. React hydrates them: attaching event listeners to server-rendered HTML. The "Enroll now" button becomes clickable; the video player initializes.

Step 6: Client-side navigation

When the student clicks another course, Next.js fetches only the RSC payload for that route — not a full page reload. LearnHub feels like an SPA while keeping server rendering benefits.

Rendering Pipeline (Mermaid Diagram)

flowchart LR
    A[Browser Request] --> B[Next.js Server]
    B --> C{Middleware}
    C -->|Pass| D[Match Route in app/]
    C -->|Fail| R[Redirect / 401]
    D --> E[Server Components]
    E --> F[Data Layer DB / API]
    F --> E
    E --> G[RSC Payload + HTML Stream]
    G --> H[Browser]
    H --> I[Hydrate Client Components]
    I --> J[Interactive LearnHub UI]
    J -->|Client Navigation| A

Reading the diagram: Requests enter from the left. Middleware gates protected routes. Server Components talk to your data layer. The response combines HTML and RSC data. Client Components hydrate in the browser. Subsequent navigations loop back without full reloads.

Installation: Creating LearnHub

Open a terminal and scaffold the project:

npx create-next-app@latest learnhub

Recommended prompts for this tutorial:

PromptChoiceReason
TypeScriptYesType-safe course models and auth
ESLintYesCatch mistakes early
Tailwind CSSYesFast LMS styling
src/ directoryNoKeeps paths simple for beginners
App RouterYesRequired for Next.js 15 patterns
TurbopackYes (optional)Faster local dev
Import alias @/*YesClean imports like @/components/CourseCard

Start the dev server:

cd learnhub
npm run dev

Visit http://localhost:3000. You should see the default Next.js welcome page — soon replaced by LearnHub branding.

Project Structure Explained

After scaffolding, your LearnHub folder looks like this:

learnhub/
├── app/                    # App Router — URLs live here
│   ├── layout.tsx          # Root layout (navbar, fonts, global CSS)
│   ├── page.tsx            # Home page (/)
│   ├── courses/
│   │   ├── page.tsx        # /courses
│   │   └── [slug]/
│   │       └── page.tsx    # /courses/react-fundamentals
│   ├── dashboard/
│   │   └── page.tsx        # /dashboard (protected later)
│   ├── api/                # Optional Route Handlers
│   │   └── health/
│   │       └── route.ts
│   ├── globals.css
│   └── favicon.ico
├── components/             # Reusable UI (CourseCard, Navbar)
├── lib/                    # Utilities (db client, auth helpers)
├── public/                 # Static files (logo.svg, certificates)
├── types/                  # Shared TypeScript interfaces
├── next.config.ts          # Next.js configuration
├── package.json
├── tsconfig.json
└── .env.local              # Secrets (never commit)

Key rules:

  • app/page.tsx = route for /
  • app/courses/page.tsx = route for /courses
  • Folders define URL segments; page.tsx makes a route publicly accessible
  • layout.tsx wraps child routes — perfect for LearnHub's persistent sidebar
  • loading.tsx and error.tsx are optional UX files per route segment

Beginner Example: LearnHub Home Page

Replace app/page.tsx with LearnHub's landing page:

// app/page.tsx
import Link from "next/link";

export default function HomePage() {
  return (
    <main className="mx-auto max-w-3xl px-4 py-16">
      <h1 className="text-4xl font-bold text-slate-900">
        Welcome to LearnHub
      </h1>
      <p className="mt-4 text-lg text-slate-600">
        Toolliyo Academy&apos;s learning platform — master web development
        with hands-on courses.
      </p>
      <Link
        href="/courses"
        className="mt-8 inline-block rounded-lg bg-indigo-600 px-6 py-3 text-white"
      >
        Browse courses
      </Link>
    </main>
  );
}

Line-by-line explanation

Line 1 — import Link from "next/link" Next.js provides a Link component that prefetches routes on hover. Plain <a> tags cause full page reloads; Link enables client-side navigation while keeping SEO-friendly anchor semantics.

Line 3 — export default function HomePage() Every page exports a default React component. The function name is for your readability — Next.js cares about the file location, not the name. This component is a Server Component by default (no "use client" directive).

Lines 5–8 — <main> and heading Semantic HTML helps screen readers and SEO. Tailwind classes style the layout. Because this renders on the server, students see the heading in the first HTML chunk — no waiting for JavaScript.

Lines 9–12 — Description paragraph &apos; is the HTML entity for an apostrophe in JSX text. The copy is static; no database call needed. Perfect for SSG at build time.

Lines 13–18 — Navigation link href="/courses" maps to app/courses/page.tsx (which you will create next). The button styling is pure presentation. When clicked, Next.js swaps route content without reloading the entire document.

Why this matters: In under 20 lines you have a fast, SEO-friendly home page with client-side navigation ready. No router configuration file. No BrowserRouter wrapper.

Intermediate Example: Course Listing Page

Create app/courses/page.tsx to show LearnHub's catalog with server-fetched data:

// app/courses/page.tsx
import Link from "next/link";

type Course = {
  id: string;
  slug: string;
  title: string;
  instructor: string;
  durationHours: number;
  level: "Beginner" | "Intermediate" | "Advanced";
};

async function getCourses(): Promise<Course[]> {
  // In production: await db.course.findMany()
  // Simulated delay teaches async Server Component patterns
  await new Promise((resolve) => setTimeout(resolve, 100));

  return [
    {
      id: "1",
      slug: "react-fundamentals",
      title: "React Fundamentals",
      instructor: "Priya Sharma",
      durationHours: 12,
      level: "Beginner",
    },
    {
      id: "2",
      slug: "nextjs-mastery",
      title: "Next.js Mastery",
      instructor: "Arjun Mehta",
      durationHours: 18,
      level: "Intermediate",
    },
    {
      id: "3",
      slug: "typescript-deep-dive",
      title: "TypeScript Deep Dive",
      instructor: "Sneha Patel",
      durationHours: 10,
      level: "Advanced",
    },
  ];
}

export default async function CoursesPage() {
  const courses = await getCourses();

  return (
    <main className="mx-auto max-w-4xl px-4 py-12">
      <h1 className="text-3xl font-bold">Course Catalog</h1>
      <p className="mt-2 text-slate-600">
        {courses.length} courses available on LearnHub
      </p>

      <ul className="mt-8 grid gap-4 sm:grid-cols-2">
        {courses.map((course) => (
          <li
            key={course.id}
            className="rounded-xl border border-slate-200 p-6 shadow-sm"
          >
            <span className="text-xs font-medium uppercase text-indigo-600">
              {course.level}
            </span>
            <h2 className="mt-2 text-xl font-semibold">{course.title}</h2>
            <p className="mt-1 text-sm text-slate-500">
              {course.instructor} · {course.durationHours}h
            </p>
            <Link
              href={`/courses/${course.slug}`}
              className="mt-4 inline-block text-indigo-600 hover:underline"
            >
              View syllabus →
            </Link>
          </li>
        ))}
      </ul>
    </main>
  );
}

Concepts introduced

  • async page components — Server Components can await data directly. No useEffect fetch on the client.
  • TypeScript Course type — Documents shape for API/database migration later.
  • Dynamic links — ` /courses/${course.slug} maps to app/courses/[slug]/page.tsx`.
  • Grid layout — Responsive catalog without a client-side state library.

Add app/courses/[slug]/page.tsx for individual course pages using params:

// app/courses/[slug]/page.tsx
type Props = {
  params: Promise<{ slug: string }>;
};

export default async function CourseDetailPage({ params }: Props) {
  const { slug } = await params;
  // Fetch single course by slug from database
  return (
    <main className="mx-auto max-w-3xl px-4 py-12">
      <h1 className="text-3xl font-bold capitalize">
        {slug.replace(/-/g, " ")}
      </h1>
      <p className="mt-4 text-slate-600">Full syllabus coming from CMS...</p>
    </main>
  );
}

In Next.js 15, params is a Promise — always await it in async components.

Enterprise Example: Auth + Server Action Enrollment

LearnHub must ensure only logged-in students enroll, and enrollment must write to the database securely. Here is a production-shaped pattern.

Database schema (Prisma example)

// prisma/schema.prisma (conceptual)
model User {
  id          String       @id @default(cuid())
  email       String       @unique
  enrollments Enrollment[]
}

model Course {
  id          String       @id @default(cuid())
  slug        String       @unique
  title       String
  enrollments Enrollment[]
}

model Enrollment {
  id        String   @id @default(cuid())
  userId    String
  courseId  String
  enrolledAt DateTime @default(now())
  user      User     @relation(fields: [userId], references: [id])
  course    Course   @relation(fields: [courseId], references: [id])

  @@unique([userId, courseId])
}

Auth helper

// lib/auth.ts
import { cookies } from "next/headers";

export type SessionUser = {
  id: string;
  email: string;
};

export async function getSessionUser(): Promise<SessionUser | null> {
  const cookieStore = await cookies();
  const sessionId = cookieStore.get("learnhub_session")?.value;
  if (!sessionId) return null;

  // Validate session against database or auth provider
  // Return null if invalid — never trust cookie value alone
  return { id: "user_abc", email: "student@toolliyo.com" };
}

Server Action for enrollment

// app/courses/[slug]/actions.ts
"use server";

import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { getSessionUser } from "@/lib/auth";
import { prisma } from "@/lib/prisma";

export type EnrollState = {
  error?: string;
  success?: boolean;
};

export async function enrollInCourse(
  _prevState: EnrollState,
  formData: FormData
): Promise<EnrollState> {
  const user = await getSessionUser();
  if (!user) {
    return { error: "Please sign in to enroll." };
  }

  const courseId = formData.get("courseId");
  if (typeof courseId !== "string" || !courseId) {
    return { error: "Invalid course." };
  }

  try {
    await prisma.enrollment.create({
      data: { userId: user.id, courseId },
    });
  } catch {
    return { error: "You may already be enrolled." };
  }

  revalidatePath("/dashboard");
  redirect("/dashboard");
}

Client form with useActionState

// components/EnrollButton.tsx
"use client";

import { useActionState } from "react";
import { enrollInCourse, type EnrollState } from "@/app/courses/[slug]/actions";

const initialState: EnrollState = {};

type Props = {
  courseId: string;
  courseTitle: string;
};

export function EnrollButton({ courseId, courseTitle }: Props) {
  const [state, formAction, isPending] = useActionState(
    enrollInCourse,
    initialState
  );

  return (
    <form action={formAction} className="mt-6">
      <input type="hidden" name="courseId" value={courseId} />
      <button
        type="submit"
        disabled={isPending}
        className="rounded-lg bg-indigo-600 px-6 py-3 text-white disabled:opacity-50"
      >
        {isPending ? "Enrolling..." : `Enroll in ${courseTitle}`}
      </button>
      {state.error && (
        <p className="mt-2 text-sm text-red-600" role="alert">
          {state.error}
        </p>
      )}
    </form>
  );
}

Middleware for protected routes

// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  const session = request.cookies.get("learnhub_session");
  const isProtected = request.nextUrl.pathname.startsWith("/dashboard");

  if (isProtected && !session) {
    const loginUrl = new URL("/login", request.url);
    loginUrl.searchParams.set("callbackUrl", request.nextUrl.pathname);
    return NextResponse.redirect(loginUrl);
  }

  return NextResponse.next();
}

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

Enterprise takeaways:

  • "use server" marks functions that run only on the server — safe for database writes
  • Never trust courseId from the client without validation
  • revalidatePath refreshes cached dashboard data after enrollment
  • Middleware enforces auth before the page renders
  • Client Components handle pending UI; Server Actions handle mutations

Folder Organization for a Growing LearnHub

As LearnHub grows, adopt this structure:

learnhub/
├── app/
│   ├── (marketing)/          # Route group — no URL segment
│   │   ├── page.tsx          # Landing
│   │   └── about/page.tsx
│   ├── (platform)/           # Authenticated app shell
│   │   ├── layout.tsx        # Shared sidebar
│   │   ├── dashboard/
│   │   └── courses/
│   ├── login/
│   └── layout.tsx
├── components/
│   ├── ui/                   # Buttons, inputs
│   ├── courses/              # CourseCard, EnrollButton
│   └── layout/               # Navbar, Footer
├── lib/
│   ├── prisma.ts
│   ├── auth.ts
│   └── validators/
├── types/
│   └── course.ts
└── actions/                  # Shared Server Actions (optional)

Route groups (marketing) and (platform) organize files without changing URLs. Colocate components near features, but promote to components/ when reused twice.

Data Fetching: SSR, SSG, ISR, and Server Actions

StrategyWhen to Use in LearnHubHow in App Router
SSGAbout page, static syllabus PDFsDefault fetch cached at build
SSRPersonalized dashboardfetch(url, { cache: 'no-store' }) or dynamic page
ISRCourse catalog (updates nightly)revalidate: 3600 in fetch or export const revalidate = 3600
Client fetchLive quiz leaderboardClient Component + SWR/React Query
Server ActionsEnroll, submit assignment"use server" functions

Example ISR catalog fetch:

async function getCourses() {
  const res = await fetch("https://api.learnhub.internal/courses", {
    next: { revalidate: 3600 }, // refresh every hour
  });
  if (!res.ok) throw new Error("Failed to load courses");
  return res.json();
}

Rule of thumb: Fetch on the server by default. Move to the client only when you need browser APIs, real-time updates, or heavy interactivity.

Routing Deep Dive

FilePurposeLearnHub Example
page.tsxUI for a routeCourse detail page
layout.tsxShared wrapperDashboard sidebar
loading.tsxSuspense fallbackSkeleton while courses load
error.tsxError boundary"Failed to load lesson"
not-found.tsx404 UIUnknown course slug
route.tsAPI endpointWebhook from payment gateway

Dynamic segments: [slug], [...catchAll] Parallel routes: @modal for enrollment modal over catalog Intercepting routes: Show lesson preview as overlay from catalog

Navigation:

import Link from "next/link";
import { useRouter } from "next/navigation"; // Client Component only

// Declarative
<Link href="/courses/nextjs-mastery">Next.js Mastery</Link>

// Programmatic (client)
const router = useRouter();
router.push("/dashboard");

State Management Overview

LearnHub rarely needs Redux on day one. Use the right tool per layer:

State TypeToolExample
Server dataServer Components + cacheCourse list
Form stateServer Actions + useActionStateEnrollment
UI togglesuseState in Client ComponentsSidebar open/close
Global client stateReact Context or ZustandVideo player settings
Remote syncTanStack QueryLive discussion threads

Principle: If data can live on the server, keep it on the server. Client state is for interaction, not duplication of server truth.

Performance Optimization

1. Server Components by default — Ship less JavaScript to students on mobile. 2. next/image — Automatic WebP/AVIF, lazy loading, size hints for course thumbnails. 3. Dynamic importsconst Chart = dynamic(() => import('./Chart'), { ssr: false }) for heavy admin charts. 4. Streaming with Suspense — Show lesson header while comments load. 5. Font optimizationnext/font eliminates layout shift for LearnHub branding. 6. Analyze bundles@next/bundle-analyzer finds accidental imports. 7. Edge vs Node runtime — Edge for geo redirects; Node for Prisma/database.

import Image from "next/image";

<Image
  src="/courses/react-fundamentals.jpg"
  alt="React Fundamentals course cover"
  width={640}
  height={360}
  priority={false}
/>

SEO for LearnHub

Search traffic drives enrollments. Next.js Metadata API makes SEO declarative:

// app/courses/[slug]/page.tsx
import type { Metadata } from "next";

type Props = { params: Promise<{ slug: string }> };

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params;
  const title = slug.replace(/-/g, " ");
  return {
    title: `${title} | LearnHub`,
    description: `Enroll in ${title} on Toolliyo Academy's LearnHub platform.`,
    openGraph: {
      title: `${title} | LearnHub`,
      type: "website",
    },
  };
}

Additional SEO practices:

  • Semantic HTML (<main>, <article>, <nav>)
  • Canonical URLs for duplicate content
  • sitemap.ts and robots.ts in app/
  • JSON-LD structured data for courses (schema.org Course type)
  • Fast LCP — hero text in Server Components, optimized images

Authentication Patterns

LearnHub supports multiple auth strategies:

ApproachBest ForNotes
Auth.js (NextAuth v5)Email + OAuth, sessions@auth/nextjs, database sessions
Clerk / Auth0Fast MVP, hosted UILess backend code
Custom JWT + cookiesFull controlHigher security burden

Core patterns regardless of provider:

  • Store sessions in httpOnly cookies, not localStorage
  • Validate session on server for every protected Server Action
  • Use middleware for route-level guards
  • Pass role claims (student, instructor, admin) for authorization inside Server Components
// Example: role check in Server Component
const user = await getSessionUser();
if (!user || user.role !== "instructor") {
  notFound(); // or redirect
}

API Routes (Route Handlers)

When you need REST endpoints — webhooks, mobile app backends, third-party integrations — use Route Handlers:

// app/api/courses/route.ts
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";

export async function GET() {
  const courses = await prisma.course.findMany({
    select: { id: true, slug: true, title: true, level: true },
  });
  return NextResponse.json(courses);
}

export async function POST(request: Request) {
  const body = await request.json();
  // Validate with Zod, create course, return 201
  return NextResponse.json({ ok: true }, { status: 201 });
}

Server Actions vs Route Handlers:

  • Server Actions — Forms and mutations from your Next.js UI
  • Route Handlers — External clients, webhooks, public APIs

LearnHub uses Server Actions for enrollment and Route Handlers for Stripe payment webhooks.

Database Integration

Typical LearnHub stack:

  • PostgreSQL — Relational data (users, courses, enrollments, progress)
  • Prisma ORM — Type-safe queries, migrations
  • Connection pooling — PgBouncer or Prisma Accelerate in serverless deploys
// lib/prisma.ts
import { PrismaClient } from "@prisma/client";

const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };

export const prisma =
  globalForPrisma.prisma ??
  new PrismaClient({
    log: process.env.NODE_ENV === "development" ? ["query"] : [],
  });

if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;

Singleton pattern prevents too many connections during hot reload in development.

Environment variables in .env.local:

DATABASE_URL="postgresql://user:pass@localhost:5432/learnhub"
AUTH_SECRET="generate-a-long-random-string"

Never expose DATABASE_URL to the client. Only import prisma in Server Components, Server Actions, and Route Handlers.

Best Practices

1. Colocate by feature — Keep course components near app/courses/. 2. Validate all inputs — Use Zod on Server Actions and Route Handlers. 3. Prefer composition — Small Server Components wrapping Client Component islands. 4. Explicit caching — Know whether each fetch is static, dynamic, or revalidated. 5. Type everything — Shared types in types/ prevent API drift. 6. Use loading.tsx — Perceived performance matters for LMS retention. 7. Test critical flows — Enrollment, payment, certificate generation. 8. Keep secrets server-sideprocess.env without NEXT_PUBLIC_ prefix stays private. 9. Progressive enhancement — Forms should work before hydration where possible. 10. Monitor production — Vercel Analytics, Sentry, or OpenTelemetry for LearnHub ops.

Common Mistakes

MistakeSymptomFix
Adding "use client" to every fileHuge JS bundle, slow TTIDefault to Server Components
Using useEffect to fetch on mountWaterfall, SEO gapsFetch in Server Component
Passing non-serializable props to Client ComponentsRuntime errorsPass plain JSON, not functions/classes
Forgetting await params in Next.js 15Stale or undefined slugconst { slug } = await params
Mutating data without revalidationStale dashboard after enrollrevalidatePath or revalidateTag
Storing JWT in localStorageXSS token thefthttpOnly cookies
Importing prisma in Client ComponentsBundle leak / crashServer-only imports
Overusing cache: 'no-store'Slow every requestISR where data allows
Missing key in listsReact reconciliation bugsStable key={course.id}
Ignoring middleware matcherProtected routes exposedTest /dashboard without cookie

Security Essentials

  • CSRF — Server Actions include built-in origin checks; verify for Route Handlers
  • XSS — React escapes by default; avoid dangerouslySetInnerHTML unless sanitized
  • SQL injection — Use Prisma parameterized queries, never string-concat SQL
  • Authorization — Auth ≠ authorization; check roles on every sensitive action
  • Rate limiting — Protect login and enrollment endpoints (Upstash, middleware)
  • Security headers — Configure CSP, HSTS in next.config.ts
  • Dependency auditsnpm audit in CI pipeline
// next.config.ts — example headers
const nextConfig = {
  async headers() {
    return [
      {
        source: "/(.*)",
        headers: [
          { key: "X-Frame-Options", value: "DENY" },
          { key: "X-Content-Type-Options", value: "nosniff" },
        ],
      },
    ];
  },
};

export default nextConfig;

Advantages of Next.js

  • Full-stack in one repo — UI, API, and server logic together
  • Excellent DX — Fast Refresh, TypeScript, sensible defaults
  • SEO and performance — Server rendering without custom infrastructure
  • Ecosystem — Vercel deploy, Auth.js, Prisma, Tailwind integrations
  • Incremental adoption — Migrate page by page from legacy React
  • RSC innovation — Industry direction aligned with React core team

Limitations and When to Choose Something Else

Next.js is not universal. Consider alternatives when:

ScenarioBetter ChoiceWhy
Mostly static docs/blog, minimal JSAstroShips zero JS by default
Web-standard forms, nested routing focusRemixLoaders/actions model, progressive enhancement
Mobile app onlyReact NativeNext.js targets web
Real-time collaborative editorPlain React + WebSocket backendRSC less suited to CRDT sync
Simple internal widget embedded in legacy PHPPlain React bundleFull framework overhead unnecessary
Edge-only API with no UIHono / ExpressNo React needed

LearnHub stays on Next.js because it blends marketing pages, authenticated dashboards, and API webhooks in one TypeScript codebase — the framework's sweet spot.

Frequently Asked Questions

1. Do I need to learn React before Next.js?

Yes. Next.js is React with conventions. Understand components, props, and basic hooks first. Toolliyo's React tutorial pairs well with this guide.

2. App Router or Pages Router?

Use App Router for all new projects. Pages Router (pages/) is maintenance mode for legacy apps. LearnHub uses app/ exclusively.

3. Can I deploy Next.js without Vercel?

Absolutely. AWS (Amplify, ECS), Docker, Netlify, Railway, and self-hosted Node all work. Run next build then next start or use standalone output for containers.

4. Are Server Components the same as SSR?

Related but different. SSR is *when* HTML is generated (per request). Server Components are *what* runs on the server (by default, most components). You can statically generate Server Components at build time too.

5. When should I use "use client"?

When you need useState, useEffect, browser APIs (window, localStorage), or event handlers (onClick). Keep the boundary as low in the tree as possible — wrap only the interactive leaf.

6. How does LearnHub handle file uploads (assignment PDFs)?

Use Server Actions with FormData and stream to S3/R2, or presigned URL uploads from a Client Component. Never process large files synchronously without streaming.

7. Is Next.js good for large teams?

Yes, with discipline: feature folders, shared ESLint rules, typed Server Actions, and documented caching strategy. Monorepo tools (Turborepo) help when LearnHub splits into @learnhub/ui packages.

8. Does Next.js replace my backend?

Partially. Server Actions and Route Handlers cover many backend needs. Complex microservices (video transcoding, ML grading) may still live in separate services that LearnHub calls via API.

9. What Node version for Next.js 15?

Node.js 20.9+ minimum. LTS 20 or 22 recommended for LearnHub production.

10. How do I debug Server Components?

Use console.log on the server — output appears in the terminal, not the browser DevTools. React DevTools shows Client Components; server logs are your friend for RSC.

Interview Questions and Answers

Fresher Level

Q: What is Next.js? A: A React framework for production web apps. It adds file-based routing, server rendering, API routes, and optimizations like image handling on top of React.

Q: What is the difference between page.tsx and layout.tsx? A: page.tsx defines unique UI for a URL segment. layout.tsx wraps pages and persists across navigations — ideal for navbars and sidebars that should not remount.

Q: What does "use client" do? A: Marks a component and its imports as Client Components that bundle JavaScript to the browser for interactivity.

Intermediate Level

Q: Explain SSR, SSG, and ISR. A: SSR generates HTML per request. SSG generates at build time. ISR regenerates static pages on a timer after first request — combining static speed with periodic freshness.

Q: How do Server Actions differ from REST API calls? A: Server Actions are typed functions invoked directly from forms or client code. Next.js handles serialization and POST wiring. REST Route Handlers expose HTTP endpoints for any client.

Q: Why must you await params in Next.js 15? A: Dynamic route params are asynchronous to support streaming and partial rendering. Pages receive params: Promise<{ slug: string }>.

Senior Level

Q: How would you design caching for LearnHub's course catalog? A: SSG or ISR for public catalog with revalidate aligned to CMS publish schedule. Tag-based revalidation (revalidateTag) when a course updates. Personalization (enrolled badge) in a small Client Component or separate Server Component fetch with no-store only for user-specific fragments.

Q: How do you prevent unauthorized enrollment in Server Actions? A: Re-authenticate session inside the action, validate input, check authorization server-side, use database constraints (@@unique([userId, courseId])), and never rely on hidden form fields alone.

Q: When would you split LearnHub into micro-frontends? A: When independent teams need separate deploy cycles at scale — e.g., video player team vs billing team. Until then, monolithic Next.js with route groups and modular packages is simpler.

Architect Level

Q: How do RSC and hydration affect your system design? A: RSC shifts data fetching and heavy computation to the server, reducing client bundle size. Hydration cost remains for Client Component islands — architect so interactivity is localized. Caching boundaries (CDN, data cache, full route cache) must be explicit in design docs.

Q: Next.js on serverless vs long-running Node — trade-offs? A: Serverless scales to zero and handles spikes (enrollment day) but cold starts and connection limits hurt database-heavy pages. Long-running Node suits WebSockets and persistent connections. LearnHub might hybrid: static/ISR on CDN, dynamic dashboard on Node with connection pooling.

Q: How would you migrate LearnHub from Pages Router to App Router? A: Incremental — enable app/ alongside pages/, migrate leaf routes first, port data fetching to Server Components, replace getServerSideProps with async pages, move API routes to route.ts, update auth middleware, and run parallel QA before cutover.

Summary

You started with a real problem — building LearnHub for Toolliyo Academy — and saw why plain React SPAs struggle with speed, SEO, and security. Next.js 15 with the App Router gives you Server Components, flexible rendering strategies, and Server Actions in one TypeScript codebase.

You learned the request path from browser to server to RSC payload to hydration. You scaffolded a project, built a home page, a course catalog, and an enterprise enrollment flow with auth middleware. You explored routing, caching, performance, SEO, databases, and security — plus when Remix, Astro, or plain React might be better fits.

The pattern repeats throughout your career: why before how, server first, client only when necessary, and validate every mutation on the server.

What's Next on Your LearnHub Journey

1. Complete the Toolliyo Next.js 100-article track — Each lesson deepens one concept from this master guide. 2. Build the lesson player — Client Component video controls + Server Component syllabus sidebar. 3. Add Auth.js — Google login for students, role-based instructor dashboard. 4. Connect Prisma + PostgreSQL — Replace mock getCourses with real data. 5. Deploy to Vercel — Preview URLs for every pull request. 6. Add E2E tests — Playwright flow: browse → enroll → dashboard. 7. Study caching deeply — Read Next.js docs on fetch cache, unstable_cache, and tags. 8. Explore parallel routes — Enrollment modal without losing catalog scroll position.

Open your terminal, run npx create-next-app@latest learnhub, and replace the default page with LearnHub's welcome screen. The catalog, dashboard, and certificates are waiting — and now you have the map to build them.

*Toolliyo Academy — LearnHub Project Thread · Next.js 15 · App Router · TypeScript*

Ready for hands-on practice?

Start Lesson 1 and build LearnHub step by step — one concept per lesson with TypeScript you run locally.

Start Lesson 1 — Introduction to Next.js View full course syllabus
Next.js Tutorial
Course syllabus
Start Here Next.js Complete Beginner's Guide
Module 1: Next.js Foundations Introduction to Next.js — Complete Guide Installing Next.js — Complete Guide Understanding Project Structure — Complete Guide App Router Basics — Complete Guide Pages and Layouts — Complete Guide React Components in Next.js — Complete Guide Client Components — Complete Guide Server Components — Complete Guide Routing Fundamentals — Complete Guide Dynamic Routing — Complete Guide
Module 2: Layouts & Styling Nested Layouts — Complete Guide Navigation and Linking — Complete Guide Static Assets — Complete Guide CSS Modules — Complete Guide Tailwind CSS in Next.js — Complete Guide Data Fetching — Complete Guide Server Actions — Complete Guide Forms in Next.js — Complete Guide Form Validation — Complete Guide Authentication Basics — Complete Guide
Module 3: Data & Forms Middleware — Complete Guide API Route Handlers — Complete Guide Database Integration — Complete Guide File Upload — Complete Guide Image Optimization — Complete Guide Metadata and SEO — Complete Guide Performance Optimization — Complete Guide Deployment — Complete Guide E-Commerce App Project — Complete Guide SaaS Dashboard Project — Complete Guide
Module 4: Auth & APIs SSR vs SSG vs ISR — Complete Guide Streaming and Suspense — Complete Guide Loading and Error UI — Complete Guide Route Groups — Complete Guide Parallel Routes — Complete Guide Intercepting Routes — Complete Guide Edge Runtime — Complete Guide Caching in Next.js — Complete Guide Revalidating Data — Complete Guide TanStack Query in Next.js — Complete Guide
Module 5: SEO & Deploy NextAuth.js — Complete Guide Clerk Authentication — Complete Guide OAuth and Social Login — Complete Guide Protected Routes — Complete Guide Prisma ORM — Complete Guide MongoDB with Next.js — Complete Guide PostgreSQL with Next.js — Complete Guide Environment Variables — Complete Guide Unit Testing — Complete Guide Integration Testing — Complete Guide
Module 6: Advanced Routing Playwright E2E — Complete Guide CI/CD for Next.js — Complete Guide Internationalization — Complete Guide Accessibility — Complete Guide XSS and CSRF Protection — Complete Guide Security Headers — Complete Guide Rate Limiting — Complete Guide Structured Data — Complete Guide Sitemap and Robots — Complete Guide Zustand State — Complete Guide
Module 7: Auth & Database Redux Toolkit in Next.js — Complete Guide React Context Patterns — Complete Guide Monorepo with Turborepo — Complete Guide Docker for Next.js — Complete Guide Vercel Deployment — Complete Guide AWS Amplify — Complete Guide Azure Static Web Apps — Complete Guide Micro Frontends — Complete Guide Remix vs Next.js — Complete Guide Web Vitals Tuning — Complete Guide
Module 8: Quality & Security Font Optimization — Complete Guide Bundle Analysis — Complete Guide Blog Application Project — Complete Guide Student Portal Project — Complete Guide Job Portal Project — Complete Guide Hospital Portal Project — Complete Guide Food Delivery Frontend Project — Complete Guide Banking Dashboard Project — Complete Guide LMS Course Player Project — Complete Guide CRM Admin Project — Complete Guide
Module 9: Cloud & Scale Real-Time Chat Project — LearnHub Project Multi-Tenant SaaS Project — LearnHub Project Inventory Dashboard Project — LearnHub Project Travel Booking Project — LearnHub Project News Portal Project — LearnHub Project Portfolio Site Project — LearnHub Project Enterprise Architecture — LearnHub Project Clean Folder Structure — LearnHub Project API Design Patterns — LearnHub Project Error Handling Patterns — LearnHub Project
Module 10: Portfolio Projects Logging and Monitoring — LearnHub Project Stripe Payments — LearnHub Project Analytics and Observability — LearnHub Project Storybook with Next.js — LearnHub Project GraphQL with Next.js — LearnHub Project Content Security Policy — LearnHub Project Partial Prerendering — LearnHub Project Server Actions Security — LearnHub Project Production Checklist — LearnHub Project Next.js Career Roadmap — LearnHub Project
Toolliyo Assistant
Ask about tutorials, ebooks, training, pricing, mentor services, and support. I use public site content only—not admin or internal tools.

care@toolliyo.com

Need callback? Share your details