For installing in the current directory ”./”
npx create-next-app@latest ./
To run a component on the client, type on the top of the page:
"use client"
Client rendering needs to be used for:
interactivity and event listeners
useState or useEffect
using browser only APIs
app/ or pages/
├── users/
│ └── route.js => /users (API route)
├── blog/
│ └── page.tsx => /blog
├── ... => other routes & components
lib/
├── connectDb.js => reusable DB utilities
models/
├── User.js => mongoose/ORM models
public/
components/
styles/ => CSS files
types/ => TypeScript definitions
.env
next.config.js
package.json
all Routes need to be inside the app folder
files representing a route should be named: page.js / page.tsx
every folder is a path segment in the URL
app/
├── page.tsx => /
├── about/
│ └── page.tsx => /about
├── blog/
│ ├── page.tsx => /blog
│ └── [id]/
│ └── page.tsx => /blog/random-name
// Dynamic routes with brackets (id or slug):
// params are automatically received as props
export default async function Blog({params}: {params:{id:string}}){
const {id} = await params; // async since nextjs v5
return <h1>Product</h1>
}
app/
├── page.tsx => /
├── (auth)/
├── register/
│ └── page.tsx => /register
└── login/
└── page.tsx => /login
# No need to type "auth" in the url
Share UI between multiple pages, e.g. navigation, footer
layout.tsx
...
return (
<html lang="en">
<body>
<Nav></Nav> // will be visible on all pages
{children}
<Footer></Footer> // will be visible on all pages
</body>
</html>
);
// Place content above or beneath the children props
Place a layout.tsx in a folder which route would need this specific layout
app/
├── page.tsx
├── products/
│ └── [id]/
│ ├── layout.tsx
│ └── page.tsx
# products/[id]/layout.tsx
export default function ProductLayout({children}: {children: React.ReactNode}){
return (
<div>
{children} // renders the component from products/[id]/page.tsx
<h2>Product Header</h2> // will show on all products pages
<div>
)
}
"use client"
import Link from "next/link";
import {usePathname} from "next/navigation";
const pathname = usePathname();
<Link href="/" className={pathname ? "light" : "dark"}>Home</Link>
Programatic Navigation
"use client"
import {useRouter} from "next/navigation";
const router = useRouter();
<button onClick={()=>router.push("/")}>Back Home</button>
Create RESTful endpoints
app/
├── users/
│ ├── route.ts # name is mandatory
# Request: http://localhost:3000/users
users/route.ts
import User from "../models/User.ts";
// GET handler
export async function GET() {
try {
const users = User.find();
return Response.json(users, { status: 200 });
} catch (error) {
return Response.json(
{ message: "Error fetching users" },
{ status: 500 }
);
}
}
// POST handler
export async function POST(request: Request) {
try {
const body = await request.json();
const newUser = User.create(body);
return Response.json(newUser);
} catch (error){
return Response.json(
{ message: "Error fetching users" },
{ status: 500 }
);
}
}
app/
├── users/
│ └── [id]/
│ └── route.ts
# users/route.ts
import User from "../models/User.ts";
export async function GET(request:Request, {params}:{params: {id: string}}) {
try {
const {id} = await params;
const user = User.findById(id);
return Response.json(user);
} catch (error) {
return Response.json(
{ message: "Error fetching user" },
{ status: 500 }
);
}
}
// URL Request: http://localhost:3000/12345
export async function GET(request:Request, {params}:{params: {id: string}}) {}
const {id} = await params; // id = 12345
// URL Request: http://localhost:3000?search=shirt
const { searchParams } = new URL(request.url);
const search = searchParams.get('search'); // search = "shirt"
A check if the model already exists is necessary and to prevent creating multiple instances of a model
import { Schema, model, models } from 'mongoose';
export default models.Post || model("User", userSchema);
Since next.js does not have an entry point like a server.js, the connection needs to be established on an api call
or the experimental instrumentation feature can be used:
next.config.ts
module.exports = {
experimental: {
instrumentationHook: true,
},
}
instrumentation.js (at root level of the project)
// import connectDb to connect with MongoDB
export async function register() { // function name is mandatory
await connectDb()
}
Without instrumentation
libs/connectDb.ts
let isConnected = false;
export default function connectDB() {
if (isConnected) return;
// code to connect to the database
isConnected = true;
}
Next.js handles the fetch in page.tsx automatically with the content of error.tsx and loading.tsx
Example structure:
app/
├── users-server/ # random name
│ ├── error.tsx # naming is mandatory
│ ├── loading.tsx # naming is mandatory
│ └── page.tsx
# user-server/loading.tsx
export default function Loading(){
return <div>Loading...</div>
}
user-server/error.tsx
"use client"
import {useEffect} from "react";
export default function Error({error}:{error: Error}){
useEffect(()=>{
console.log(error);
},[error])
return <div>Error fetching users data!</div>
}
user-server/page.tsx
export default async function UsersServer(){
type User = {
name: string,
email: string
};
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const users = await response.json();
return (
<div>{user.map(user:User)=>(
<p>user.name</p>
)}
</div>
)
}
Asynchronous functions executed on the server
For form submissions, updating database, operations which require server-side execution
File structure:
app/
├── mock-users/ # random name
│ └── page.tsx
# mock-users/page.tsx
import {revalidatePath} from "next/cache"; // to avoid manual page reload
export default async function MockUsers(){
const response = await fetch("https://2032aa902f238eß472d20.mockapi.io/users");
const users = await response.json();
// server action
async function addUser(formData: FormData){
"use server"
const name = formData.get("name");
const response = await fetch("https://2032aa902f238eß472d20.mockapi.io/users",
{
method:"POST",
headers: {
"Content-Type": "applicaton/json",
// Auth would go here
// Authorization: "Bearer YOUR-PRIVATE-KEY"
},
body: JSON.stringify({name})
});
const newUser = response.json();
revalidatePath("mock-users"); // updates data for this path (no reload needed)
console.log(newUser);
};
return (
<div>
<form action={addUser}> // name of server action function
<input type="text" id="name" name="name">
<button>Add User</button>
</form>
</div>
)
}