一、创建项目
npx create-next-app@latest
二、基于文件的路由机制
三、动态路由参数
注意:在匹配 URL 时,静态路由优先权大于动态路由
1、基础使用
http://localhost:3000/about/1
http://localhost:3000/about/1
import {useRouter} from 'next/router' //引入钩子函数
export default function AboutProjectPage() {
const router = useRouter() //使用钩子函数
return (
<>
<div>
About Project. The id is: {router.query.id} //获取动态路由参数
</div>
</>
)
}
2、多层路由参数
还可以使用多层的路由参数
3、多个参数
变成数组
四、路由跳转
1、Link
(1)、基本使用
import Link from 'next/link'; //引入
<Link href="/"> //使用
首页
</Link>
(2)、传入UrlObject
interface UrlObject {
auth?: string | null | undefined;
hash?: string | null | undefined;
host?: string | null | undefined;
hostname?: string | null | undefined;
href?: string | null | undefined;
pathname?: string | null | undefined;
protocol?: string | null | undefined;
search?: string | null | undefined;
slashes?: boolean | null | undefined;
port?: string | number | null | undefined;
query?: string | null | ParsedUrlQueryInput | undefined;
}
2、编程式导航(router.push、router.replace)
3、404页面
pages 文件夹下创建特殊文件 404.js,Next.js 将会在返回 404 错误时,自动加载组件。
相当于用户可以自定义 404 页面。
五、静态文件
根目录 public 目录下放静态文件,Next.js 会自动处理,放在这个文件夹外的静态文件是无法获取到的。
六、css模块
1、组件样式
Next.js 通过 js组件的名称.module.css 文件命名约定来支持 CSS 模块。
只影响所绑定的那个组件
2、全局样式
在根目录下的styles写globals.css
引入
七、SSG
SSG 是静态站点生成,就是在文件打包阶段,预先生成页面。
Next.js 默认会预渲染所有没有动态数据的页面,而动态的数据还是像 React 一样在客户端渲染的。
如果要在 HTML 源码中展现动态数据,可以使用 page 下 getStaticProps 方法。这个方法是跑在服务端环境下的,可以在服务端获取数据并渲染,并且客户端不会收到方法内任何的代码。
此外,Next.js 拓展了一些功能,比如 fetch 是浏览器的接口,在服务端是不能用的,而在getStaticProps 方法中是可以使用 fetch API 的。
1、getStaticProps
getStaticProps 方法返回值类型如下,一共有三种情况:
export type GetStaticPropsResult<P> =
| { props: P; revalidate?: number | boolean }
| { redirect: Redirect; revalidate?: number | boolean }
| { notFound: true; revalidate?: number | boolean }
(1)、基本使用
一般正确获得数据后,返回值是第一种情况。
(2)、revalidate
(3)、redirect、notFound
获取数据失败时,引导用户进行下一步操作,重定向或直接返回404错误。
(4)、获取动态路由参数(context)
pages/[pid].js
无法跳转到[pid].js
因为[pid]是动态的,nextjs无法提前渲染
要借助 getStaticPaths
2、getStaticPaths
文件名带[]的js如果需要生成静态页面,需要使用 getStaticPaths 方法。
getStaticPaths 方法定义了一组需要生成静态页面的列表,每项数据都会调用 getStaticProps 来获取数据,所以要使用 getStaticPaths 一定先要有定义 getStaticProps。
(1)、基本使用
pages/[pid].js
此时打开页面,是将所有的json全部发送给浏览器
(2)、fallback
- fallback 值为 false 时,如果 URL 请求参数不在 paths 属性中定义了,那么会直接返回404页面。
- fallback 值为 true 时,如果通过 Link 组件在页面中导航,那不会有问题。但是如果是直接在 URL 中访问未在 paths 中列出的路径,会直接报错,需要在 React 组件中判断对应 props 的参数,在服务器还未准备好时,先返回一个加载中的提示。
- 除了布尔值,fallback 还可以赋值为 'blocking',请求页面时如果数据未准备好就阻塞请求,等待页面渲染完毕后再返回页面。相比第二种情况,相当于免除了组件中判断这一环节。
fallback 值为 true 时,如果访问不存在的请求路径,就可以在 getStaticProps 中直接返回 { notFound: true } 来返回 404 页面。
八、SSR
SSR 是服务端渲染,getServerSideProps 方法可以针对每次请求作出处理,适用于数据变化比较频繁的页面。
getStaticProps 与 getServerSideProps 只能二选一。
getServerSideProps 也是运行在服务器上的方法,这个方法的参数 context 可以完整获取请求的所有数据,context 的类型如下:
export type GetServerSidePropsContext<
Q extends ParsedUrlQuery = ParsedUrlQuery,
D extends PreviewData = PreviewData
> = {
req: IncomingMessage & {
cookies: NextApiRequestCookies
}
res: ServerResponse
params?: Q
query: ParsedUrlQuery
preview?: boolean
previewData?: D
resolvedUrl: string
locale?: string
locales?: string[]
defaultLocale?: string
}
getServerSideProps 返回值类型:
export type GetServerSidePropsResult<P> =
| { props: P | Promise<P> }
| { redirect: Redirect }
| { notFound: true }
getServerSideProps 的返回值类型基本同 getStaticProps,只是少了 revalidate 属性,因为getServerSideProps 会对每次请求进行重新渲染。
(1)、基本使用
九、不适合预渲染的情况
- 数据变化非常频繁的页面(比如股票数据)
- 与用户身份高度耦合的页面(比如用户最近xxx的xxx)
- 页面中只有某一小部分数据不同的情况
碰到这些情况,还是在客户端使用 useEffect 中 fetch 来获取数据,Next.js 团队也编写了一个React 钩子库 SWR(https://swr.vercel.app/zh-CN) 来简化客户端请求,示例如下:
每次当页面获得焦点时,swr钩子都会重新发送请求获取数据
npm i swr
import useSWR from 'swr'
const fetcher = (...args) => fetch(...args).then((res) => res.json())
function Profile() {
const { data, error } = useSWR('/api/profile-data', fetcher)
if (error) return <div>Failed to load</div>
if (!data) return <div>Loading...</div>
return (
<div>
<h1>{data.name}</h1>
<p>{data.bio}</p>
</div>
)
}
十、增加Meta信息
1、基本使用
2、Head组件复用
3、全局通用Head
全局通用的 Head 组件可以添加在 _app.js 这个文件中。此外,相同的头标签会合并,合并的规则就是最后渲染的 Head 覆盖之前渲染的 Head。
另一个全局特殊 JS 文件是 /pages/_document.js,这个文件的默认值如下:
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
_app.js 这个文件相当于 body 中的内容,_document.js 相当于整个 HTML 文档,比前者更“外面”。注意这里的 Head 组件的引入包与普通页面引入的包不同,不要搞错。
十一、图片优化
Next.js 提供了优化图片的方案——Image 组件。
使用 Image 组件有四点好处
- 对各个设备使用合适的尺寸与格式(使用Chrome访问页面时,图片会转换成webp格式)
- 防止Cumulative Layout Shift(累计布局偏移)
- 图片在视图中才会被加载
- 可以自定义图片尺寸
// ...
import Image from 'next/image'
export default function About(props) {
return <>
{/* ... */}
<Image
src={'/img.jpeg'}
alt="图片"
width={100}
height={100}
/>
<img
src={'/img.jpeg'}
alt="图片"
/>
</>
}
Next.js 会根据 Image 的 width 与 height 值,在页面请求服务端时,转换并缓存相应大小的图片。
十二、API 路由
/pages/api 文件下的 JS 文件不会导出页面组件,Next.js 会将这些文件映射成 /api/* 的 API 端点。Next.js 团队在 NodeJS 的 http 模块之上封装,提供了类似 express 的 web 服务器开发功能。
我们可以在这这些文件里写服务端的逻辑,同 getStaticProps 方法一样,这些逻辑客户端是看不到的。
这些API路由的基本格式如下:
export default function handler(req, res) {
if (req.method === 'POST') {
// 处理POST请求
} else {
// 处理其他HTTP方法请求
}
}
1、基本使用
pages/index.js
post请求
get请求
pages/api/feedback.js
post请求
get请求
2、动态api路由
使用
十三、部署Next.js
1、构建
构建 Next.js 应用有两种方式:
(1)、next build
第一种是“标准构建”,使用命令 next build 构建,课程之前构建都是使用这种方式。
使用这种方式构建,我们会得到优化后的前端项目 + 一个 NodeJS 服务端程序。这个服务端程序提供了 API 路由、SSR 与页面重验证等功能。所以如果要部署这个应用,需要服务器有NodeJS 环境。
(2)、next export
第二种构建方式是静态打包,使用命令 next export 构建。
使用这种方式生成的代码,只会包含纯前端的内容,HTML、CSS、JS 以及静态资源。没有 NodeJS 服务端程序,所以部署可以不需要 NodeJS 环境。当然这样的话,API路由、SSR 等 Next.js 提供的特性就不能使用了。
2、配置
项目根目录 next.config.js 文件,可以对 Next.js 进行配置。
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
}
module.exports = nextConfig
这个文件中的代码也是服务端代码,在构建过程中以及构建生成的 NodeJS 服务端程序中会使用到。此外这个文件不会被 Webpack, Babel 或 TypeScript 处理,所以确保使用与机器NodeJS 版本相匹配的语法。
具体配置项看官方文档
十四、加密
课程使用 bcryptjs 包来实现加密逻辑,通过下列命令安装:
npm i bcryptjs
bcryptjs 包我们只要关注两个函数,分别是加密 hash 和比较 compare 。注意两个方法都是异步的。
import { hash, compare } from 'bcryptjs'
// ...
// 通过 hash 函数加密明文密码
const hashedPwd = await hash(pwd, 12)
// ...
// 通过 compare 函数比较两个密码是否相同,返回布尔值
const isValid = await compare(newPwd, hashedPwd)
十五、鉴权
npm install next-auth
1、后端生成jwt令牌
next-auth 包提供了前后端鉴权需要的逻辑,在 API 路由中,创建特殊文件 /api/auth/[...nextauth].js,在文件内引入 next-auth 包并实现相关逻辑:
import NextAuth from "next-auth"
import CredentialsProvider from "next-auth/providers/credentials"
export const authOptions = {
providers: [
CredentialsProvider({
name: 'Credentials',
session: {
strategy: "jwt",
},
async authorize(credentials, req) {
// 自己实现验证逻辑
const res = await fetch("/your/auth", {
method: 'POST',
body: JSON.stringify(credentials),
headers: { "Content-Type": "application/json" }
})
const user = await res.json()
// 一切正常返回用户信息
if (res.ok && user) {
return user
}
// 用户信息没获取到就返回null
return null
}
})
// ...其他 providers
],
}
export default NextAuth(authOptions)
2、在前端组件中,使用next-auth/react模块的signIn函数登录
import { signIn } from "next-auth/react"
export default () => <button onClick={() =>
signIn('credentials', { redirect: false, username: 'username', password: 'password' })}>登录</button>
3、客户端通过 useSession 钩子获取鉴权信息
import { useSession } from "next-auth/react"
export default function About(props) {
const { data: session, status } = useSession()
console.log('session ', session)
console.log('status ', status)
return <>
{/* ... */}
</>
}
v4版本 next-auth 需要 SessionProvider 才能使用上面这个钩子,在 _app.js 中增加相关代码:
import {SessionProvider} from "next-auth/react"
import '../styles/globals.css'
export default function App({Component, pageProps: {session, ...pageProps},}) {
return <SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
}
4、使用 next-auth/react 模块的 signOut 函数登出用户:
import { signOut } from "next-auth/react"
export default () => <button onClick={() => signOut()}>登出</button>
十六、路由守卫
在客户端侧,可以使用 getSession 获取当前的鉴权信息,在需要权限的页面,可以判断 session 的值进行进一步操作。
在服务端侧,可以在 getStaticProps 方法中使用 unstable_getServerSession 函数来获取 session,v4版本中,getSession 不可以在服务端使用。
import { unstable_getServerSession } from "next-auth/next"
import { authOptions } from "./api/auth/[...nextauth]"
export async function getServerSideProps(context) {
return {
props: {
session: await unstable_getServerSession(
context.req,
context.res,
authOptions
),
},
}
}