这段时间工作要求将之前的 vue 的 H5 活动页面转移到 Next.js 下面,经过一段时间的研究,总结出了下面的内容。
什么是 Next.js
基于React
的 SSR(服务端渲染框架)
SSR & CSR

The main difference is that for SSR your server’s response to the browser is the HTML of your page that is ready to be rendered, while for CSR the browser gets a pretty empty document with links to your javascript. That means your browser will start rendering the HTML from your server without having to wait for all the JavaScript to be downloaded and executed. In both cases, React will need to be downloaded and go through the same process of building a virtual dom and attaching events to make the page interactive — but for SSR, the user can start viewing the page while all of that is happening. For the CSR world, you need to wait for all of the above to happen and then have the virtual dom moved to the browser dom for the page to be viewable.

Next.js 的优点
- 更好的 SEO
- 更快的首屏渲染速度
Next.js 基础(与 React 开发的不同之处)
路由映射
在 Next.js 中,一个 page(页面) 就是一个从
.js
、jsx
、.ts
或.tsx
文件导出(export)的 React 组件 ,这些文件存放在pages
目录下。每个 page(页面)都使用其文件名作为路由(route)。
pages/about.js/jsx/ts/tsx → /about
pages/dashboard/settings/username.js → /dashboard/settings/username
自带路由:next/router
next/link
使用与react-router
类似,包括编程式跳转router.push
以及组件式跳转<Link href="/about"><a>click me</a></Link>
import { useRouter } from 'next/router';
const router = useRouter();
router.push({
pathname: '/activities/experience-lesson/course-info',
query: { ...queryData, isFree: 0 }
});
router.push('/about');
渲染方式
预渲染
- 静态生成(Static Generation)(HTML 重用、build 生成)
- 服务器端渲染(Server-side Rendering)(每次请求生成的 HTML 不同、用户请求时生成)
相关 API
-
静态生成
getStaticProps(context)
getStaticPaths(context)
-
服务器渲染
getServerSideProps(context)
-
客户端获取数据
- SWR(官方推荐)
**注意:**在开发环境中getStaticProps
和getStaticPaths
每次请求都会被调用
使用,在页 面文件中导出
function Page({ data }) {
// Render data...
}
// This gets called on every request
export async function getServerSideProps() {
// Fetch data from external API
const res = await fetch(`https://.../data`);
const data = await res.json();
// Pass data to the page via props
return { props: { data } };
}
export default Page;
项目结构及工程化
配置 Eslint+Prettier
https://github.com/paulolramos/eslint-prettier-airbnb-react
https://dev.to/karlhadwen/setup-eslint-prettier-airbnb-style-guide-in-under-2-minutes-a27
https://dev.to/bybruno/configuring-absolute-paths-in-react-for-web-without-ejecting-en-us-52h6
解决eslint
无法识别动态引入语法import()
:
// eslint 配置
parserOptions: {
ecmaVersion: 2020, // Use the latest ecmascript standard
sourceType: 'module', // Allows using import/export statements
ecmaFeatures: {
jsx: true // Enable JSX since we're using React
}
},
考虑到团队协作,在根目录下创建.vscode
文件夹并在里面创建setting.json
文件以达到保存自动修复的效果,保证了团队代码的统一性。
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
后期经过一段时间发现有些团队成员的编辑器设置了实时保存功能,于是就直接跳过了这一步,这里便可以用husky
(用来给 git 添加 hook)搭配lint-staged
在 commit 代码前来自动做这一件事情。
yarn add husky lint-staged prettier --dev
编写package.json
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js, jsx}": [
"npm run lint",
"git add"
]
}
配置 alias
next.config.js
配置
/* eslint-disable no-param-reassign */
const path = require('path');
module.exports = {
webpack: config => {
// Note: we provide webpack above so you should not `require` it
// Perform customizations to webpack config
config.resolve.alias['@'] = path.resolve(__dirname, './src');
// Important: return the modified config
return config;
}
};
eslint 无法识别 alias,需要在根目录下创建文件jsconfig.json
并在.eslintrc.js
配置settings
- jsconfig.json
- .eslintrc.js
{
"compilerOptions": {
"baseUrl": "src",
"paths": {
"@/*": ["./*"]
}
},
"exclude": ["node_modules", "**/node_modules/*"]
}
module.exports = {
root: true, // Make sure eslint picks up the config at the root of the directory
extends: ['airbnb', 'airbnb/hooks', 'plugin:prettier/recommended', 'prettier/react'],
env: {
browser: true,
commonjs: true,
es6: true,
jest: true,
node: true
},
globals: {
wx: true
},
parserOptions: {
ecmaVersion: 2020, // Use the latest ecmascript standard
sourceType: 'module', // Allows using import/export statements
ecmaFeatures: {
jsx: true // Enable JSX since we're using React
}
},
rules: {
'react/react-in-jsx-scope': 0,
'jsx-a11y/alt-text': 0, // img alt
'react/prop-types': 0,
'jsx-a11y/click-events-have-key-events': 0,
'jsx-a11y/no-static-element-interactions': 0,
'dot-notation': 0,
'import/prefer-default-export': 0,
'react/jsx-props-no-spreading': 0,
'jsx-a11y/href-no-hash': ['off'],
'react/no-array-index-key': 0,
'no-console': 0,
'no-alert': 0,
'consistent-return': 0,
// eslint-disable-next-line prettier/prettier
eqeqeq: 1,
'react/self-closing-comp': 0,
'react-hooks/exhaustive-deps': 0,
'react/no-danger': 0,
'no-shadow': 0,
'jsx-a11y/label-has-associated-control': 0,
'react/jsx-filename-extension': ['warn', { extensions: ['.js', '.jsx'] }],
'max-len': [
'warn',
{
code: 120,
tabWidth: 2,
comments: 120,
ignoreComments: false,
ignoreTrailingComments: true,
ignoreUrls: true,
ignoreStrings: true,
ignoreTemplateLiterals: true,
ignoreRegExpLiterals: true
}
]
},
settings: {
'import/resolver': {
alias: {
map: [['@', './src']],
extensions: ['.ts', '.js', '.jsx', '.json']
}
}
}
};
封装 axios 在每次请求时显示spin
组件
这里要注意一点,由于服务端不存在document
,所以要判断一下当前所处的环境再去执行操作。
详细代码
import axios from 'axios';
import ReactDOM from 'react-dom';
import Spin from '../components/Spin/Spin';
const Axios = axios.create({
timeout: 20000
});
const csr = process.browser;
// 当前正在请求的数量
let requestCount = 0;
function showLoading() {
if (requestCount === 0) {
var dom = document.createElement('div');
dom.setAttribute('id', 'loading');
document.body.appendChild(dom);
ReactDOM.render(<Spin />, dom);
}
requestCount++;
console.log('showLoading', requestCount);
}
function hideLoading() {
requestCount--;
if (requestCount === 0) {
document.body.removeChild(document.getElementById('loading'));
}
console.log('hideLoading', requestCount);
}
Axios.interceptors.request.use(
config => {
csr && showLoading();
return config;
},
err => {
csr && hideLoading();
return Promise.reject(err);
}
);
Axios.interceptors.response.use(
res => {
csr && hideLoading();
return res;
},
err => {
csr && hideLoading();
return Promise.reject(err);
}
);
export default Axios;