1. Introduction
2. File Based Routing
2.1. Installation and Folder Sttructure
创建的时候可以不选 src,不会生成 src 目录
npx create-next-app myapp
2.2. Creating Routes
import React from "react";
const about = () => {
return <div>about</div>;
};
export default about;
export default function Home() {
return (
<>
<div>Hello World</div>
</>
);
}
2.3. Nested Routes
2.4. Dynamic Routes
2.5. Slug (Long Routes)
2.6. Extracting Values of Route Params
import { useRouter } from "next/router";
const BookDetails = () => {
const router = useRouter();
console.log(router);
return <div>BookDetails - {router.query["id"]}</div>;
};
export default BookDetails;
import { useRouter } from "next/router";
const Slug = () => {
let slug = [];
const router = useRouter();
slug = router.query.slug;
return (
<div>
<ul>{slug ? slug.map((slug, i) => <li key={i}>{slug}</li>) : <></>}</ul>
</div>
);
};
export default Slug;
3. PROJECT - File Based Routing
3.1. Getting Started and Rendering Books
// pages/books/index.js
import React from "react";
import { books } from "../../data/utils";
const index = () => {
console.log(books);
return (
<div className="">
{books.map((book, i) => (
<div className="" key={i}>
<h1>{book.name}</h1>
<p>{book.description}</p>
<article>Link</article>
</div>
))}
</div>
);
};
export default index;
const books = [
{
id: "1",
name: "Mindset",
description: "this is my first book",
},
{
id: "1",
name: "Mindset",
description: "this is my first book",
},
{
id: "2",
name: "Mindset2",
description: "this is my 2 book",
},
{
id: "3",
name: "Mindset3",
description: "this is my 3 book",
},
{
id: "4",
name: "Mindset4",
description: "this is my 4 book",
},
{
id: "5",
name: "Mindset5",
description: "this is my 5 book",
},
];
const fecthBookFromID = (id) => {
const fetchedBook = books.find((book) => book.id === id);
return fetchedBook;
};
export { books, fecthBookFromID };
3.2. Adding CSS Styles to the Pages
import React from "react";
import { books } from "../../data/utils";
const index = () => {
console.log(books);
return (
<div className="">
{books.map((book, i) => (
<div
style={{
width: 300,
background: "withesmoke",
margin: "auto",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
}}
key={i}
>
<h1>{book.name}</h1>
<p>{book.description}</p>
<article
style={{
border: "1px solid black",
padding: 12,
background: "#ccc",
}}
>
Link
</article>
</div>
))}
</div>
);
};
export default index;
3.3. Adding Link to Navigate to different route
import React from "react";
import { books } from "../../data/utils";
import Link from "next/link";
const index = () => {
console.log(books);
return (
<div className="">
{books.map((book, i) => (
<div
style={{
width: 300,
background: "withesmoke",
margin: "auto",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
}}
key={i}
>
<h1>{book.name}</h1>
<p>{book.description}</p>
<article
style={{
border: "1px solid black",
padding: 12,
background: "#ccc",
}}
>
<Link href={`/books/${book.id}`}>Detail</Link>
</article>
</div>
))}
</div>
);
};
export default index;
3.4. Creating Book Detail Page (Getting book from ID)
import { useRouter } from "next/router";
import { fecthBookFromID } from "@/data/utils";
const BookDetails = () => {
const { query } = useRouter();
const bid = query.id;
const book = fecthBookFromID(bid);
return (
<div
style={{
width: 300,
background: "withesmoke",
margin: "auto",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
}}
key={book.id}
>
<h1>{book.name}</h1>
<p>{book.description}</p>
</div>
);
};
export default BookDetails;
3.5. Summary
4. Pre-rendering and Data Fetching (Static Site Generation)
4.1. Introduction To Pre Rendering
4.2. Two Forms of Pre Rendering
4.3. Creating NextJS App
npx create-next-app myapp
4.4. Creating Data
// public/dummy.json
{
"books": [
{
"name": "Mindest",
"description": "Mindest Book",
"id": "1"
},
{
"name": "Mindest2",
"description": "Mindest Book2",
"id": "2"
},
{
"name": "Mindest3",
"description": "Mindest Book3",
"id": "3"
},
{
"name": "Mindest4",
"description": "Mindest Book4",
"id": "4"
},
{
"name": "Mindest5",
"description": "Mindest Book5",
"id": "5"
}
]
}
4.5. Connecting and Configuring to Firebase
它在 firebase 里导入了 json,创建数据库
4.6. Creating Route Structure
4.8. Using getStaticProps (Static Site Generation)
后端服务我直接用 json-server 了
import { getBooks } from "@/utils/api";
import React from "react";
import Link from "next/link";
const BookName = ({ books }) => {
return (
<div>
<ul>
{books.map((book) => (
<li>
<div>
<h1>{book.name}</h1>
<p>{book.description}</p>
<aritcle>
<Link href={"books/" + book.id}>Go to book</Link>
</aritcle>
</div>
</li>
))}
</ul>
</div>
);
};
export default BookName;
/*
getStaticProps 主要用于构建时落地一些静态数据,
但不同于 getServerSideProps,
getStaticProps 默认情况下只会在构建时执行一次,
之后的每次请求都会使用构建时的数据
在 ISR、SSG 等场景下还有不同的表现。
*/
export async function getStaticProps() {
let books = await getBooks();
return {
props: {
books: books,
},
};
}
// utils/api.js
// fetch是替代XMLHttpRequest的web api
export async function getBooks() {
let res = await fetch("http://localhost:5555/books");
return res.json();
}
4.10. Fetching Book From ID in Detail Page
import { getBookFormId } from "@/utils/api";
import React from "react";
const BookDetail = ({ book }) => {
return (
<div>
<h1>{book.name}</h1>
<p>{book.description}</p>
</div>
);
};
export default BookDetail;
export async function getStaticProps({ params }) {
const book = await getBookFormId(params.id);
return {
props: {
book,
},
};
}
export async function getBookFormId(id) {
const books = await getBooks();
const book = books.find((b) => b.id === id);
return book;
}
4.11. What is getStaticPaths Error
next 不知道 id 到底有多少,不知道需要渲染多少/哪些静态页面
4.12. Using getStaticPaths
import { getBookFormId, getBooks } from "@/utils/api";
import React from "react";
const BookDetail = ({ book }) => {
return (
<div>
<h1>{book.name}</h1>
<p>{book.description}</p>
</div>
);
};
export default BookDetail;
export async function getStaticProps({ params }) {
const book = await getBookFormId(params.id);
return {
props: {
book,
},
};
}
// 提供要渲染的页面的path参数
export async function getStaticPaths() {
const books = await getBooks();
const paths = books.map((book) => ({ params: { id: book.id } }));
return {
paths: paths,
fallback: false,
};
}
4.13. What is Incremental Static Generation
这个方法生成静态页面,但是只在构建期间执行一次,后续数据发生了变化,但是页面不会动态更新数据
4.14. Using Incremental Static Generation
加上 revalidate 就可以指定多久重新获取数据
import { getBookFormId, getBooks } from '@/utils/api'
import React from 'react'
const BookDetail = ({ book }) => {
return (
// ...
)
}
export default BookDetail
export async function getStaticProps({ params }) {
const book = await getBookFormId(params.id)
return {
props: {
book
},
// 10秒后重新获取数据
revalidate: 10,
}
}
// 提供要渲染的页面的path参数
export async function getStaticPaths() {
// ...
}
好像需要手动刷新一下页面才会重新获取
4.15. The fallback key
5. Server Side Generation (Contd.)
5.1. Story Behind Server Side Generation
如果使用 getStaticProps 的 revalidate 来重新获取数据,如果数据量大,则整个页面重新渲染加上请求数据的时间会很长
5.2. What is getServerSideProps
5.3. Using getServerSideProps
import { getBookFormId, getBooks } from '@/utils/api'
import React from 'react'
const BookDetail = ({ book }) => {
return (
// ...
)
}
export default BookDetail
export async function getServerSideProps({ params }) {
const book = await getBookFormId(params.id)
return {
props: {
book,
}
}
}
```
5.4. getStaticProps vs getServerSideProps (Which form to use and when)
5.5. Summary
6. Creating API and Full Stack Apps Using NextJS (API Routing)
6.1. Introduction to API Routing
6.2. Overview and Demo Of The Application That We Are Building
6.3. About API Folder
npx create-next-app book_crud
import { useState } from "react";
export default function Home() {
const [name, setName] = useState("");
const data = fetch("/api/hello/")
.then((res) => res.json())
.then((data) => setName(data.name));
return <div className="">{name}</div>;
}
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
export default function handler(req, res) {
res.status(200).json({ name: "John Doe" });
}
7. API Routing Project ( Full Stack NextJS)
7.1. Creating the API of our application
import fs from "fs";
import path from "path";
function handler(req, res) {
if (req.method === "GET") {
const filePath = path.join(process.cwd(), "data", "books.json");
const fileData = fs.readFileSync(filePath);
const data = JSON.parse(fileData);
console.log(data);
return res.status(200).json({ message: data });
}
}
export default handler;
7.2. Creating Pages and Components to Call the API
// components/BookList.js
import React, { useEffect, useState } from "react";
import BookItem from "./BookItem";
const BookList = () => {
const [data, setData] = useState();
const sendRequest = () => {
fetch("/api/books/")
.then((res) => res.json())
.then((data) => setData(data.message))
.catch((e) => console.log(e));
};
useEffect(() => {
sendRequest();
}, []);
return (
<div>
<ul>
{data &&
data.map((item, i) => (
<BookItem
description={item.description}
name={item.name}
id={item.id}
imgUrl={item.imgUrl}
/>
))}
</ul>
</div>
);
};
export default BookList;
// components/BookItem.js
import React from "react";
const BookItem = ({ name, description, id, imgUrl }) => {
return (
<li>
<img src={imgUrl} alt={name} />
<h3>{name}</h3>
<p>{description}</p>
</li>
);
};
export default BookItem;
// data/books.json
[
{
"name": "Mindest",
"description": "Mindest Book",
"id": "1",
"imgUrl": "https://img9.doubanio.com/view/subject/l/public/s34300626.jpg"
},
{
"name": "Mindest2",
"description": "Mindest Book2",
"id": "2",
"imgUrl": "https://img2.doubanio.com/view/subject/l/public/s34192061.jpg"
},
{
"name": "Mindest3",
"description": "Mindest Book3",
"id": "3",
"imgUrl": "https://img2.doubanio.com/view/subject/l/public/s34249411.jpg"
},
{
"name": "Mindest4",
"description": "Mindest Book4",
"id": "4",
"imgUrl": "https://img2.doubanio.com/view/subject/l/public/s34201041.jpg"
},
{
"name": "Mindest5",
"description": "Mindest Book5",
"id": "5",
"imgUrl": "https://img2.doubanio.com/view/subject/l/public/s34072342.jpg"
}
]
// pages/index.js
import BookList from "@/components/BookList";
export default function Home() {
return <BookList />;
}
7.3. Adding custom CSS Styling to Books
.listContainer {
display: flex;
align-items: flex-start;
justify-content: flex-start;
list-style-type: none;
flex: 1;
align-content: flex-start;
}
.listItem {
margin: 10px;
border: 1px solid #ccc;
width: 16rem;
padding: 1rem;
height: 22rem;
}
.listItem img {
padding-bottom: 0.2rem;
width: 100%;
height: 80%;
}
@media screen and (max-width: 1000px) {
.listContainer {
margin: 0 3rem;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
.listItem {
margin: 2rem;
width: 16rem;
height: 18rem;
}
}
@media screen and (max-width: 725px) {
.listContainer {
display: flex;
justify-content: center;
flex-wrap: wrap;
}
.listItem {
margin: 2rem;
width: 16rem;
height: 18rem;
}
}
import React, { useEffect, useState } from "react";
import BookItem from "./BookItem";
import classes from "../styles/Books.module.css";
const BookList = () => {
// ...
return (
<div>
<ul className={classes.listContainer}>// ...</ul>
</div>
);
};
export default BookList;
import React from "react";
import classes from "../styles/Books.module.css";
const BookItem = ({ name, description, id, imgUrl }) => {
return <li className={classes.listItem}>// ...</li>;
};
export default BookItem;
7.4. Creating API for POST HTTP Request
import fs from "fs";
import path from "path";
function getData() {
const filePath = path.join(process.cwd(), "data", "books.json");
const fileData = fs.readFileSync(filePath);
const data = JSON.parse(fileData);
return data;
}
function handler(req, res) {
if (req.method === "GET") {
const data = getData();
return res.status(200).json({ message: data });
} else if (req.method === "POST") {
const { name, description, imgUrl } = req.body;
const data = getData();
const newBook = {
name,
description,
imgUrl,
id: Date.now(),
};
data.push(newBook);
return res.status(201).json({ message: "Added", book: newBook });
}
}
export default handler;
.container {
width: 50%;
height: 50%;
margin: auto;
margin-top: 20px;
}
.formControl {
display: flex;
flex-direction: column;
justify-content: center;
}
const { default: AddBook } = require("@/components/AddBook");
export default function Add() {
return <AddBook />;
}
import React from "react";
import classes from "../styles/Form.module.css";
const AddBook = () => {
return (
<div className={classes.container}>
<form action="" className={classes.formControl}>
<label htmlFor="name">name</label>
<input type="text" name="name" />
<label htmlFor="description">description</label>
<input type="text" name="description" />
<label htmlFor="imgUrl">imgUrl</label>
<input type="text" name="imgUrl" />
<button type="submit">Submit</button>
</form>
</div>
);
};
export default AddBook;
7.5. Creating AddBook Page to add new book
import React, { useState } from "react";
import classes from "../styles/Form.module.css";
const AddBook = () => {
return (
<div className={classes.container}>
<form className={classes.formControl}>
<label htmlFor="name">name</label>
<input type="text" name="name" />
<label htmlFor="description">description</label>
<input type="text" name="description" />
<label htmlFor="imgUrl">imgUrl</label>
<input type="text" name="imgUrl" />
<button type="submit">Submit</button>
</form>
</div>
);
};
export default AddBook;
7.6. Handling Form Data
import React, { useState } from "react";
import classes from "../styles/Form.module.css";
const AddBook = () => {
const [inputs, setInputs] = useState({
name: "",
description: "",
imgUrl: "",
});
const handleChange = (e) => {
setInputs((prevState) => ({
...prevState,
[e.target.name]: e.target.value,
}));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(inputs);
};
return (
<div className={classes.container}>
<form onSubmit={handleSubmit} className={classes.formControl}>
<label htmlFor="name">name</label>
<input
value={inputs.name}
onChange={handleChange}
type="text"
name="name"
/>
<label htmlFor="description">description</label>
<input
value={inputs.description}
onChange={handleChange}
type="text"
name="description"
/>
<label htmlFor="imgUrl">imgUrl</label>
<input
value={inputs.imgUrl}
onChange={handleChange}
type="text"
name="imgUrl"
/>
<button type="submit">Submit</button>
</form>
</div>
);
};
export default AddBook;
7.7. Sending POST HTTP Request to API
import React, { useState } from 'react'
import classes from '../styles/Form.module.css'
const AddBook = () => {
const [inputs, setInputs] = useState(
{ name: "", description: "", imgUrl: '' }
)
const handleChange = (e) => {
// ...
}
const sendRequest = () => {
fetch('/api/books/', {
method: 'POST',
body: JSON.stringify({
name: inputs.name,
description: inputs.description,
imgUrl: inputs.imgUrl
}),
headers: {
"Content-Type": "application/json"
}
})
.then(res => res.json())
.then(data => console.log(data))
}
const handleSubmit = (e) => {
e.preventDefault()
if (!inputs.name || !inputs.description || !inputs.imgUrl) {
return
}
sendRequest()
}
return (
// ...
)
}
export default AddBook
import fs from "fs";
import path from "path";
const filePath = path.join(process.cwd(), "data", "books.json");
function getData() {
const fileData = fs.readFileSync(filePath);
const data = JSON.parse(fileData);
return data;
}
function handler(req, res) {
if (req.method === "GET") {
const data = getData();
return res.status(200).json({ message: data });
} else if (req.method === "POST") {
const { name, description, imgUrl } = req.body;
const data = getData();
const newBook = {
name,
description,
imgUrl,
id: Date.now(),
};
data.push(newBook);
fs.writeFileSync(filePath, JSON.stringify(data));
return res.status(201).json({ message: "Added", book: newBook });
}
}
export default handler;
7.8. Connecting to Real Database (MongoDB Database)
登录 mongodb 官网,点免费试用
npm i mongodb
7.9. Modifying API For MongoDB Operations
import fs from "fs";
import path from "path";
import mongodb, { MongoClient } from "mongodb";
async function handler(req, res) {
const client = await MongoClient.connect(
`mongodb+srv://malguy2022:${process.env.SECRET}@cluster0.umzcezh.mongodb.net/?retryWrites=true&w=majority`
);
// create db
const db = client.db("books");
if (req.method === "GET") {
const books = await db.collection("books").find().sort().toArray();
if (!books) {
return res.status(500).json({ message: "Internal Server Error" });
}
return res.status(200).json({ message: books });
} else if (req.method === "POST") {
const { name, description, imgUrl } = req.body;
const newBook = {
name,
description,
imgUrl,
id: Date.now(),
};
const generatedBook = await db.collection("books").insertOne(newBook);
return res.status(201).json({ message: "Added", book: generatedBook });
}
}
export default handler;
7.11. Final Optimizations and Validations
import fs from "fs";
import path from "path";
import mongodb, { MongoClient } from "mongodb";
async function handler(req, res) {
const client = await MongoClient.connect(
`mongodb+srv://malguy2022:${process.env.SECRET}@cluster0.umzcezh.mongodb.net/?retryWrites=true&w=majority`
);
// create db
const db = client.db("books");
if (req.method === "GET") {
let books;
try {
books = await db.collection("books").find().sort().toArray();
} catch (e) {
console.log(e);
}
if (!books) {
return res.status(500).json({ message: "Internal Server Error" });
}
return res.status(200).json({ message: books });
} else if (req.method === "POST") {
const { name, description, imgUrl } = req.body;
if (
!name ||
name.trim() === "" ||
!description ||
description.trim() === "" ||
!imgUrl ||
imgUrl.trim() === ""
) {
return res.status(422).json({ message: "Invalid data" });
}
const newBook = {
name,
description,
imgUrl,
id: Date.now(),
};
let generatedBook;
try {
generatedBook = await db.collection("books").insertOne(newBook);
} catch (e) {
console.log(e);
}
return res.status(201).json({ message: "Added", book: generatedBook });
}
}
export default handler;