前言
nextjs 我看挺火的,很多人都在使用,这里也可以学习使用一下,在学习的过程中,也看到了他很优秀的一面,同时肯定也有着比较拉跨的一面,没办法一个工具也不可能哪里都好,另外都坏就要怀疑它存在的价值了
个人看到的一些优缺点(写到了开头,就一部分哈)
- 入门很快,有 react 基础更快,也支持 sass(对于less貌似不太友好,个人尝试了总是报错哈),支持直接写后端接口(尤其是后端业务比较少的,很推荐),数据库结合 prisma 使用很方便
- 支持 SSR、SSG、CSR,也就是我们说的服务器渲染、服务端静态生成、客户端渲染功能,会编译成很多单页面,单页访问速度变快,非常适合用户端,另外有些功能是 react 不具备的,很灵活,尤其是一些需要推广的,这个很适合
- 分为 App router、pages router 两种模式,也可以一起使用,像后端那种嵌套式的路由界面 App router绝对适应,页面简单的 pages router更推荐,且前面提到的 SSR、SSG的一些功能,只有这个模式能用
- 开发 debug 调试,现编译页面,速度极慢(大问题哈),严重影响开发效率,开了开发者工具更甚至(如果功能写的差不多,可以build + start解决)
- 里面的一些组件并不好用,一些基础功能也没那么好用,对于个别组件兼容一般哈,目录即页面的方式可能有些人也不太适应(个人用着还挺喜欢哈)
- 跟 react 比,似乎也没有没有必换的理由,尤其是嵌套式路由,除了单页面模式访问慢,对于服务端渲染(和一些支持的操作),实际上并没有存在特别大的优势,那些功能实际上也不是一个最大卖点,静态页面加速单页面访问速度倒是不错
- 说是支持后端,但是实际上后端业务稍微多一点,稍微严谨一点,就会体会到这里开发后端的痛苦,直接换 nestjs 最好
- 其他就不多说了,避免有些人懒得学哈,毕竟优点大于缺点
接入 nextjs
创建项目
//指令加上项目名称,直接安装最近版本
npx create-next-app@latest project-name
安装基础环境
npm install next@latest react@latest react-dom@latest
启动项目
测试,直接 yarn dev 即可
//测试,进入每个页面都会现编译,也是他的缺点,没办法
yarn dev
//如果项目写的差不多,暂时不需要改变,自己整体看一下测试一下,则可以build 在运行
yarn build && yarn start
安装 pritter
安装库
yarn add prettier --dev
在项目根目录(package.json)创建一个 .prettierrc 文件,里面编写如下件
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": false, //结尾分号
"singleQuote": true //单引号
}
若想保存(control-s、command-s)即格式化,那么直接 vscode 下载一个 prettier插件,然后按照说明走即可
应用在项目中,直接根目录创建一个 .vscode 文件夹,里面创建 settings.json 文件,里面内容如下所示,即可实现保存格式化了,我们只格式化 js 和 ts 文件
{
"editor.formatOnSave": true,
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
安装 sass(scss)
这里面默认没有 less 的,less按照其他设置也不行,就使用 sass 吧,这个跟 less 也差不了多少,挺好用的
要用 Tailwind 的可以自己配置哈,附近就有
yarn add sass --dev
在 next.config.mjs 中添加下面操作
import path from 'path';
const nextConfig = {
sassOptions: {
includePaths: [path.join(__dirname, 'styles')], //我这里是不行哈
}
}
上面的会出现__dirname路径错误,这边没办法获取重命名一下吧
import path from 'path';
import { fileURLToPath } from 'url';
const __dirnameNew = path.dirname(fileURLToPath(import.meta.url));
const nextConfig = {
sassOptions: {
includePaths: [path.join(__dirnameNew, 'styles')],
}
}
到这里就准备差不多了,可以直接进入正题了
nextjs介绍
说明一点,前端页面路径即路由哈
pages router、app router简介
nextjs 目前主要分为两种模式,一种是早期的 pages router 模式,一种是后面的 app router 模式,看文档也要注意这两种模式,有些东西是不一样的
pages router 对于后端页面那种嵌套路由时的支持不太好,但好在有 SSR、SSG 模式,由于是单页面,对于一些需要推广的功能很是友好,很适合给用户端使用,也是很多地方比较推荐的(对于ssr、ssg一些标记的功能,看着挺好,但并不是什么场景都要用到,并且个人感觉有的有些鸡肋哈,有点被忽悠的很高大上的感觉,整体感觉挺好,就是吹得厉害了)
app router 增加了 layout、page,可以很好的支持嵌套路由的页面,但不支持 SSR、SSG这种特殊功能,这个很棒,完美解决了写后端页面的问题(众所周知,很多前端项目写的最多的项目就是给后端维护人员使用的页面了)
需要注意的是,这两个并不冲突,可以在一个项目中同时使用,也就意味着基本没有太多烦恼了,有人可能会很在意 SSR、SSG这东西,后面会介绍,这东西一般不会使用,滥用反而会造成一些麻烦,很多项目都用不到,就算不知道也不影响开发,没有想象中那么高大上,只是渲染单页面时,补充了以前 React 无奈舍弃的一些功能,有的则是可有可无哈,总体挺好的
ps:无法接受调试过慢的直接离开吧,急性子略显暴躁症的不要使用这个debug开发,否则噩梦,这种推荐 react、vue(尤其是vue开发习惯的,可能要疯掉哈),就是 dev 模式下,每个页面单独渲染,加载更新很慢,并且打开控制台更是慢上加慢,这一句就懂了吧,提示编译完毕后,测试两三个页面中间等个一二十秒甚至可能更多,不过分哈😂
个人所说的嵌套路由这种页面大体长下面这样,前面的每一层路由可能就是一个侧边栏、工具栏,实际可能比这个要复杂哈
目录结构
简单介绍下,实际上推荐的就这两种目录,如果两种模式都用,比较就用第二种吧,毕竟资源路径一目了然,如果只用其中一种,那么这两种都行,看自己习惯爱好了
目录、路由介绍(包含后端路由)
app router 模式下,所有的页面都要创建到 app 目录下,以 文件夹 / page.tsx 的方式
app模式下,我们的每一个路由都是一个文件夹 / page.tsx,文件夹就是我们的路由名字,page.tsx 存在代表有这个页面,否则没有,例如:/app/login/page.tsx这个路径就代表可以访问这个路由.../loginapp模式下,每个文件夹还支持编写一个layout.tsx,这个是只要走到这个路由下,一定会渲染出来的,我们的侧边栏功能就是要写到这里的,只要访问这个路由,一定会渲染出来,例如:/app/dashboard/layout.tsx,访问.../dashboard/...则会将layout的内容也渲染出来,我们的layout一般也会加上 children,给我们嵌套的子页面预留app模式下,layout.tsx和page.tsx都不是必须存在的,空目录就什么也不显示占一个路由位置罢了,一个文件夹下,如果只有layout则要渲染layout内容,只有 page,就是一个普通页面,两个都有,那么就是 本layout 嵌套一个 本page 子页面,路由显示和目录一样罢了,即:app/dashboard/layout.tsx|page.tsx,访问.../dashboard时显示的实际上和.../dashboard/page效果一样,有layout、有page,这里效果看起来一样并不是占据了 page 路由哈
pages router 模式下,所有的页面都要创建到 pages 目录下,以 文件名.tsx 的方式创建
pages模式下,我们的每一个 tsx 文件都是一个单页面,支持文件夹,但没有 layout 这个东西了,适合非嵌套页面,如果非要用,那么组件套子视图也是可以的(页面固定的话,使用SSG可以提高效率)pages路由中创建一个api的文件夹,里面又叫api routers,里面的每一个文件就是一个接口,添加文件夹相当于多一个路由,就不多介绍了(个人不太推荐这么写哈,当然也会简单介绍下怎么用),因此所有的接口都会有一个前缀api,例如:.../api/login
通过 Pages router大致可以看出,这个模式的 api 接口和页面,设计的有点像,搞到一块了,只不过一个返回页面,一个是返回数据
nextjs 前端页面和组件注意
nextjs 是前后端可以一起写的,其默认功能是后端,因此我们每写一个页面、组件时,需要在上面标记 'use client',否则可能会显示不出来,或者不正常哈
'use client';
export default function App() {
return <div />
}
基础功能使用介绍
如下所示,是我简单创建的几个目录和文件,我们默认进入的就是 app 的 page.tsx 页面中,(但是 app router 中 先走 layout.tsx,其才是我们能看到的第一个哈)
page.tsx 实际上就是我们的 https://localhost:3000初始页面,也就是我们的 app入口 哈,有些会让其跳转登录页面;有人会让其设置成空页面预留,不使用,以其他子页面为基准(主动导航过去),例如:重定向;也有人将其作为一个判断页面,登录后就直接跳转到用户主页,没登录直接跳转登录,等等就不多说了
其开发就是引入的 react 可以看出,就是用的 react 那一套,因此 react 能用的,这货基本也能用,但也有一些例外,就是跟其特性有关的,可以明显看到其路由不一样了,因此 react-router-dom 就用不了了,但可以使用其默认的 next/navigation,这个也可以实现跳转,也可以通过 Link 标签
import { useRouter } from 'next/navigation';
const router = useRouter();
//自己注意绝对路径(/开头)和相对路径
router.push('/home');
router.push('/dashboard/user');
router.push('./detail'); //user路径下,使用相对路径,进入其详情页 /dashboard/user/detail
router.push('user/detail'); //user路径下,使用自己路径,进入其详情页 /dashboard/user/detail
//直接返回
router.back()
使用也比较简单,但就是没有了 react-router-dom 的 state 传值了哈,单页面直接使用 query 的方式传递(...?id=123),自己拼写哈,他也没有给留下一个方便的对象方式方式,自己拼吧
个人嫌麻烦,自己简单写了一个,需要其他功能可以自行补充调整哈
import { useRouter } from 'next/navigation';
interface PushType {
(path: string): void;
(path: string, options: Record<string, string | number>): void;
(path: string, params: string[], options?: Record<string, any>): void;
}
export type RouterWithOptionsType = {
push: PushType;
back: () => void;
};
export const useRouterWithOptions = () => {
const router = useRouter();
const newRouter: RouterWithOptionsType = {
push(
path: string,
optionsOrParams?: Record<string, any> | string[],
options?: Record<string, any>
) {
if (optionsOrParams) {
if (Array.isArray(optionsOrParams)) {
for (const value of optionsOrParams) {
path += '/' + value;
}
if (!options) {
router.push(path);
return;
}
optionsOrParams = options;
}
let idx = 0;
for (const key in optionsOrParams) {
const value = optionsOrParams[key];
if (value === undefined) continue;
path += `${idx === 0 ? '?' : '&'}${key}=${value}`;
idx++;
}
}
router.push(path);
},
back() {
router.back();
},
};
return newRouter;
};
有人可能会说,用了这个,我就不使用不了 router 的 query 了么,还得自己引用原来 router 不是,不是还有 useSearchParams 么
const params = useSearchParams();
params.get('id')
params.forEach((value, key) => {})
然后你说,还想要 params 的参数,自己去写去吧,谁知道哪个是你的路由,哪个是你的参数,有得用就行,还挑🤣
pages router的 SSR、SSG,以及CSR介绍
跟标题一样,只有 pages router 模式能够使用的功能,我看很多人吹什么 SSR、SSG 的巨大优点,实际我们也要理性,理解其真正的优缺点才是关键,值不值得使用
nextjs 服务端渲染的好处就是,压力一些给到了服务端,服务端会分离渲染出用户访问的单页面,比起react,用户访问时,单次加载的页面内容变少,自然访问速度快,并且单页面推广时也有好处,毕竟单页面,还可以有自己的标题(当然其他的也可以做到切换标题),对于用户端这种,还能还能降低服务器压力,相比较之下,对于可能会频繁切换的后端页面,react这种一次性获取,反而页面性能上表现更加优秀
此外,后面使用到的 SSR、SSG 函数一个跟这个描述感觉明显没有那么大关系,当然不能说没有关系
SSR 函数可以用于在获取页面渲染前在服务端直接拉取数据,直接返回带数据的html页面,也就是服务端渲染,可以避免暴露一些公开接口的情况,性能方面某种程度上可以说没什么优化,由于其后端预渲染处理逻辑还要调用接口获取数据,甚至整体性能会有所降低,可以根据情况使用,当然如果是针对一些设备比较拉跨的地方使用,由服务端代偿部分开销,未尝不是一种优化
SSG 为静态生成,能够在编译阶段预生成一些静态html资源,可以复用,更像是为了生成一些模版之类的东西(例如有些页面万年不变),当然可以根据情况处理,有些页面访问频率高,基本不更新内容,静态生成未尝不是提高效率的手段
SSR
早期遗留的异步函数(不推荐),可以前端执行,也可以后端执行
export default function Home(props: any) {
return <div />;
}
//这是一个遗留的异步函数,官方推荐使用getServerSideProps、getStaticProps代替它,都存在是都会执行
//其既可以在客户端运行也可以在服务端运行,服务端渲染时其在服务端执行,客户端跳转页面时,在客户端执行
// Home.getInitialProps = async (ctx: NextPageContext) => {
// const res = await fetch('https://api.github.com/repos/vercel/next.js');
// const json = await res.json();
// return { stars: json.stargazers_count };
// };
现在的 SSR,如果要使用,推荐下面函数 getServerSideProps
//可以在pages这种页面使用,app里面不允许使用下面功能
//SSR-服务端渲染(按请求按需渲染页面),页面每次请求都要从服务端给出渲染页面,适用于数据需要频繁更新的页面,这种实际上可以使用csr+请求代替,接口处理好就没事
///pages/article/detail -- /pages/article/collects
export async function getServerSideProps() {
//这里在后端执行,每次请求页面都会执行,请求接口获取最新数据或者固定数据
return {
props: {
info: '我是动态获取到的信息',
},
};
}
export default function Article(props: any) {
const router = useRouter();
return (
<div>
<div>我是article页面</div>
<div>SSR: {props.info}</div>
<div>
SSG: id:{props.id} -- name:{props.name}
</div>
<div
onClick={() => {
router.push('/');
}}
>
返回app
</div>
</div>
);
}
SSG
生成单个静态页面
// 构建时执行一次,SSG 静态生成,数据也固定了,不用每次请求,浏览器会缓存,适用于比较静态的页面(固定)
export async function getStaticProps() {
const info = {
id: '123123',
name: '啦啦啦',
};
return { props: info };
}
export default function Article(props: any) {
const router = useRouter();
return (
<div>
<div>我是article页面</div>
<div>SSR: {props.info}</div>
<div>
SSG: id:{props.id} -- name:{props.name}
</div>
<div
onClick={() => {
router.push('/');
}}
>
返回app
</div>
</div>
);
}
一次生成多个静态页面,通过 path 获取生成的内容
//静态生成所有可能的 path,构建时调用一次
export async function getStaticPaths() {
const paths = ['111', '222', '123'].map((e) => ({
params: {
id: e,
},
}));
return { paths, fallback: false };
}
//根据传递的id生成固定信息,构建时生成一次
export async function getStaticProps({ params }: { params: any }) {
// params contains the post `id`.
// If the route is like /posts/1, then params.id is 1
const info = {
id: params.id,
name: '啦啦啦' + params.id,
};
// Pass post data to the page via props
return { props: info };
}
//通过上面可以看出,这个页面模版构建时什么样子,整体架构基本就固定了,浏览器会缓存,下次速度就很快
//内容可以使用CSR动态请求,例如 hooks 请求数据内容
export default function Article2(props: any) {
const router = useRouter();
const [count, setCount] = useState(0);
return (
<div>
<div>我是article页面</div>
<div>
id:{props.id} -- name:{props.name}
</div>
<div onClick={() => setCount(count + 1)}>count:{count}</div>
<div
onClick={() => {
router.push('/');
}}
>
返回app
</div>
</div>
);
}
CSR
就是和我们的 react 正常开发,到前端后就是走的 CSR 了,例如:例如使用我们的 hooks 更新页面,也可以看出这个跟 SSR 不是一个东西,他们并不冲突,只是在页面构建到渲染之间,他们在不同阶段做了不同的事情罢了
pages router路由
其一个文件就是一个路由,文件夹也是路由的一部分,当然也可以当做一个组件 其 和 app router不一样,app router 是一个文件夹是一个路由(要有 page 文件)
pages router HEAD 标题
对于 page router 这种单页面来说是可以直接便利地设置自己的标题的,毕竟有自己独立的 html,这个是有利于 seo 的
当然 app router 就和 react 一样,属于一个大页面模式,不可以设置自己的独立标题
所以他们两个使用场景要注意了,可以混合使用,但不要写错地方
<>
<Head>
<title>home子标题akjsdhkfashkfahsdkjfhaskdjfhkasjd</title>
</Head>
<div>
...pages router 单页面
</div>
</>
api后端接口
pages 中的 api 文件夹就是写接口的了,但个人感觉开发体验不怎么样,比较适合自己开发稍微简单一些的接口,稍微复杂一些的就不推荐了,还是使用更专业的后台框架(js 推荐 nextjs),那样无论是后期维护还是多人开发,都是非常好的
import type { NextApiRequest, NextApiResponse } from 'next'
type ResponseData<T = any> = {
code: number
data?: T
message: string
}
//接口是必须要写的,有些表单提交是要创建安全通道的,因此接口模式很符合
export default function handler(
req: NextApiRequest,
res: NextApiResponse<ResponseData>
) {
//可以看情况获取自己需要的信息
console.log(req.query, req.cookies, req.method, req.body, req.headers)
res.status(200).json({ code: 200, message: 'home接口访问成功了!' })
// res.redirect('/') //重定向到另一个url
// res.redirect(307, '/') //可以设置重定向状态status
}
//指定配置选项
export const config = {
api: {
bodyParser: {
sizeLimit: '1mb', //设置请求体body大小
},
responseLimit: '8mb', //默认4mb
// responseLimit: false, //关闭限制
},
// Specifies the maximum allowed duration for this function to execute (in seconds)
maxDuration: 5,
}
校验中间件
这个是后端校验中间件,可以用来鉴权 token、cookie 内容,不合适直接给出错误结束接口的访问即可,很像 nestjs 的 guard,不写后端的话基本不需要他了(除非就需要在前端拦截验证一些请求),可以设置匹配路由
import { NextRequest, NextResponse } from 'next/server'
//放到主目录和next.config.mjs同级
export function middleware(request: NextRequest) {
// Check the origin from the request
const token = request.headers.get('token') ?? ''
//验证token,可以使用别人的算法,也可以使用自己的加密算法,使用对称加密验证sign串,秘钥在后台,使用秘钥加解锁
//部分要求高的场景防篡改,可以额外在单个请求中加入自己的非对称加密规则
if (!token) {
return Response.json(
{ success: false, message: '未授权或或已过期' },
{ status: 401 }
)
}
return NextResponse.next()
}
export const config = {
matcher: '/api/:path*',
}
mjs配置文件简介
这个也是稍微得关注一些的,为配置文件,无论是编译、打包、开发都会用到,参考地址
sass 配置
习惯 antd 的可能更倾向于 less, 但这里 less 支持不太好,sass 还是可以的,他们两个挺像稍微学习一下,就可以掌控 scss 文件了
yarn add sass --dev
配置 mjs 文件,导入 path,但由于这里目录参数不识别,创建了个新的目录路径,当然还是看自己
import path from 'path';
//下面的根据自己环境导入
import { fileURLToPath } from 'url';
const __dirnameNew = path.dirname(fileURLToPath(import.meta.url));
const baseUrl = '';
const nextConfig = {
sassOptions: {
// includePaths: [path.join(__dirname, 'styles')], //我这里是不行哈
includePaths: [path.join(__dirnameNew, 'styles')],
},
}
代理
当对接非本项目后台时,可能会有跨域问题,那么直接在 mjs 的 nextConfig 加入下面配置即可,需要注意的是 mac 代理很头痛,经常会访问失败,建议开发阶段让后端直接允许跨域即可
rewrites() {
return [
// 当请求路径符合 /api 时,将请求转发到代理服务器
{
source: '/api/:path*', //':path*'通配符
destination: `${baseUrl}/api/:path*`,
},
];
},
重定向
前端开发中肯定会用到重定向功能,我们需要在这个页面设置重定向(必要也可以走接口的重定向哈),仍然需要在 mjs 的 nextConfig 中加入 redirects 重定向功能,可以重定向单个路由,也可以重定向一组路由
redirects() {
return [
{
source: '/other', //默认地址
destination: '/other/about', //重定向地址
permanent: true,
},
// {
// source: '/',
// destination: '/login',
// permanent: true,
// },
{
source: '/home/:slug', //:slug不写也是一样,替换指定路由
destination: '/other/:slug',
permanent: true,
},
];
},
图片访问
对于远端图片访问,前端使用 image 标签时会访问不到,也需要我们在 nextConfig 配置一下地址
images: {
remotePatterns: [
{
protocol: 'http', //协议
hostname: 'www.baidu.com', //域名
},
],
},
除此之外, nextjs 的 Image 使用稍微有点坑,css 属性只能用于静态图片,网络图片必须要设置 with、heigth属性,互不影响,如果想通用,那么需要自己简单封装一下,别忘了设置 config 的远端地址
import Image from 'next/image';
<Image
style={{
width: 36,
height: 36,
}}
src={
e.head
? e.head
: require('../assets/home_header_avatar.png')
}
width={e.head ? 36 : undefined}
height={e.head ? 36 : undefined}
alt={''}
/>
稍微完整点了
额外加入 reactStrictMode、typescript 在特殊时候,自己无法解决,但是不影响使用的时候,可以开启,一个是关闭严格模式,一个是关闭 typescript 校验
/** @type {import('next').NextConfig} */
//ts版本文件,被声明为mjs
import path from 'path';
import { fileURLToPath } from 'url';
const __dirnameNew = path.dirname(fileURLToPath(import.meta.url));
const baseUrl = '';
const nextConfig = {
sassOptions: {
// includePaths: [path.join(__dirname, 'styles')], //我这里是不行哈
includePaths: [path.join(__dirnameNew, 'styles')],
},
images: {
remotePatterns: [
{
protocol: 'http',
hostname: 'www.baidu.com',
},
],
},
rewrites() {
return [
// 当请求路径符合 /api 时,将请求转发到代理服务器
{
source: '/api/:path*', //':path*'通配符
destination: `${baseUrl}/api/:path*`,
},
];
},
redirects() {
return [
{
source: '/other', //默认地址
destination: '/other/about', //重定向地址
permanent: true,
},
// {
// source: '/',
// destination: '/login',
// permanent: true,
// },
{
source: '/home/:slug', //:slug不写也是一样,替换指定路由
destination: '/other/:slug',
permanent: true,
},
];
},
reactStrictMode: false, //去掉严格模式,例如 antd这个模式导入使用就会报红,但不影响使用
typescript: {
// !! WARN !!,自己、队友、三方插件写的ts有问题,必要时可以用这个,但是不推荐
// Dangerously allow production builds to successfully complete even if
// your project has type errors
ignoreBuildErrors: true,
},
};
export default nextConfig;
最后
就先讲到这里吧,nextjs 使用还是蛮简单的,也相对比较好理解,只要上手写,基本上就会写(有react基础的话),好好了解 page router、app router 方向没问题就 ok 了,想写的很优秀,还是要花点功夫的
ps:无法接受 dev 调试缓慢的,直接离开,会被逼疯哈,强迫症建议先别用,再等等(关闭控制台能提高一点速度哈,但还是很慢)
ps2:能解决 dev 调试编译渲染缓慢的,请在这里讨论,我好好学习一下,非常感谢😂