基于Next.js构建高性能网站小结
Next.js是一个基于React的一个服务端渲染框架.
入门知识
如何检测相关性能
提升性能
以下建议皆针对首屏优化进行的措施,对于网站基础内容的优化详见其他清单, 如Front-End-Performance-Checklist。
善用 Dynamic Import
Next.js本身支持代码自动分割,另外也支持 ES2020 dynamic import(),支持 Javascript模块(如React组件)的动态加载。理想的情况是一个单独模块是一个单一的文件。
这种基于React组件的分割,可以针对由不同的路由的功能的加载的模块也不一样:
基本使用步骤:
const BasicLayout = dynamic(() => import('@/container/Layout'))
const MobileLayout = dynamic(() => import('@/container/Layout/Mobile'))
const AmpLayout = dynamic(() => import('@/container/Layout/Amp'))
使用@zeit/next-bundle-analyzer
分析打包后代码结构
基本使用步骤:
...
const withBundleAnalyzer = require('@zeit/next-bundle-analyzer')
const nextConfig = {
analyzeServer: ['server', 'both'].includes(publicRuntimeConfig.isAnalyze),
analyzeBrowser: ['browser', 'both'].includes(publicRuntimeConfig.isAnalyze),
bundleAnalyzerConfig: {
server: {
analyzerMode: 'static',
reportFilename: 'analyze/server.html'
},
browser: {
analyzerMode: 'static',
reportFilename: 'analyze/client.html'
}
},
...
}

查看未使用的代码是否被加载
我们在使用第三方包的时候,常常会把未使用的代码一并的打包一起,就像:
// bad
import { x } from 'package';
// ok
import x from 'package/x'
当然啦,你也可以使用webpack相关的插件解决这些问题,或者是直接安装这些包的独立模块(如 lodash.get )。
特别地, 另外一种未使用的代码指的是类似Polyfills, 有些浏览器并不需要加载他们,可以建议尝试使用Polyfill.io,但有一定的风险。
查看是否存在庞大的代码模块
针对自己的业务优化一些引用的包。例如 moment
可用 date-fns
替换,如果使用功能很简单,完全可以自己构建。在使用一个新的第三包的同时也可以使用这个工具进行大小和功能比较, 同时也会提供一些相关的库给您选择。


善于使用 LazyLoad
首屏的加载并不需要加载首屏之外的JS和图片,或者一些其他的如媒体/Ifame/字体类的资源,建议使用 react-lazyload
对这些内容进行懒加载。
基本使用步骤:
import React from 'react'
import Lazyload from 'react-lazyload'
import Loader from '@/components/Loader'
export default (config = {}) => {
return (WrappedComponent) => (props) => {
return <Lazyload {...{
throttle: config.throttle || 200,
height: config.height || 300,
once: config.once || true,
offset: config.offset || 500,
placeholder: config.disabledLoader
? null
: <Loader width={config.width || '100%'} height={config.height || 300} />
}}>
<WrappedComponent {...props} />
</Lazyload>
}
}
特别地,SegmentIo 至4.1版本后也支持仅加载指定的JS-SDK.
另外,针对加载一些不必要的第三方的脚本,仅仅作为局部测试或者修改非首屏内容的,可采用监听window滚动事件进行加载
,效果也很不错的。
基本使用步骤:
window.addEventListener('scroll', this.doSometing, { once: true })
巧用Node.js/Global对象来避免加载非渲染库
服务端渲染,我们通常使用lru-cache
或者redis
进行缓存数据,如何在next.js中进行交互?
基本使用步骤:
// 自定义服务器层
const RedisClient = require('./lib/redis')
const LRU = require('lru-cache')
global.RedisCache = new RedisClient(redis)
global.LRU = new LRU(LRU_OPTIONS)
// 渲染层
global.RedisCache && global.RedisCache.get(key)
此时的global.RedisCache对象仅仅是个内存对象,并不会被打包给客户端,那么如果我们需要一些库仅仅它处理的结果而且并不需要它打包给用户的话,我们可以把它挂载到global对象上面,举个例子,我们需要把markdown
转成html
的,而此时我们需要一个markdown-it
的库,这时我们可以:
基本使用步骤:
// 在自定义服务器层
server/global.js
const Markdown = require('markdown-it')
global.markdown = new Markdown({
html: true,
linkify: true,
typographer: true
})
// 渲染层直接调用即可
global.markdown && global.markdown.render(html)
Critical Rendering Path
此解决方案是为了提高页面的渲染速度。具体可参考: penthouse, 仅需要一个地址和相关的css样式便可。
基本使用步骤:
// 先从 LRU 层加载
let criticalCss = await global.LRU.get(criticalKey)
if (!criticalCss) {
// 若不存则,则从 Redis 层加载
criticalCss = await global.RedisCache.get(criticalKey)
}
let html = await app.renderToHTML(req, res, pathName, { ...params })
// 引入 next.js 渲染后 html中
if (criticalCss) { html = html.replace('<head>', `<head><style id='criticalCss'>${criticalCss}</style>`) }
// 把其他的样式改为 preload 即可
html = html.replace(/<link rel="stylesheet"/g, `<link rel="preload" as="style" onload="this.rel='stylesheet'"`)
使用 Amp
AMP,全称是 Accelerated Mobile Pages, 是 Google 推出的开源前端框架,是目前 Web 最快框架之一,详情可参考: GOOGLE AMP 完全指南 附实战心得。
Next.js 内置支持Amp, 只需要按在约定配置即可,不喜欢这种方式也可以自己进行加载,推荐使用 react-amphtml, 附上与Next.js 进行配置例子 dfrankland/ampreact
基本使用步骤:
const AmpAnalytics = dynamic(() => import('react-amphtml').then(model => model.AmpAnalytics))
const segment = () => {
return <AmpAnalytics type='segment'>
<script type='application/json' dangerouslySetInnerHTML={{ __html: JSON.stringify(segment) }} />
其他小技巧点
- 既然都用了服务端渲染,当然离不开SEO,我们可以参考next-seo, 里面是可基于配置来生成对应的 meta 属性和 jsonld。
- 可以使用
<Link prefetch>
来让页面在后台同时获取和预加载,以获得最佳的页面加载性能。