主题
Next.js 项目搭建
一、环境准备
Next.js 14版本对于Node.js最低的版本要求是 18.17.0,目前我的环境为 18.19.0
VSCode相关插件安装:Prettier - Code formatter,ESlint,Nextjs snippets,vscode-styled-components,Tailwind CSS IntelliSense
二、项目创建
为了更快的安装速度,没有切换镜像的,先切换镜像
bash
npm config set registry https://registry.npmmirror.com运行
bash
npx create-next-app@latest根据提示,创建项目
输入项目的名称
启用Typescript
启用ESLint
使用Tailwind CSS
将src作为目录:方便将业务文件与配置文件区分开来
使用App Router:可使用 Server component 和 Streaming 等新特性
不自定义别名,使用默认的@代表src目录
三、配置 Prettier
ESLint 脚手架已默认安装,现需配置prettier
1. 安装 eslint ts默认规则补充
bash
npm i @typescript-eslint/eslint-plugin@^6.21.0 -D提示:@typescript-eslint/eslint-plugin目前版本已到7.1,但脚手架自带的@typescript-eslint/parser版本为6.21.0,因此安装的时候需手动指定版本。之后如果脚手架升级,只需安装对应版本即可
@typescript-eslint/typescript-estree6.21.0版本不支持typescript高于5.4.0的版本,而项目中的package.json中"typescript": "^5",即安装typescript5的最新版本,目前typescript最新版本为5.4.2,会不兼容,因此需指定typescript的版本
2. 指定Typescript版本
如果已经安装了,先执行npm uninstall typescript进行卸载,然后运行npm i typescript@^5.2.2 -D进行安装
3. 安装 prettier
bash
//允许 ESLint 报告不符合 Prettier 格式化规则的代码
npm i eslint-plugin-prettier@^5.1.3 -D
//用来解决与eslint的冲突
npm i eslint-config-prettier@^9.1.0 -D
//安装prettier
npm i prettier@^3.2.5 -D4. 修改 .eslintrc.json
json
{
"extends": [
"next/core-web-vitals",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"eslint-config-prettier"
],
"plugins": ["prettier"],
"rules": {
"@typescript-eslint/no-explicit-any": ["off"], //允许使用any
"@typescript-eslint/ban-ts-comment": "off", //允许使用@ts-ignore
"@typescript-eslint/no-non-null-assertion": "off", //允许使用非空断言
"@typescript-eslint/no-var-requires": "off", //允许使用CommonJS的写法
"no-console": [
//提交时不允许有console.log
"warn",
{
"allow": ["warn", "error"]
}
],
"no-debugger": "warn" //提交时不允许有debugger
}
}5. 新建 .prettierrc
json
{
"endOfLine": "auto",
"printWidth": 120,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"bracketSpacing": true
}6. 新建 .prettierignore
text
/.next/
/node_modules
.env*.local7. 修改 package.json
修改 lint 启动命令
json
"scripts": {
"lint": "eslint src --fix --ext .ts,.tsx,.js,.jsx --max-warnings 0",
},重启VSCode,运行 npm run lint,可自动修复eslint(prettier/prettier)问题
四、配置 VSCode
每次运行npm run lint修复eslint(prettier/prettier)问题比较麻烦。可配置VSCode,在文件保存时,自动格式化文档
新建.vscode文件夹,在文件夹下新建settings.json
json
{
"editor.codeActionsOnSave": {
"source.fixAll": "explicit"
}
}在插件中选择扩展设置,选择Prettier - Code formatter
将每行代码的长度限制由80改为120(因为 .prettierrc中"printWidth": 120,)
保存之后,重启VScode
五、配置 import 导入顺序
在开发组件时,需要引用外部资源,当import的包很多时,例如有next、react第三方包,有自定义包,有Typescript类型文件,有样式文件等等。
如果对import的顺序不进行排序的话,会显得很杂乱,比如以下导入,在引入自定义组件的时候,可能会被另一个开发者引入类型文件,这时候规范引入顺序就显得很重要了
tsx
import Header from "@/components/header";
import type { Metadata } from "next";
import Tabs from "@/components/tabs";1. 安装
bash
npm i eslint-plugin-import@^2.29.1 -D2. 修改.eslintrc.json,在rules下添加以下配置
json
"import/order": [
"error",
{
//按照分组顺序进行排序
"groups": ["builtin", "external", "parent", "sibling", "index", "internal", "object", "type"],
//通过路径自定义分组
"pathGroups": [
{
"pattern": "react*",
"group": "builtin",
"position": "before"
},
{
"pattern": "@/components/**",
"group": "parent",
"position": "before"
},
{
"pattern": "@/utils/**",
"group": "parent",
"position": "after"
},
{
"pattern": "@/apis/**",
"group": "parent",
"position": "after"
}
],
"pathGroupsExcludedImportTypes": ["react"],
"newlines-between": "always", //每个分组之间换行
//根据字母顺序对每个组内的顺序进行排序
"alphabetize": {
"order": "asc",
"caseInsensitive": true
}
}
]3. 运行 npm run lint,可统一修复排序问题
在开发的过程中,因为配置了VSCode保存时自动修复,因此配置好之后,不需要我们再操心import导入顺序的问题
六、配置 .gitignore
Next.js目前使用基于Rust的编译器SWC来编译JavaScript/TypeScript。SWC的安装受到Node.js版本等因素的影响,如果版本不一致可能导致启动失败,出现类似 'Failed to load SWC binary for win32/x64' 的错误提示。
解决方案一:package-lock.json文件不上传到GitHub上,修改.gitignore,新增
text
#lock
package-lock.json
yarn.lock如果文件已在git暂存区,可运行:
bash
git rm --cached -r package-lock.json七、配置 Git hooks
husky:一个为git客户端增加hook的工具
lint-staged:仅对Git 代码暂存区文件进行处理,配合husky使用
@commitlint/cli:让commit信息规范化
1. 创建 git 仓库
bash
git init2. 安装
bash
//固定husky的版本
npm i husky@8.0.3 -D
npm i lint-staged@^15.2.2 -D
npm i @commitlint/cli@^19.0.3 -D
npm i @commitlint/config-conventional@^19.0.3 -D3. 运行
bash
//生成 .husky 的文件夹
npx husky install
// 添加 hooks,会在 .husky 目录下生成一个 pre-commit 脚本文件
npx husky add .husky/pre-commit "npx --no-install lint-staged"
// 添加 commit-msg
npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'4. 修改package.json
json
"lint-staged": {
"src/**/*.{js,jsx,ts,tsx,json}": [
"npm run lint",
"prettier --write"
]
}5. 新建commitlint.config.js
js
module.exports = {
extends: ["@commitlint/config-conventional"],
};6. 提交格式
text
git commit -m <type>[optional scope]: <description> //注意冒号后面有空格- type:提交的类型(如新增、修改、更新等)
- optional scope:涉及的模块,可选
- description:任务描述
type类型:
| 类别 | 含义 |
|---|---|
| feat | 新功能 |
| fix | 修复 bug |
| style | 样式修改(UI校验) |
| docs | 文档更新 |
| refactor | 重构代码(既没有新增功能,也没有修复 bug) |
| perf | 优化相关,比如提升性能、体验 |
| test | 增加测试,包括单元测试、集成测试等 |
| build | 构建系统或外部依赖项的更改 |
| ci | 自动化流程配置或脚本修改 |
| revert | 回退某个commit提交 |
7. 提交示范
bash
git commit -m 'feat: 增加 xxx 功能'
git commit -m 'bug: 修复 xxx 功能'提示:非规范提交,将commit提交失败
八、配置端口号
Next.js的默认访问端口是3000,可修改启动命令,来改变端口号
修改package.json,在启动命令后增加 " -p 3001 " ,这样开发环境与生产环境的启动端口为3001
json
"scripts": {
"dev": "next dev -p 3001",
"build": "next build",
"start": "next start -p 3001"
},九、配置环境变量
项目根目录新建 .env、.env.development、.env.production、.env.local,分别代表着所有环境变量,开发环境变量,生产环境变量,本地环境变量
覆盖优先级为:.env.local > .env.production | .env.development > .env
1. 定义服务端组件使用的环境变量
text
//.env
PRIVATE_KEY = '12345'在服务端组件中,使用 process.env.PRIVATE_KEY 获取值,在客户端组件中访问,会得到undefined
2. 定义客户端组件使用的环境变量
使用NEXT_PUBLIC前缀定义客户端组件的环境变量
text
//.env.development
NEXT_PUBLIC_BASEURL = 'http://127.0.0.1:9000/api'
//.env.production
NEXT_PUBLIC_BASEURL = 'https://www.test.com/api'使用 process.env. 进行访问
text
process.env.NEXT_PUBLIC_BASEURL3. 配置Typescript类型提示
项目根目录新建typings.d.ts
ts
declare namespace NodeJS {
interface ProcessEnv {
/* 基础路径 */
NEXT_PUBLIC_BASEURL: string;
}
}修改tsconfig.json,在include中将新增文件加进来
json
"include": [
//新增
"typings.d.ts",
],之后再VsCode中输入process.env,将会提示出NEXT_PUBLIC_BASEURL
提示:如果想自定义环境变量的文件位置,或者定义多个工程的环境变量,可使用 dotenv
十、配置临时环境变量
在启动或编译应用程序时,我们常常需要根据不同的参数走不同的配置
1. 安装
bash
npm i cross-env -D2. 修改package.json
json
"scripts": {
"dev": "cross-env NODE_ENV_PLATFORM=window next dev -p 3001",
"dev:l": "cross-env NODE_ENV_PLATFORM=linux next dev -p 3001",
},3. 读取临时环境变量
text
process.env.NODE_ENV_PLATFORM运行 npm run dev,npm run dev:l 将会得到不同的参数配置
十一、配置 Scss
随着Next.js逐渐弃用Less,Antd5也弃用Less改用css-in-js方案,这里改用Sass
Next.js内置支持Sass,只需安装sass包
1. 安装
bash
npm i sass2. 新建src/page.module.scss
scss
$color: red;
$primary-color: #64ff00;
//定义局部样式
.title {
color: $color;
}
//在CSS Module中导出scss变量
:export {
primaryColor: $primary-color;
}3. 使用,修改src/app/page.tsx
tsx
import style from "./page.module.scss";
export default async function Home() {
return (
<>
<span className={style.title}>111</span>
<span style={{ color: style.primaryColor }}>222</span>
</>
);
}为了避免全局样式的影响,这里先在src/app/layout.tsx中去掉import './globals.css';
十二、配置 Scss 全局样式文件
1. 新建src/styles/index.scss
scss
$btn-color: red;2. 修改next.config.mjs
js
const nextConfig = {
sassOptions: {
additionalData: '@import "@/styles/index.scss";',
},
};3. 在scss文件中直接使用变量
scss
button {
color: $btn-color;
}十三、配置 css-in-js
css-in-js有多种方案,例如@mui/material,styled-jsx,styled-components,@vanilla-extract/css 等。我这边选择下载量最高,更新频率还可以的styled-components
1. 安装
bash
npm i styled-components2. 配置next.config.mjs
js
const nextConfig = {
compiler: {
styledComponents: true,
},
};3. 新建全局注册组件
新建lib/registry.tsx
tsx
"use client";
import React, { useState } from "react";
import { useServerInsertedHTML } from "next/navigation";
import { ServerStyleSheet, StyleSheetManager } from "styled-components";
export default function StyledComponentsRegistry({
children,
}: {
children: React.ReactNode;
}) {
// Only create stylesheet once with lazy initial state
// x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet());
useServerInsertedHTML(() => {
const styles = styledComponentsStyleSheet.getStyleElement();
styledComponentsStyleSheet.instance.clearTag();
return <>{styles}</>;
});
if (typeof window !== "undefined") return <>{children}</>;
return (
<StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
{children}
</StyleSheetManager>
);
}4. 包装根布局组件
修改src/app/layout.tsx
tsx
import StyledComponentsRegistry from "@/lib/registry";
const RootLayout = ({ children }: React.PropsWithChildren) => (
<html lang="en">
<body>
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>
</body>
</html>
);
export default RootLayout;5. 使用
tsx
"use client";
import styled from "styled-components";
export default function Home() {
return <Li>1</Li>;
}
const Li = styled.p`
color: red;
`;CSS-in-JS的样式需要在客户端进行处理,因此需使用 'use client' 声明当前组件为客户端组件。该组件及其所有子组件都会在客户端进行渲染。
提示:
如果不经过如上配置,安装后直接使用,会出现样式闪烁等现象
在styled-components中使用背景图片,图片路径不会被正确解析,需先引入图片地址
tsx
import pic from '@/assets/img/sprite_footer_01.png'
background-image: url(${pic});十四、配置状态管理仓库
仓库不选择传统的redux,@reduxjs/toolkit。而是选择更为轻便的zustand来管理
1. 安装 zustand
bash
npm i zustand2. 安装 immer
immer:以更方便的方式处理不可变状态
bash
npm i immer3. 创建store,新建src/store/user.ts
ts
import { produce } from "immer";
import { create } from "zustand";
interface UserInfo {
name: string;
age: number;
}
interface UserState {
userInfo: UserInfo;
token: string;
updateUserInfo: (parmas: UserInfo) => void;
updateAge: (params: number) => void;
updateToken: (params: string) => void;
}
// 创建状态存储
const useUserStore = create<UserState>((set) => ({
userInfo: {
name: "zhangsan",
age: 23,
},
token: "S1",
//更新整个对象
updateUserInfo: (userInfo) => set({ userInfo }), //合并userInfo
//更新对象中某个属性
updateAge: (age) =>
set(
produce((state) => {
state.userInfo.age = age;
}),
),
//更新原始数据类型
updateToken: (token) => set({ token }),
}));
export default useUserStore;4. 使用 store,修改src/app/page.tsx
tsx
"use client";
import { useCallback } from "react";
import useUserStore from "@/store/user";
const Info = () => {
const { userInfo, token, updateUserInfo, updateAge, updateToken } =
useUserStore();
const hanlderUser = useCallback(() => {
updateUserInfo({ name: "lisi", age: 24 });
}, [updateUserInfo]);
const handlerAge = useCallback(() => {
updateAge(userInfo.age + 1);
}, [updateAge, userInfo.age]);
const handlerToken = useCallback(() => {
updateToken("23652");
}, [updateToken]);
return (
<div className="App">
<div>
姓名:{userInfo.name} 年龄:{userInfo.age}
</div>
<div>token:{token}</div>
<button onClick={hanlderUser}>更新用户</button>
<button onClick={handlerAge}>更新年龄</button>
<button onClick={handlerToken}>更新token</button>
</div>
);
};
export default Info;十五、配置 UI 组件库
1. 基本使用
1. 安装
bash
npm i antd2. 修改src/app/page.tsx,引入 antd 的按钮组件
tsx
import { Button } from "antd";
const Home = () => (
<div className="App">
<Button type="primary">Button</Button>
</div>
);
export default Home;3. 目前按钮样式在首屏刷新时会闪动,还需配合@ant-design/nextjs-registry包使用
bash
npm i @ant-design/nextjs-registry4. 在 app/layout.tsx 中使用
tsx
import { AntdRegistry } from "@ant-design/nextjs-registry";
import StyledComponentsRegistry from "@/lib/registry";
const RootLayout = ({ children }: React.PropsWithChildren) => (
<html lang="en">
<body>
<StyledComponentsRegistry>
<AntdRegistry>{children}</AntdRegistry>
</StyledComponentsRegistry>
</body>
</html>
);
export default RootLayout;提示:Next.js App Router 当前不支持直接使用 . 引入的子组件,如 <Select.Option />、<Typography.Text /> 等,需要从路径引入这些子组件来避免错误
2. 解决与tailwindcss的冲突
方案:关闭tailwindcss的预设样式
修改tailwind.config.ts
js
module.exports = {
// ...
corePlugins: {
preflight: false,
},
};在antd 5.15.2版本,该问题解决了
3. 有些浏览器版本过低,不支持:where选择器
让antd使用类选择器
tsx
import { StyleProvider } from "@ant-design/cssinjs";
// `hashPriority` 默认为 `low`,配置为 `high` 后,
// 会移除 `:where` 选择器封装
export default () => (
<StyleProvider hashPriority="high">
<MyApp />
</StyleProvider>
);但该方案,使layout.tsx只能为客户端组件,肯定是行不通的,因此只能升级浏览器版本
另一种UI库:shadcn/ui
十六、配置 Typescript 声明
在window上添加自定义属性,或者使用不支持ts的第三方包是很常见的业务场景。因为项目使用了Typescript,因此需要配置相关的声明使其不报错
修改 next-env.d.ts,新增以下内容:
ts
//在window上添加自定义属性
declare interface Window {
custom: any;
}
//外部模块声明,已支持未使用Typescript的第三方库
declare module "react-beautiful-dnd";后续需根据业务场景进行配置
十七、配置图片网络资源
使用<Image>组件时,如果图片是网络资源,需增加 remotePatterns 配置,否则图片会加载失败
修改next.config.mjs
js
const nextConfig = {
images: {
remotePatterns: [
{
protocol: "https", //图片资源的协议
hostname: "www.test.com", //图片资源的域名
},
],
},
};后续需根据业务场景进行配置
十八、配置打包资源分析
在打包的时候,分析包资源大小以及占比
1. 安装
bash
npm i @next/bundle-analyzer -D2. 配置next.config.mjs
js
import bundleAnalyzer from "@next/bundle-analyzer";
const withBundleAnalyzer = bundleAnalyzer({
enabled: process.env.ANALYZE === "true", //当环境变量ANALYZE为true时开启
});
const nextConfig = {
compiler: {
styledComponents: true,
},
sassOptions: {
additionalData: '@import "@/assets/styles/index.scss";',
},
images: {
remotePatterns: [
{
protocol: "https",
hostname: "www.test.com",
},
],
},
};
export default withBundleAnalyzer(nextConfig);3. 修改package.json
将启动参数build修改为:
json
"build": "cross-env ANALYZE=true next build",4. 运行 npm run build,进行打包
.next/analyze/nodejs.html:显示的是服务端文件包的大小,即.next/server文件夹下的资源
.next/analyze/client.html:显示的是客户端文件包的大小,即.next/static文件夹下的资源
十九、关闭严格模式
严格模式主要用于识别不安全的生命周期、过时的API等情况。但在开发模式下,会让组件执行两次,意味着会多次调用接口,因此需关闭该模式
js
// next.config.js
const nextConfig = {
//新增
reactStrictMode: false,
};二十、开发环境使用HTTPS
修改package.json,配置启动参数:--experimental-https
json
"dev": "next dev --experimental-https",二十一、配置代理
原先的webpack配置:
js
'/finchinaAPP': {
secure: false,
changeOrigin: false,
target: 'https://dev.qyyjt.cn/finchinaAPP',
pathRewrite: {'^/finchinaAPP',''}
}即请求时,例如localhost:3000/finchinaAPP/list将会被代理为https://dev.qyyjt.cn/finchinaAPP/list
Next.js实现代理,修改next.config.mjs
js
const nextConfig = {
async rewrites() {
return [
{
source: "/finchinaAPP/:path*",
destination: `https://dev.qyyjt.cn/finchinaAPP/:path*`,
},
];
},
};当客户端发送以 /finchinaAPP/ 开头的请求时,Next.js 会自动将其重定向到 https://dev.qyyjt.cn/finchinaAPP/ 下的相同路径
提示:配置代理只是为了解决浏览器端的跨域,在服务端中请求,不需要配置代理
如果配置了basePath: '/ssr',,需额外增加basePath: false,
js
const nextConfig = {
async rewrites() {
return [
{
source: "/finchinaAPP/:path*",
destination: `https://dev.qyyjt.cn/finchinaAPP/:path*`,
basePath: false,
},
];
},
};二十二、禁用x-powered-by
默认情况下,在生产环境中,Next.js会在响应头上添加x-powered-by: Next.js;如果想禁用,修改next.config.mjs
js
const nextConfig = {
poweredByHeader: false,
};二十三、日志监控
会经常性的遇到接口请求失败的情况,因此做日志监控很有必要。
安装:winston和winston-daily-rotate-file
新建lib/winston-logger.ts
ts
import { createLogger, transports, format } from "winston";
import DailyRotateFile from "winston-daily-rotate-file";
export const logger = createLogger({
level: "info",
format: format.combine(
format.timestamp({
format: "YYYY-MM-DD HH:mm:ss",
}),
format.errors({ stack: true }),
format.splat(),
format.json(),
),
transports: [
new transports.File({ filename: "logs/error.log", level: "error" }),
new DailyRotateFile({
filename: "%DATE%.log",
dirname: "logs",
datePattern: "YYYY-MM-DD",
maxSize: "20m",
maxFiles: "60d",
}),
],
});
if (process.env.NODE_ENV !== "production") {
logger.add(
new transports.Console({
format: format.combine(format.colorize(), format.simple()),
}),
);
}在需要使用到的地方调用,修改utils/request.ts
ts
import { logger } from "@/lib/winston-logger";
logger.error(errorData);
logger.info(req);