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 workImport 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)
.cssimport inapp/layout.jsorpages/_app.js - Module CSS
*.module.csscan 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 like
window, 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 named
page.jsx layout.jsxandpage.jsxare 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/abcGlobal Layout - app/layout.jsx
- It must define the
<html>and<body>elements and accept thechildrenprop.
// 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 aparamsobject 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
- Use
<Link>instead of<a> import Link from 'next/link';- Using
<a href='...'will cause the whole page to reload, including the root component. - Can also use the
useRouter()hook: nextjs.org/docs/pages/api-reference/functions/use-router
// 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.jsxmust 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.jsand(shop)/about/page.jswould 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,DELETErequests. - They are **server-side only** by default, meaning no client JavaScript is bundled.
- You can use
NextResponseto 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:
propsandparent props.params: contains the dynamic route parameters (/blog/[id]→props.params.id)props.searchParams: contains current URL's search paramsparent: 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 }) {}