Next.js Setup & File-Based Routing
Next.js Setup and Routing
Next.js is the framework most React developers reach for when they need more than a single-page app — server-side rendering, routing, API endpoints, optimized images, and production-ready infrastructure all built in. This lesson gets you set up and explains the App Router, which is the modern way to build Next.js applications.
Why Next.js?
Plain React is great for client-side UIs, but it leaves you to solve a lot on your own:
- How do you handle routing between pages?
- How do you fetch data on the server?
- How do search engines crawl your app?
- How do you serve optimized images?
Next.js solves all of these. It gives React a full-stack context.
Creating a Project
npx create-next-app@latest my-app
You'll see prompts:
Would you like to use TypeScript? › Yes
Would you like to use ESLint? › Yes
Would you like to use Tailwind CSS? › Yes
Would you like to use the src/ directory? › Yes
Would you like to use App Router? › Yes
Would you like to customize the import alias? › No
Start the dev server:
cd my-app
npm run dev
Open http://localhost:3000 — you have a working Next.js app.
The App Router File Structure
Next.js uses the filesystem as the router. Every folder inside src/app/ becomes a URL route:
src/app/
├── layout.tsx → Root layout (wraps everything)
├── page.tsx → Homepage: /
├── globals.css → Global styles
├── about/
│ └── page.tsx → /about
├── courses/
│ ├── page.tsx → /courses
│ └── [slug]/
│ └── page.tsx → /courses/react-complete
├── blog/
│ ├── page.tsx → /blog
│ └── [id]/
│ └── page.tsx → /blog/123
└── api/
└── users/
└── route.ts → /api/users (API endpoint)
The rules are simple:
- A folder creates a URL segment
- A
page.tsxfile makes that URL accessible - Square brackets
[param]create dynamic segments
page.tsx — The Route Component
Every page.tsx exports a default React component. For static pages, just return JSX:
// src/app/about/page.tsx
export default function AboutPage() {
return (
<main className="max-w-4xl mx-auto px-4 py-16">
<h1 className="text-4xl font-bold">About Us</h1>
<p className="mt-4 text-gray-600">
We build practical courses for developers.
</p>
</main>
);
}
layout.tsx — Shared Wrapper
A layout.tsx wraps all pages at that level and below. The root layout is required:
// 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: "AiTechWorlds — Learn Modern Development",
description: "Practical courses in React, Python, AI, and more.",
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body className={inter.className}>
<nav>/* Navigation goes here */</nav>
{children}
<footer>/* Footer goes here */</footer>
</body>
</html>
);
}
Nested layouts work too:
// src/app/dashboard/layout.tsx
// Wraps all routes inside /dashboard/**
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex">
<aside className="w-64 bg-gray-900 min-h-screen">
{/* Sidebar navigation */}
</aside>
<main className="flex-1 p-8">{children}</main>
</div>
);
}
Dynamic Routes
Wrap a folder name in [brackets] to create a dynamic segment:
// src/app/courses/[slug]/page.tsx
interface Props {
params: { slug: string };
}
export default function CoursePage({ params }: Props) {
return (
<div>
<h1>Course: {params.slug}</h1>
</div>
);
}
Now /courses/react-complete, /courses/python-2026, etc. all use this component.
Catch-All Routes
[...slug] catches multiple segments:
src/app/docs/[...slug]/page.tsx
Matches /docs/intro, /docs/react/hooks, /docs/react/advanced/patterns
// params.slug is an array: ["react", "advanced", "patterns"]
export default function DocsPage({ params }: { params: { slug: string[] } }) {
return <div>{params.slug.join(" / ")}</div>;
}
Linking Between Pages
Use Next.js's <Link> component — it prefetches linked pages automatically:
import Link from "next/link";
// Basic link
<Link href="/courses">Browse Courses</Link>
// Dynamic link
<Link href={`/courses/${course.slug}`}>
{course.title}
</Link>
// With replace (no back button history)
<Link href="/dashboard" replace>
Go to Dashboard
</Link>
Never use plain <a href="..."> for internal navigation — it causes a full page reload.
Programmatic Navigation
"use client";
import { useRouter } from "next/navigation";
function LoginForm() {
const router = useRouter();
async function handleSubmit() {
await login(credentials);
router.push("/dashboard"); // navigate
router.replace("/dashboard"); // navigate without history entry
router.back(); // go back
router.refresh(); // re-fetch server data
}
}
Note the import: next/navigation, not next/router (that's the old Pages Router).
The usePathname and useSearchParams Hooks
"use client";
import { usePathname, useSearchParams } from "next/navigation";
function NavLink({ href, children }: { href: string; children: React.ReactNode }) {
const pathname = usePathname();
const isActive = pathname === href;
return (
<Link
href={href}
className={isActive ? "text-blue-600 font-semibold" : "text-gray-600"}
>
{children}
</Link>
);
}
function SearchResults() {
const searchParams = useSearchParams();
const query = searchParams.get("q") ?? "";
return <p>Results for: {query}</p>;
}
Static Generation with generateStaticParams
For dynamic routes where you know all possible values at build time, generate them statically:
// src/app/courses/[slug]/page.tsx
export async function generateStaticParams() {
const courses = await fetchAllCourses();
return courses.map(course => ({
slug: course.slug,
}));
}
export default function CoursePage({ params }: { params: { slug: string } }) {
// This component runs at build time for each slug
return <div>{params.slug}</div>;
}
Next.js pre-renders a static HTML file for every returned slug. Fast load times, SEO-friendly, no server needed.
Page Metadata
Export a metadata object or a generateMetadata function for SEO:
// Static metadata
export const metadata = {
title: "Courses — AiTechWorlds",
description: "Learn React, Python, and AI from scratch.",
};
// Dynamic metadata (fetches data for title/description)
export async function generateMetadata({ params }: Props) {
const course = await getCourse(params.slug);
return {
title: `${course.title} — AiTechWorlds`,
description: course.description,
openGraph: {
title: course.title,
description: course.description,
images: [course.ogImage],
},
};
}
Not Found and Error Pages
// src/app/not-found.tsx — shown for any unmatched route
export default function NotFound() {
return (
<div className="min-h-screen flex flex-col items-center justify-center">
<h1 className="text-6xl font-bold text-gray-900">404</h1>
<p className="mt-4 text-gray-600">This page doesn't exist.</p>
<Link href="/" className="mt-6 text-blue-600 hover:underline">
Go home
</Link>
</div>
);
}
// src/app/error.tsx — shown when a page throws
"use client";
export default function ErrorPage({ error, reset }: { error: Error; reset: () => void }) {
return (
<div>
<h2>Something went wrong</h2>
<button onClick={reset}>Try again</button>
</div>
);
}
Next lesson: Server vs. Client Components — the most important concept to understand in modern Next.js.
Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises