我为什么没用上NextJS

为了确保用户有良好的首屏加载体验,网站应该努力将核心的页面加载时间(LCP)控制在 2.5秒 之内。然而,在我之前负责的B端项目中,大部分核心页面的LCP时间 高达6秒以上,这直接影响了用户的首屏加载体验。

当然,提高性能有很多方法,但服务器端渲染(SSR)是突破 JavaScript 资源加载和执行瓶颈的关键技术,实现首屏加载时间控制在 2.5 秒以内的效果。

最近我接手了公司的一个新B端项目,本想趁着项目刚起步,尚未积累太多技术债务的时候就优化首屏加载时间。为此,我在项目预研阶段选择了Next.js,考虑到它已经是一个成熟的适用于B端项目的SSR框架。然而,最终我却放弃了使用Next.js。接下来的,我将分享一些我对这一决定的思考。

UI问题

B端项目通常会使用类似于 Ant Design UI 的UI组件库,而我所在的公司前端团队自己实现了一个UI组件库。然而,公司内部的UI组件库不支持直接的服务器端渲染(SSR)调用,主要是因为以下原因:

1.组件外部调用window API

公司内部的UI组件库中的大部分组件需要在组件外部调用window对象的API。例如,假设一个按钮(Button)UI组件的实现中包含以下代码:

js 复制代码
// 在NextJS中,服务器端在组件外部调用window会报错,因为window是空的
console.log(window.location.pathname);

const Button = () => {
  return <button>MyButton</button>;
};

在Next.js中调用这个Button组件会报错,因为Node.js环境中并不存在window对象,因此调用window的API会报错。Button UI组件的需要改成这样才能被NextJS正常调用:

js 复制代码
import { useEffect } from "react";

const Button = () => {
  // NextJS服务端会忽略useEffect里面的代码,因此这样写不会报错
  useEffect(() => {
    console.log(window.location.pathname);
  }, []);

  return <button>MyButton</button>;
};

在NextJS的服务端环境中,组件内部的useEffect代码会被忽略,在useEffect里面的window对象不会被调用,因此能正常调用。

2.import 全局css问题

公司内的UI组件库的组件最终的产物里会有import 全局 css 文件的代码,所以需要调用方的构建环境提供编译css的webpack loader:

js 复制代码
// 最终的Button组件产物里会import css,NextJS编译这个代码会报错
import "./index.css";

const Button = () => {
  return <button>MyButton</button>;
};

在NextJS调用这个UI组件会报这个错:

Global CSS cannot be imported from files other than your Custom . Due to the Global nature of stylesheets, and to avoid conflicts, Please move all first-party global CSS imports to pages/_app.js. Or convert the import to Component-Level CSS (CSS Modules).

NextJS为了解决css冲突问题,强制不允许自定义组件import 全局 css。导致引入公司内UI组件库报错。

使用Ant Design UI库?

你可能会问,为什么不直接使用 Ant Design 呢?是的,Ant Design 支持 NextJS,我也是使用 Ant Design 来避开上述问题。但是公司的UI设计师会根据公司的UI组件来设计UI稿。如果使用Ant Design UI库,那么我们需要额外的工作量来调整UI上的差异,以使其与现有UI组件协调一致。

业务组件

B端项目中存在大量重复性较高的功能,因此其他同事通常会将这些通用性较高的功能抽象成业务组件。然而,这些业务组件通常并未考虑到服务器端渲染(SSR)的场景,和UI组件一样在 NextJS 上调用会有两个问题:

  • 组件外部调用window API问题
  • import 全局 css 问题

业务组件上面两个问题也有解决办法:

业务组件与UI组件的不同之处在于,大部分业务组件并不需要在 SSR 服务中进行渲染,而是可以只在客户端中进行渲染。NextJS 的 dynamic方法 可以做到组件仅在客户端中渲染:

js 复制代码
"use client";

import dynamic from "next/dynamic";

// 使用dynamic方法,让组件仅在客户端渲染时渲染
const SomeComponent = dynamic(() => import("@scope/some-component"));

const MyApp = () => {
  return (
    <div>
      <SomeComponent />
    </div>
  );
};

export default MyApp;

使用这个方法可以解决上述的第一个问题:服务端不会调用这些业务组件的window API了。

使用了dynamic后,组件的js和css都会懒加载,也就是说业务组件的css不会在服务端中加载了,因此可以在next.config.js下,放开业务组件的css构建限制:

js 复制代码
// next.config.js
const nextConfig = {
  webpack(config) {
    config.module.rules.push({
      // NextJS的webpack配置中默认会忽略匹配的less和css。所以要重新自定义less和css的loader配置
      test: /(?<!NEXTJS_CSS_DETECTION_FILE)\.(less|css)$/,
      include: /node_modules\/@scope/,
      exclude: /src/,
      use: [
        {
          loader: "style-loader",
        },
        {
          loader: "css-loader",
          options: { modules: false, esModule: false, importLoaders: 1 },
        },
        {
          loader: "postcss-loader",
          options: {
            postcssOptions: {
              config: false,
            },
          },
        },
        {
          loader: "less-loader",
          options: { lessOptions: { javascriptEnabled: true, math: "always" } },
        },
      ],
    });

    return config;
  },
};

module.exports = nextConfig; 

虽然解决了业务组件的使用问题,但是dynamic的引入方式还是会有点麻烦,对于新加入进来的开发同学不是很友好。

生态与复用

在我所在的公司部门,前端开发的特点是将多个B端项目分配给不同的团队负责。因此,公司的架构组开发了一个类似于UmiJS的前端框架,以确保统一的代码构建流程,并为大家提供了很多可复用的功能,例如上报系统等。

如果转向使用 Next.js,我们将脱离这个生态,需要自行实现许多可复用的功能,额外增加工作量。

开发难度与开发时间

使用 Next.js 会增加项目的代码复杂性。它要求开发者了解服务端组件和客户端组件之间的区别,同时要求对 window API 的使用方式有一定程度的了解。更重要的是,SSR 会额外增加一个服务,即增加维护服务的成本。许多前端同事在如何保持服务稳定方面的知识和经验仍然相对欠缺。

实际上,上述问题只要有充足的时间都是可以解决的。然而,该项目最紧缺的资源就是时间,项目的开发周期只有一个月。如果选择使用 Next.js,很可能会导致项目延期。

总结

在我所处的公司中使用 Next.js 时所面临的最大问题在于公司内部的生态局限。UI组件库、业务组件库以及前端框架均不支持服务器端渲染(SSR),且前端架构团队的成员以及前端业务开发人员在开发时不会考虑 SSR 的应用场景。仅凭我个人的力量,目前还无法在功能比较复杂的页面上使用 Next.js。如果真的打算推动使用 Next.js,那么必须与前端架构团队共同合作,共建支持服务器端渲染的生态系统。

相关推荐
江城开朗的豌豆19 分钟前
JavaScript篇:自定义事件:让你的代码学会'打小报告'
前端·javascript·面试
ai产品老杨1 小时前
减少交通拥堵、提高效率、改善交通安全的智慧交通开源了。
前端·vue.js·算法·ecmascript·音视频
lexiangqicheng1 小时前
JS-- for...in和for...of
开发语言·前端·javascript
粥里有勺糖1 小时前
视野修炼-技术周刊第122期 | 发光图片制作
前端·javascript·github
Carlos_sam2 小时前
OpenLayers:封装Tooltip
前端·javascript
工呈士2 小时前
MobX与响应式编程实践
前端·react.js·面试
嘉小华2 小时前
Android Lifecycle 使用
前端
Sherry0072 小时前
实时数据传输协议:WebSocket vs MQTT
前端·websocket
然我2 小时前
JavaScript的OOP独特之道:从原型继承到class语法
前端·javascript·html
腹黑天蝎座2 小时前
如何更好的实现业务中图片批量上传需求
前端