Next.js Tutorial

From Next.js: nextjs.org/learn/dashboard-app


Next.js Documentation

From Next.js: nextjs.org/docs

  • Next.js mounts a component twice during testing, so there might be double console logs, but it's removed in production build

Importing

Next.js does not require an extension in import statements for files.js .jsx .ts .tsx

import MyComponent from '@/src/components/MyComponent.jsx';
import MyComponent from '@/src/components/MyComponent'; // will also work

Import Alias @

Configure jsconfig.json or tsconfig.json to include the following line"@/*": ["./src/*"] in the paths object so @ will still point at the src/ folder.

  • "baseUrl": "./" allows access to files in the root of the project likeimport * from 'store.js'
    • Root relative notation / is reserved for items in the public/ folder
  • "@/*": ["src/*"] gives a shortcut to access the src/ folder likeimport * from '@/components/web/TopBar'
// jsconfig.json / tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"],
      "@/context/*": ["src/components/shared/context/*"]
    }
  }
}

Importing CSS

  • Next.js only allows regular (global) .css import inapp/layout.js or pages/_app.js
  • Module CSS *.module.css can be imported anywhere

Public Folder Assets

  • Files in public/ can be accessed directly at the root - /images/*

Server-Side vs Client-Side Component

  • Components in Next.js are server-side, rendered on the server, by default
  • Server components can NOT use hooks, event handlers, and browser APIs likewindow, document, etc
  • A client-side component must have 'use client'; at the top to be a client component
  • Both server and client components are SSRed

Balancing Server/Client & Stateless/Stateful Component

  • Client Component
    • Client component should also be stateful component
    • Use client/stateful component when you need event handlers, hooks, browser-only APIs (window, document, etc)
    • Primarily for interactive elements, managing states, UI
    • Ideal for interactive components: buttons, forms, etc.
  • Server Component
    • Server component should also be stateless component
    • Use server/stateless component when you want to fetch data or APIs close to the source
    • Good for API keys, tokens, authorization, and other secrets without exposing them to the client
    • Use to run expensive processes that client can't handle
    • Reduce the amount of Javascript sent to the browser
    • Server component can NOT accept client-side callback functions or hooks
    • Ideal for rendering static content, layout, fetching data, or expensive calculation

Official explanation: nextjs.org/docs/app/getting-started/server-and-client-components

// JSX

// Server Component (Stateless Parent)
import Counter from '../components/Counter';

export default function Page() {
  return (
    <div>
      <h1>Server Page</h1>
      <p>This is rendered on the server.</p>
      <Counter /> {/* Stateful child */}
    </div>
  );
}

--------------------------------------------------------------

// Client Component (Stateful Child)
'use client'; // makes it client-side component
import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

Notes

  • The web page should be a server component to make use of metadata
  • To improve SEO, any interactive parts of the page should be a separate React component that is on the client-side

Built-in Router

This section is about the App Router (Next.js version 13+)

Folder Structure

  • Next.js uses file structure within the app/ folder to determine routing
  • The subfolder name sets the route name while the component for that route has to be namedpage.jsx
  • layout.jsx and page.jsx are special files and must retain their names
my-app/
├── app/
│   ├── layout.jsx          // Global layout (header, nav, etc.) and entry point
│   ├── page.jsx            // Home page → /
│   ├── about/
│   │   └── page.jsx        // About page → /about
│   └── blog/
│       └── [id]/
│           └── page.jsx    // Dynamic route → /blog/1, /blog/abc

Global Layout - app/layout.jsx

  • It must define the <html> and <body> elements and accept thechildren prop.
// JSX
// app/layout.jsx
import './globals.css'; // global CSS
import TopNav from './components/TopNav'; // global UI component
import LeftNav from './components/LeftNav'; // global UI component

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

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body id="root">
        <TopNav />
        <div className="container">
          <LeftNav />
          <main className="main-content">{children}</main>
        </div>
      </body>
    </html>
  );
}

Normal Pages - */page.jsx

// JSX
// Home page - app/page.jsx
export default function HomePage() {
  return <h1>Welcome to the Home Page!</h1>;
}

--------------------------------------------------------------

// About page - app/about/page.jsx
export default function AboutPage() {
  return <h1>About Page</h1>;
}

Dynamic Page - */[x]/page.jsx

  • Dynamic pages use brackets [] and a variable name in the file structure.
  • Within the component, use the useParams() function to get aparams object with the key that matches your variable name.
// JSX
// Dynamic blog page - app/blog/[id]/page.js
import { useParams } from 'next/navigation';

export default function BlogPage() {
  const params = useParams(); // { id: '1' }
  return <h1>Blog Post ID: {params.id}</h1>; // if id = 1 → /blog/1
}

Navigating

// JSX
'use client'; // Required to be able to use event handlers and hooks
import { useRouter } from 'next/navigation';

export default function HomePage() {
  const router = useRouter();
  return <button onClick={() => router.push('/about')}>Go to About</button>;
}

Route Groups

A route group can be added by wrapping a folder in parenthesis (), and they will not appear in the route or URL.

  • Route groups can be used to organize routes into groups.
  • Can also be used to define multiple root layouts.
  • Route groups don't automatically inherit the global layout app/layout.jsx
    • A new layout.jsx must be defined or omitted if desired.
  • Group up certain routes to share a layout.
  • To avoid conflicting paths, routes in different groups should not have the same URL paths.
    • (marketing)/about/page.js and (shop)/about/page.js would resolve to/about
my-app/
├── app/
│   ├── layout.jsx          // Global layout
│   ├── page.jsx            // Uses global layout (/)
│   ├── dashboard/
│   │   └── page.jsx        // Uses global layout (/dashboard)
│   └── (no-layout)/        // New route group
│       └── special/
│           └── page.jsx    // This page ignores global layout! (/special)

API Routes

In Next.js App Router, API routes live inside the app/api/ folder. Each folder inside app/api becomes an API endpoint.

  • API routes can handle GET, POST, PUT, DELETE requests.
  • They are **server-side only** by default, meaning no client JavaScript is bundled.
  • You can use NextResponse to return JSON or redirect users.
  • API routes can fetch data from databases or external APIs without exposing secrets.
// app/api/hello/route.js

import { NextResponse } from 'next/server';

// Handle GET requests
export async function GET() {
  return NextResponse.json({ message: 'Hello World!' });
}

// Handle POST requests
export async function POST(request) {
  const body = await request.json();
  return NextResponse.json({ received: body });
}

Example usage: fetch('/api/hello') → returns { message: 'Hello World!' }


Metadata

metadata can only be set on server components

By default, two <meta> tags are always added to a page:

<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />

Static Metadata

Export a Metadata object into a layout.jsx or page.jsx and define the fields.

export const metadata = {
  title: 'My Blog',
  description: '...',
};

export default function Page() {}

Generated Metadata

Use the generateMetadata function to fetch data and return a Metadata object.

  • Parameters: props and parent
    • props.params: contains the dynamic route parameters (/blog/[id]props.params.id)
    • props.searchParams: contains current URL's search params
    • parent: a promise of the resolved metadata from the parent route
export async function generateMetadata({ params, searchParams }, parent) {
  const id = (await params).id;

  // Fetch post information
  const post = await fetch(`https://api.vercel.app/blog/${id}`).then((res) =>
    res.json()
  );

  return {
    title: post.title,
    description: post.description,
  };
}

export default function Page({ params, searchParams }) {}

Streaming Metadata

nextjs.org/docs/app/getting-started/metadata-and-og-images