Week 6

useEffect - Side Effects, Data Fetching & Async State

Browser DevTools - The Network Tab

Next.js - Structure & Routing

Tailwind v4

Next.js - Deployment

Practice

Assignment

Back to Track

Next.js: Structure & Routing

So far you've been writing React components in isolation: a single app with a single view. Most real products have multiple pages: a home page, an about page, a user profile, a settings screen. Managing that in plain React requires setting up a router yourself (with React Router, for example). Next.js gives you one out of the box, and it works differently from anything you've seen before: the file system is your router.

This chapter covers how to set up a Next.js project, what the generated files actually do, and how the App Router turns folders and files into navigable pages.

<aside> 💡

Info

Next.js isn't a niche tool — it's the dominant way React applications are built professionally. According to the 2025 State of JavaScript survey, Next.js is used by 59% of JavaScript developers surveyed InfoQ, making it the most used meta-framework in the ecosystem ZenRows. In 2024, React held 69.9% usage and Next.js 52.9% among frontend developers The Software House (meaning more than half of all React developers are already building with Next.js)

You'll encounter it in job listings, in open-source projects, and in codebases at companies of every size. Learning it now isn't getting ahead of yourself — it's meeting the industry where it already is.

</aside>

What Is Next.js?

Next.js is a framework built on top of React. Where React is a library for building UI components, Next.js adds the full application layer around those components: routing, server-side rendering, static generation, API routes, optimised images, and more.

You already know React — Next.js is how you take that knowledge and build something production-ready. It handles the decisions you'd otherwise have to make yourself (or get wrong).

https://www.youtube.com/watch?v=xnOwOBYaA3w

<aside> 💡

Info

Next.js has two routing systems: the older Pages Router (using a pages/ directory) and the newer App Router (using an app/ directory). The App Router has been the default since Next.js 13 and is where the framework is headed. All new projects should use it, and this is what you'll learn here. If you open an older codebase and see a pages/ directory, that's the Pages Router — the concepts are similar but the file conventions differ.

</aside>

Setting Up a Project

Run this command in your terminal:

npx create-next-app@latest my-app

You'll be asked a few questions. Choose these settings:

Would you like to use TypeScript? › Yes
Would you like to use ESLint? › Yes
Would you like to use Tailwind CSS? › No  (we'll add it in the next chapter)
Would you like your code inside a `src/` directory? › Yes
Would you like to use App Router? › Yes
Would you like to use Turbopack for next dev? › Yes
Would you like to customize the import alias? › No

Once it finishes, open the project and start the development server:

cd my-app
npm run dev

Open http://localhost:3000 — you'll see the default Next.js welcome page. You're running.

<aside> 💡

⌨️ Hands On

Run the setup above, start the dev server, and open http://localhost:3000. Then open the project in your editor and spend two minutes just looking at what was generated before reading the next section. What folders do you see? What files look familiar from your React experience?

</aside>

Understanding the Generated Structure

Here's what create-next-app generates (with the src/ option selected):

my-app/
├── src/
│   └── app/
│       ├── favicon.ico
│       ├── globals.css
│       ├── layout.tsx
│       └── page.tsx
├── public/
│   └── (static assets: images, fonts, etc.)
├── next.config.ts
├── tsconfig.json
├── package.json
└── .eslintrc.json

That's it for what you need to care about right now. The rest of the files are configuration that create-next-app handles for you.

Files to know

src/app/page.tsx — This is your homepage. Whatever this file exports is what renders at /. This is where you'll spend most of your time as you add content to your site.

src/app/layout.tsx — This is the root layout. It wraps every page in your application. Things like your <html> and <body> tags live here, along with anything that should appear on every page — a navigation bar, a footer, a global font. It accepts a children prop which is replaced by the current page.

src/app/globals.css — Global CSS that applies across the whole app. Imported in layout.tsx.

public/ — Static files served directly. An image at public/avatar.png is accessible at /avatar.png in the browser. No imports needed.

next.config.ts — Next.js configuration. You'll rarely need to touch this at your current stage.

tsconfig.json — TypeScript configuration. create-next-app sets this up correctly. Leave it alone unless you know what you're changing.

Files to leave alone

The .next/ directory (created when you first run npm run dev) is Next.js's build output. Never edit it manually — it gets regenerated on every build.

node_modules/ is your installed dependencies. Same rule: never edit, always ignore in version control.

<aside> 💡

Info

Notice the @/ import alias in the generated code. @/ maps to your src/ directory. So import something from '@/components/Button' is equivalent to import something from '../../components/Button' but much cleaner. This is configured in tsconfig.json and works out of the box.

</aside>

How File-Based Routing Works

In the App Router, folders inside app/ define URL segments. The rule is simple:

Without a page.tsx, the folder creates no route — it exists in the file system but produces no page. The file is the thing that makes a route public.

src/app/
├── page.tsx          → renders at /
├── about/
│   └── page.tsx      → renders at /about
└── projects/
    └── page.tsx      → renders at /projects

To add a new page to your app, you create a folder and add a page.tsx inside it. That's the entire workflow.

What goes in a page.tsx

A page is just a React component, exported as the default export:

// src/app/about/page.tsx

export default function AboutPage() {
  return (
    <main>
      <h1>About</h1>
      <p>This is the about page.</p>
    </main>
  );
}

Create this file, and /about immediately becomes a working route. No configuration, no route registration — just the file.

<aside> ⌨️

Hands On

Create a new folder at src/app/about/ and add a page.tsx file that exports a simple component with a heading and a paragraph. Navigate to http://localhost:3000/about in your browser. The page should appear immediately without restarting the dev server.

</aside>

Nested Routes

Routes nest the same way folders nest. To create /projects/web:

src/app/
└── projects/
    ├── page.tsx          → renders at /projects
    └── web/
        └── page.tsx      → renders at /projects/web

Each folder adds a segment to the URL. You can go as deep as you need.

Dynamic Routes

Many pages display content based on a variable — a user ID, a post slug, a product handle. You don't create a separate folder for each one; you use a dynamic segment by wrapping a folder name in square brackets.

src/app/
└── users/
    └── [id]/
        └── page.tsx      → renders at /users/1, /users/42, /users/anything

Inside the page component, the dynamic value is available as a prop:

// src/app/users/[id]/page.tsx

interface Props {
  params: Promise<{ id: string }>;
}

export default async function UserPage({ params }: Props) {
  const { id } = await params;

  return (
    <main>
      <h1>User {id}</h1>
    </main>
  );
}

Navigate to /users/42 and id will be "42". Navigate to /users/sarah and id will be "sarah".

<aside> 💡

Info

Note that params is a Promise in the App Router — you need to await it. This is a Next.js convention that enables certain performance optimisations on the server. The async keyword on the component function is what makes this possible, and it's perfectly valid in Next.js — server components can be async functions.

</aside>

The Root Layout

Open src/app/layout.tsx. It looks something like this:

// src/app/layout.tsx

import type { Metadata } from 'next';
import './globals.css';

export const metadata: Metadata = {
  title: 'My App',
  description: 'Built with Next.js',
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        {children}
      </body>
    </html>
  );
}

Every page in your app renders inside this layout. The {children} is replaced by whatever the current page exports.

This is where you'd put a navigation bar that should appear on every page:

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <header>
          <nav>{/* navigation links go here */}</nav>
        </header>
        <main>{children}</main>
        <footer>
          <p>© 2025 My App</p>
        </footer>
      </body>
    </html>
  );
}

The metadata export at the top is how Next.js handles <title> and <meta> tags. You can export metadata from any page.tsx or layout.tsx to set per-page metadata. Next.js injects it into the <head> automatically.

// src/app/about/page.tsx

export const metadata = {
  title: 'About — My App',
  description: 'Learn more about us.',
};

export default function AboutPage() {
  return <h1>About</h1>;
}

Nested layouts

You can add a layout.tsx to any route folder, not just the root. A nested layout wraps only the pages within that route segment and its children:

src/app/
├── layout.tsx          ← wraps everything
└── dashboard/
    ├── layout.tsx      ← wraps only /dashboard and its children
    └── page.tsx

This is useful for adding a sidebar or secondary navigation that only appears in a specific section of the app, without affecting other pages.

Navigating Between Pages

In standard HTML, you navigate between pages with <a href="/about">. In Next.js, you use the Link component instead:

import Link from 'next/link';

export default function Navigation() {
  return (
    <nav>
      <Link href="/">Home</Link>
      <Link href="/about">About</Link>
      <Link href="/projects">Projects</Link>
    </nav>
  );
}

Link renders as an <a> tag in the HTML, so it's fully accessible — keyboard navigable, works with screen readers, right-click to open in new tab. But under the hood, Next.js intercepts the click and handles the navigation without a full page reload. The user experience is instant, like a single-page app, while the URL and browser history still work correctly.

Why not just use <a>?

A plain <a href="/about"> works, but it triggers a full page reload: the browser discards all current JavaScript state, re-fetches the HTML, re-downloads the JavaScript, and starts over. Link does client-side navigation: only the changed content updates, and any state that lives in the layout (like an open menu or animation) is preserved.