给上市公司从0到1搭建Next.js14项目

前言

先附上源码地址:next-ts-seed

服务端渲染具有诸多优点,包括优化SEO、加快首屏加载速度、增强安全性等。针对注重SEO的C端项目而言,服务端渲染是首选方式,也是每位前端工程师必备的技术栈之一。

1. 环境准备

  1. Next.js 14版本对于Node.js最低的版本要求是 18.17.0,目前我的环境为 18.19.0
  1. VSCode相关插件安装:Prettier - Code formatter,ESlint,Nextjs snippets,vscode-styled-components

2. 项目创建

为了更快的安装速度,没有切换镜像的,先切换

js 复制代码
npm config set registry https://registry.npmmirror.com

运行

js 复制代码
npx create-next-app@latest

根据提示,创建项目

  1. 输入项目的名称

  2. 启用Typescript

  3. 启用ESLint

  4. 不使用Tailwind CSS(根据个人需求)

  5. 将src作为目录:方便将业务文件与配置文件区分开来

  6. 使用App Router:可使用 Server component 和 Streaming 等新特性

  7. 不定义别名,使用默认的@代表src目录

3. 配置 Prettier

ESLint 脚手架已默认安装,现需配置prettier

1. 安装 eslint ts默认规则补充

js 复制代码
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

js 复制代码
//允许 ESLint 报告不符合 Prettier 格式化规则的代码
npm i eslint-plugin-prettier -D

//用来解决与eslint的冲突
npm i eslint-config-prettier -D 

//安装prettier
npm i prettier -D

4. 修改 .eslintrc.json

js 复制代码
{
  "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"
  }
}

5. 新建 .prettierrc

js 复制代码
{
  "endOfLine": "auto",
  "printWidth": 120,
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "all",
  "bracketSpacing": true
}

6. 新建 .prettierignore

js 复制代码
/.next/
/node_modules
.env*.local

7. 修改 package.json

修改 lint 启动命令

js 复制代码
"scripts": {
  "lint": "eslint src --fix --ext .ts,.tsx,.js,.jsx --max-warnings 0",
},

重启VSCode,打开src/app/layout.tsx,可以看到报错信息

这是因为在 .prettierrc 中配置了"singleQuote": true即单引号,而文件中使用的是双引号,所以编辑器提示报错

运行 npm run lint,可自动修复上述 eslint(prettier/prettier) 问题

4. 配置 VSCode

每次运行 npm run lint 修复 eslint(prettier/prettier) 问题比较麻烦。可配置VSCode,在文件保存时,自动格式化文档

根目录新建.vscode文件夹,在文件夹下新建settings.json

js 复制代码
{
  "editor.codeActionsOnSave": {
    "source.fixAll": "explicit"
  }
}

新版的vscode配置为explicit,老版的为true

在插件中选择扩展设置

将每行代码的长度限制由80改为120(因为 .prettierrc中"printWidth": 120,)

保存之后,重启VScode

5. 配置 import 导入顺序

在开发组件时,需要引用外部资源,当import的包很多时,例如有next、react第三方包,有自定义包,有Typescript类型文件,有样式文件等等。

如果对import的顺序不进行排序的话,会显得很杂乱,比如以下导入,在引入自定义组件的时候,可能会被另一个开发者引入类型文件,这时候规范引入顺序就显得很重要了

js 复制代码
import Header from '@/components/header';
import type { Metadata } from 'next';
import Tabs from '@/components/tabs';

1. 安装

js 复制代码
npm i eslint-plugin-import -D

2. 修改 .eslintrc.json

在rules下添加以下配置

js 复制代码
    "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
        }
      }
    ]

运行 npm run lint,可统一修复排序问题

在开发的过程中,因为配置了VSCode保存时自动修复,因此配置好之后,不需要我们再操心import导入顺序的问题

6. 配置 .gitignore

Next.js目前使用基于Rust的编译器SWC来编译JavaScript/TypeScript。SWC的安装受到Node.js版本等因素的影响,如果版本不一致可能导致启动失败,出现类似 'Failed to load SWC binary for win32/x64' 的错误提示。

解决方案一:package-lock.json文件不上传到GitHub上,修改.gitignore,新增

js 复制代码
#lock
package-lock.json
yarn.lock

7. 配置 Git hooks

husky:一个为git客户端增加hook的工具

lint-staged:仅对Git 代码暂存区文件进行处理,配合husky使用

@commitlint/cli:让commit信息规范化

1. 创建 git 仓库

js 复制代码
git init

2. 安装

js 复制代码
//固定husky的版本
npm i husky@8.0.3 -D 

npm i lint-staged -D

npm i @commitlint/cli -D

npm i @commitlint/config-conventional -D

3. 运行

js 复制代码
//生成 .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

与devDependencies同级,添加

js 复制代码
"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. 提交格式

js 复制代码
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. 提交示范

js 复制代码
git commit -m 'feat: 增加 xxx 功能'
git commit -m 'bug: 修复 xxx 功能'

提示:非规范提交,将commit提交失败

8. 配置端口号

Next.js的默认访问端口是3000,可修改启动命令,来改变端口号

修改package.json,在启动命令后增加 " -p 3001 " ,这样开发环境与生产环境的启动端口为3001

js 复制代码
"scripts": {
  "dev": "next dev -p 3001",
  "build": "next build",
  "start": "next start -p 3001"
},

9. 配置环境变量

项目根目录新建 .env、.env.development、.env.production、.env.local,分别代表着所有环境变量,开发环境变量,生产环境变量,本地环境变量

覆盖优先级为:.env.local > .env.production | .env.development > .env

使用NEXT_PUBLIC_前缀定义变量

js 复制代码
//.env.development
NEXT_PUBLIC_BASEURL = 'http://127.0.0.1:9000/api'

//.env.production
NEXT_PUBLIC_BASEURL = 'https://www.test.com/api'

使用 process.env. 进行访问

js 复制代码
process.env.NEXT_PUBLIC_BASEURL

如果想自定义环境变量的文件位置,或者定义多个工程的环境变量,可使用 dotenv。可参考我的另一篇文章:[Vite 进阶] 配置环境变量(包含多工程、多环境配置)

10. 配置临时环境变量

在启动或编译应用程序时,我们常常需要根据不同的参数走不同的配置

  1. 安装
js 复制代码
npm i cross-env -D
  1. 修改package.json
js 复制代码
"scripts": {
  "dev": "cross-env NODE_ENV_PLATFORM=window next dev -p 3001",
  "dev:l": "cross-env NODE_ENV_PLATFORM=linux next dev -p 3001",
},
  1. 读取临时环境变量
js 复制代码
process.env.NODE_ENV_PLATFORM

运行 npm run dev,npm run dev:l 将会得到不同的参数配置

11. 配置 Scss

随着Next.js逐渐弃用Less,Antd5也弃用Less改用css-in-js方案,这里改用Sass

Next.js内置支持Sass,只需安装sass包

  1. 安装
js 复制代码
npm i sass
  1. 新建src/page.module.scss
js 复制代码
$color: red;
$primary-color: #64ff00;

//定义局部样式
.title {
  color: $color;
}

//在CSS Module中导出scss变量
:export {
  primaryColor: $primary-color;
}
  1. 使用,修改src/app/page.tsx
js 复制代码
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';

12. 配置 Scss 全局样式文件

  1. 新建src/styles/index.scss
js 复制代码
$btn-color: red
  1. 修改next.config.mjs
js 复制代码
const nextConfig = {
  sassOptions: {
    additionalData: '@import "@/styles/index.scss";',
  },
};
  1. 在scss文件中直接使用变量
js 复制代码
button{
  color: $btn-color;
}

13. 配置 css-in-js

css-in-js有多种方案,例如@mui/material,styled-jsx,styled-components,@vanilla-extract/css

等。我这边选择下载量最高,更新频率还可以的styled-components

  1. 安装
js 复制代码
npm i styled-components
  1. 使用
js 复制代码
'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' 声明当前组件为客户端组件。该组件及其所有子组件都会在客户端进行渲染

14. 配置状态管理仓库

仓库不选择传统的redux,@reduxjs/toolkit。而是选择更为轻便的zustand来管理

1. 安装 zustand

js 复制代码
npm i zustand

2. 安装 immer

immer:以更方便的方式处理不可变状态

js 复制代码
npm i immer

3. 创建 store

新建src/store/user.ts

js 复制代码
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

js 复制代码
'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;

zustand更多的使用方式可参考我的另一篇文章:还在用redux?试试更方便的zustand

15. 配置 UI 组件库

  1. 安装
js 复制代码
npm i antd
  1. 修改src/app/page.tsx,引入 antd 的按钮组件
js 复制代码
import React from 'react';
import { Button } from 'antd';

const Home = () => (
  <div className="App">
    <Button type="primary">Button</Button>
  </div>
);

export default Home;
  1. 目前按钮样式在首屏刷新时会闪动,还需配合@ant-design/nextjs-registry包使用
js 复制代码
npm i @ant-design/nextjs-registry
  1. 在 app/layout.tsx 中使用
js 复制代码
import React from 'react';
import { AntdRegistry } from '@ant-design/nextjs-registry';

const RootLayout = ({ children }: React.PropsWithChildren) => (
  <html lang="en">
    <body>
      <AntdRegistry>{children}</AntdRegistry>
    </body>
  </html>
);

export default RootLayout;

提示:Next.js App Router 当前不支持直接使用 . 引入的子组件,如 <Select.Option />、<Typography.Text /> 等,需要从路径引入这些子组件来避免错误

16. 配置 Typescript 声明

在window上添加自定义属性,或者使用不支持ts的第三方包是很常见的业务场景。因为项目使用了Typescript,因此需要配置相关的声明使其不报错

修改 next-env.d.ts,新增以下内容:

js 复制代码
//在window上添加自定义属性
declare interface Window {
  custom: any;
}

//外部模块声明,已支持未使用Typescript的第三方库
declare module 'react-beautiful-dnd';

后续需根据业务场景进行配置

17. 配置图片网络资源

使用<Image>组件时,如果图片是网络资源,需增加 remotePatterns 配置,否则图片会加载失败

修改next.config.mjs

js 复制代码
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https', //图片资源的协议
        hostname: 'www.test.com', //图片资源的域名
      }
    ],
  },
};

后续需根据业务场景进行配置

18. 配置打包资源分析

在打包的时候,分析包资源大小以及占比

1. 安装

js 复制代码
npm i @next/bundle-analyzer -D

2. 配置 next.config.js

目前@next/bundle-analyzer的版本为14.1.3,只支持CommonJS的写法,而项目自带的next.config.mjs代表着ESModule写法,因此需先修改文件名,将其改为next.config.js

js 复制代码
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
});

const nextConfig = {
  sassOptions: {
    additionalData: '@import "@/styles/index.scss";',
  },
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'www.test.com',
      },
    ],
  },
};

module.exports = withBundleAnalyzer(nextConfig);

3. 修改 package.json

将启动参数build修改为:

js 复制代码
"build": "cross-env ANALYZE=true next build",

4. 打包

运行 npm run build 进行打包,.next文件夹下会生成analyze文件夹

.next/analyze/nodejs.html:显示的是服务端文件包的大小,即.next/server文件夹下的资源

.next/analyze/client.html:显示的是客户端文件包的大小,即.next/static文件夹下的资源

19. 路由

一文梳理Next.js 14的App Router

结尾

后续会持续更新本篇文章内容。如果有什么好的建议,欢迎在评论区留言

相关推荐
大杯咖啡4 分钟前
localStorage与sessionStorage的区别
前端·javascript
RaidenLiu15 分钟前
告别陷阱:精通Flutter Signals的生命周期、高级API与调试之道
前端·flutter·前端框架
非凡ghost16 分钟前
HWiNFO(专业系统信息检测工具)
前端·javascript·后端
非凡ghost18 分钟前
FireAlpaca(免费数字绘图软件)
前端·javascript·后端
非凡ghost24 分钟前
Sucrose Wallpaper Engine(动态壁纸管理工具)
前端·javascript·后端
拉不动的猪26 分钟前
为什么不建议项目里用延时器作为规定时间内的业务操作
前端·javascript·vue.js
该用户已不存在33 分钟前
Gemini CLI 扩展,把Nano Banana 搬到终端
前端·后端·ai编程
地方地方34 分钟前
前端踩坑记:解决图片与 Div 换行间隙的隐藏元凶
前端·javascript
炒米233337 分钟前
【Array】数组的方法
javascript
小猫由里香40 分钟前
小程序打开文件(文件流、地址链接)封装
前端