给上市公司从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

结尾

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

相关推荐
一颗花生米。1 小时前
深入理解JavaScript 的原型继承
java·开发语言·javascript·原型模式
学习使我快乐012 小时前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
bobostudio19952 小时前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式
勿语&3 小时前
Element-UI Plus 暗黑主题切换及自定义主题色
开发语言·javascript·ui
黄尚圈圈3 小时前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水4 小时前
简洁之道 - React Hook Form
前端
正小安6 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch7 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光7 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   7 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发