JSTAcademy
0 XP
Dashboard
Crash Courses
Environment, App Router & How Next.js Actually Works
12 min
Basic+150 XP
Crash Courses · Basic

Environment, App Router & How Next.js Actually Works

Set up a Next.js 15 project, understand App Router routing, and master the Server vs Client Component model.
12 min read+150 XP on completionCert: Next.js Crash Course
Tap any word in the text below to start reading from there.

Environment, App Router & How Next.js Actually Works

Why Next.js? The Problem with Plain React

A plain React app (from Vite) ships an empty HTML shell to the browser:

<div id="root"></div>
<script src="/bundle.js"></script>

The browser downloads the JS bundle, executes it, then React renders your app into #root. Until the JS runs, the user sees nothing. Search engines see nothing. This is called Client-Side Rendering (CSR) and it has real costs:

  • Slow First Contentful Paint (FCP) — users wait for JS before seeing content
  • Poor SEO — crawlers often don't execute JS
  • No direct database access — everything needs a separate API endpoint

Next.js adds Server-Side Rendering (SSR), Static Site Generation (SSG), and Incremental Static Regeneration (ISR) — giving you pre-rendered HTML from the server on every request, at build time, or on a schedule. Users get fast, SEO-friendly pages. The App Router takes this further: components run on the server by default and you opt into client behavior only when needed.

Setting Up Your Environment

Step 1 — Create the project:

npx create-next-app@latest my-app --typescript --tailwind --app
cd my-app
npm run dev

Flags explained:

  • --typescript: TypeScript from the start (industry standard)
  • --tailwind: Tailwind CSS pre-configured
  • --app: App Router (not the older Pages Router)

Your app runs at http://localhost:3000. The terminal shows a build output with routes detected. HMR works — save a file and the browser updates instantly.

Step 2 — VS Code extensions to install:

  • Next.js extension (by Vercel) or the ES7+ React snippets extension: Component scaffolding shortcuts.
  • Tailwind CSS IntelliSense: Autocomplete for Tailwind class names in JSX.
  • Prettier: Format on save.

Step 3 — Understand the start-up output:

Run npm run dev and you will see something like:

  ▲ Next.js 15.x.x
  - Local:        http://localhost:3000
  - Environments: .env.local
  ✓ Starting...
  ✓ Ready in 2.1s

Next.js is running a Node.js server that handles every request — running your server components, serving static files, and proxying to the Next.js runtime.

File System Routing — The Mental Model

Every page.tsx file inside src/app/ defines a route. The folder path becomes the URL path:

src/app/
  layout.tsx          ← root layout — html, body, global providers
  page.tsx            ← / (home page)
  about/
    page.tsx          ← /about
  courses/
    layout.tsx        ← wraps all /courses/* routes (shared nav)
    page.tsx          ← /courses (course list)
    [id]/
      page.tsx        ← /courses/cc-js-m01, /courses/cc-react-m02, etc.
      loading.tsx     ← shown as Suspense fallback while page loads
      error.tsx       ← shown if the page component throws
  api/
    courses/
      route.ts        ← API endpoint: GET/POST /api/courses

This is the entire routing system. No router configuration file. No <Route path="...">. The file system IS the router.

Server Components vs Client Components

This is the most important mental model in Next.js 15.

Server Component (default — no directive needed):

// src/app/courses/page.tsx
// No "use client" = Server Component
// Runs on the server. Can async/await. Ships ZERO JavaScript to the browser.

import { getCourses } from '@/lib/courses'

export default async function CoursesPage() {
  const courses = await getCourses()  // direct function call — no API needed

  return (
    <main>
      <h1>All Courses</h1>
      <ul>
        {courses.map(c => (
          <li key={c.id}>{c.title} — {c.xp} XP</li>
        ))}
      </ul>
    </main>
  )
}

No useEffect. No loading state. No API route needed. The server calls getCourses() directly, gets the data, renders HTML, sends it to the browser. The browser receives fully-rendered HTML with the course list — instant paint.

Client Component (opt-in):

'use client'  // ← this one directive changes everything

import { useState } from 'react'

export function SearchBar({ onSearch }: { onSearch: (q: string) => void }) {
  const [query, setQuery] = useState('')  // useState works now

  return (
    <input
      value={query}
      onChange={e => setQuery(e.target.value)}
      onKeyDown={e => e.key === 'Enter' && onSearch(query)}
      placeholder="Search courses..."
    />
  )
}

'use client' marks a component (and all its imports) as client code. This JS is bundled and shipped to the browser. Use it only for: hooks, event handlers, browser APIs (localStorage, window, geolocation).

The Rule: Push 'use client' As Deep As Possible

// WRONG — entire page is client-side for one interactive element
'use client'
export default async function CoursesPage() { ... }

// CORRECT — only the search bar is client-side
// src/app/courses/page.tsx (Server Component)
import { SearchBar } from '@/components/SearchBar'  // 'use client' inside there

export default async function CoursesPage() {
  const courses = await getCourses()  // server-only logic still works

  return (
    <main>
      <SearchBar />             {/* ← client island */}
      <CourseList courses={courses} />  {/* ← server-rendered */}
    </main>
  )
}

Think of your page as mostly server-rendered HTML with small "islands" of client interactivity.

Root Layout — The App Shell

// src/app/layout.tsx
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'JST Academy',
  description: 'PhD-level crash courses for modern developers',
}

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <nav>JST Academy</nav>
        <main>{children}</main>
        <footer>© 2026 J Supreme Tech</footer>
      </body>
    </html>
  )
}

This layout wraps every page in the app. The metadata export auto-generates <title> and <meta description> tags. children is the current page's rendered output.

0%