Next.js
Next.js is a popular web framework built on React, known for its server-side rendering (SSR) support and file-based routing. It provides an excellent developer experience by automatically configuring the necessary tools for React and TypeScript, making it particularly user-friendly for beginners.
Initialize a New Project
To create a new Next.js project, run:
npx create-next-app@latest
It will generated following files:
├── README.md
├── next.config.mjs
├── "package-lock.json"
├── package.json
├── src
│ └── app
│ ├── favicon.ico
│ ├── fonts
│ │ ├── GeistMonoVF.woff
│ │ └── GeistVF.woff
│ ├── globals.css
│ ├── layout.tsx
│ ├── page.module.css
│ └── page.tsx
└── tsconfig.json
Start the dev server:
npm run dev
Routing
Next.js uses file-based routing. For example, the URL
http://localhost:3000/my-page corresponds to the file at
src/app/my-page/page.tsx.
If you access http://localhost:3000/my-page in the browser, you'll
see a 404 page. To create the page, add the following code to
src/app/my-page/page.tsx:
export default function MyPage() {
return <h1>My Page</h1>
}
Once saved, The page in browser should update it self as expected.
Fetching Data
Now let's add a new page src/app/users/page.tsx, to render some dynamic data:
export default async function Users() {
const response = await fetch('https://dummyjson.com/users')
const { users }: {
users: Array<{
id: number,
firstName: string,
lastName: string,
}>
} = await data.json()
return (
<div>
{users.map((user) => (
<div key={user.id}>
<div>
{user.firstName} {user.lastName}
</div>
</div>
))}
</div>
)
}
Visit http://localhost:3000/users to see the rendered page.
Dynamic Routing
To create a user detail page, first, add a link in users/page.tsx:
import Link from 'next/link'
{users.map((user) => (
<div key={user.id}>
<div>
<Link href={`/users/${user.id}`}>{user.firstName} {user.lastName}</Link>
</div>
</div>
))}
Clicking one of the links will lead to a URL like http://localhost:3000/users/26, which initially shows a 404 page.
To handle this route, create a new file at src/app/users/[id]/page.tsx with the following content:
export default async function User({ params }: { params: { id: string } }) {
const response = await fetch(`https://dummyjson.com/users/${params.id}`)
const user: {
firstName: string,
lastName: string,
email: string,
} = await data.json()
return (
<div>
<h1>{user.firstName} {user.lastName}</h1>
<div>{user.email}</div>
</div>
)
}
Once saved the web page in browser should update it self and show the user email.
Server Actions
Server actions are similar to RPCs; the client can invoke server-side functions as if they were client-side. Behind the scenes, it still makes an HTTP request. The compiler and the framework did a lot of work here like code splitting and transforming, so you don't need to manually create routes and call the APIs.
Here's how it looks like. Create a new page at src/app/products/page.tsx with the following content:
export default function Products() {
async function addProduct(data: FormData) {
"use server"
console.log(data.get('name'))
}
return (
<form action={addProduct}>
<div>
<label>
Name: <input name="name" />
</label>
</div>
<button type="submit">Submit</button>
</form>
)
}
Visit http://localhost:3000/products, fill out the form, and submit. In the network panel, you’ll see a POST request to the current path. Additionally, the server-side log will print the name, confirming that the function executed on the server.
useActionState
useActionState allows you to access the result of a form action.
It is a client only hook, and you will have to put "use client" on top of you soruce code file.
"use client"
import { useActionState } from 'react'
import { addProduct } from './action'
export default function Products() {
const [state, submitAction, isPending] = useActionState(addProduct, {message: ''})
return (
<form action={submitAction}>
<div>
<label>Name: <input name="name" /></label>
</div>
<button disabled={isPending} type="submit">Create</button>
<div>{state.message}</div>
</form>
)
}
And the action code have to be moved to a file with "use server":
"use server"
export async function addProduct(prevState: {message: string}, data: FormData) {
return { message: `${data.get('name')} saved` }
}