前言
最近接手了公司企业官网的开发项目,采用的是 Next.js 14
。
由于项目初期时间紧、任务重,前任同事直接在公司海外官网的 Next.js
项目基础上进行修改,因此代码结构和需求并不完全匹配。
这篇文章将记录并分享我在改造过程中的一些思路与经验,同时也会介绍几个具体的交互效果是如何实现的。希望能对有类似需求的你有所帮助。
主要做了以下事情:
- 添加 eslint-plugin-tailwindcss 与 eslint-plugin-jsx-a11y 相关 eslint,让编码更加规范
- 添加 code-inspector-plugin 提升开发体验
- 所有幻灯片 Swiper相关代码从命令式写法改写为声明式写法,易读易理解
- 千行大文件修改为更合理的组件划分,文件目录清晰可读
- 添加 SEO 相关会爬取的 head meta 内容
- 使用工具 knip + 人工二次确认,剔除冗余的deadcode,让代码更加清爽干净,剔除未使用的第三方包,减少包体积
- 双端开发经验分享,图片资源处理

总计修改剔除掉自动变更的 lock 文件(+3336,-5143),变更内容
改造后的Lighthouse得分:
打开项目并运行
Readme 里面写的内容不多,直接运行项目 pnpm install
、pnpm dev
我首先在终端看到了一个提示,未能开启默认的 SWC
编译,因为有一个 .babelrc
文件存在
Disabled SWC as replacement for Babel because of custom Babel configuration ".babelrc" nextjs.org/docs/messag...
发现是为了使用 @emotion/styled
才配置的。其实我们可以删掉 .babelrc
文件了,按照官网的建议Next.js 14
已经可以在 config 里面配置来支持 emotion nextjs.org/docs/14/arc...
再次启动没有警告了。变快了 2s。
剔除无用代码 (Dead Code)
虽然我们都知道 ESM 的导入导出机制,加上现代构建工具(如 webpack
、vite
)都有摇树优化(tree-shaking),能够在生产环境中自动剔除未使用的模块,不会将无用代码打包进去。
但如果项目中存在大量 dead code
,即使不会影响打包体积,依然会影响 开发体验 。
比如这个项目,功能其实很简单:只有首页、四大产品介绍页和"关于我们"等六个路由页面,但代码体量却看起来非常庞大。主要原因是:它是直接基于公司海外官网项目改的,之前的同事因为时间紧、任务重,没有做彻底的代码清理和优化。
我在群里发了这样一段话,是我自己的理解。
问:为什么需要清理?不清理有什么问题?
答: 懂软件工程的都知道这里面门道有多深(狗头)。随着项目的演进和需求变化,代码会不断增加,导致熵增。虽然不清理冗余代码对界面效果没有直接影响,但开发体验会受损。代码中会充斥大量干扰文件和注释,这使得开发者难以专注于有效的开发工作。 我们又不是没有 git 啊,代码还能丢了吗。如果所有开发者接手代码,你不动我不动的话,就会逐渐演变成为仓库内杂草丛生。后续开发者也会因为担心清理后出现问题而不敢动手,导致开发效率下降。因为每个人都只想做自己分内的事情不想多做,毕竟万一清理了文件出现了 bug,这个锅就到自己头上了。 最终,这种"破窗效应"会使代码质量不断恶化。这个是官网的前端项目,我既希望面子(呈现的内容)好看的同时,里子(代码质量)也好看。
如何清理
肯定不是手动一个个查是不是有用到啦,我使用了开源工具 knip.dev/ 按照官方教程操作一下。
然后就会在终端显示出检测到的 dead code
,我们再从终端按下 cmd + 鼠标左键
点进去二次确认一下应不应该删除。
配置需要用到的 lint,提升编码规范
项目写样式主要使用 tailwind
,然后由于是官网网站需要注意一下 SEO,所以我选择安装下这两个 lint
添加 eslint-plugin-tailwindcss 与 eslint-plugin-jsx-a11y
幻灯片 Swiper 相关改动
Swiper 极大方便了我们做幻灯片效果,感恩!
原先的写法大概是这样的,通过 useEffect 来绑定幻灯片。
其实更推荐这种声明式的写法从 swiper/react
可导出。
再分享一个我觉得挺好看的 Swiper 效果及完整代码

tsx
import { ReactNode } from 'react';
import { Swiper, SwiperSlide } from 'swiper/react';
import { EffectCreative, Pagination } from 'swiper';
import 'swiper/css';
import { MarketingItemData } from '..';
import MobileImageInfo from './mobileImageInfo';
import styled from '@emotion/styled';
type MobileImageSwiperProps = {
images: MarketingItemData[];
index: string;
describe: ReactNode;
active: string;
};
const MobileImageSwiperStyled = styled(Swiper)`
padding-bottom: 40px;
.mobile-img-swiper-pagination {
bottom: 0;
.swiper-pagination-bullet-active {
background-color: #ff6868;
}
}
`;
// 移动端案例展示图片的轮播图
export default function MobileImageSwiper({ images, index, describe }: MobileImageSwiperProps) {
return (
<div className="relative h-full py-8">
<div className="mx-auto mb-6 flex flex-col items-center justify-center px-2.5 text-center text-base">
<div>{describe}</div>
</div>
<div className="mobile-swiper-wrapper relative">
<MobileImageSwiperStyled
modules={[EffectCreative, Pagination]}
slidesPerView={1.4}
centeredSlides={true}
spaceBetween={10}
loop={true}
speed={600}
pagination={{
clickable: true,
clickableClass: 'mobile-img-swiper-pagination',
}}
className={`mobile-img-swiper-${index} relative w-full overflow-hidden`}
onSwiper={(swiper) => {
const updateSlides = () => {
swiper.slides?.forEach((slide: HTMLElement) => {
const isActive = slide.classList.contains('swiper-slide-active');
slide.style.opacity = isActive ? '1' : '0.5';
slide.style.transform = isActive ? 'scale(1)' : 'scale(0.91)';
slide.style.marginTop = isActive ? '0' : '';
slide.style.transition = 'all 0.5s ease';
});
};
setTimeout(updateSlides, 100);
swiper.on('slideChangeTransitionStart', updateSlides);
}}
>
{images.map((item, i) => (
<SwiperSlide
key={`${i}${item.casename}`}
className="overflow-hidden rounded-2xl bg-white"
>
<div className="flex flex-col">
<div className="relative aspect-[1.5] w-full">
<img
className="size-full object-cover"
src={item.img}
alt={item.casename || 'marketing-img'}
/>
</div>
<MobileImageInfo data={item} />
</div>
</SwiperSlide>
))}
</MobileImageSwiperStyled>
</div>
</div>
);
}
双端开发经验分享
一般都会在项目左下角放置一个开发模式显示的媒体查询指示器,我们可以直观的看到当前处于什么宽度条件下。
tsx
export function TailwindIndicator() {
if (process.env.NODE_ENV === 'production') return null;
return (
<div className="fixed bottom-1 left-1 z-50 flex size-6 items-center justify-center rounded-full bg-gray-800 p-3 font-mono text-xs text-white">
<div className="block sm:hidden">xs</div>
<div className="hidden sm:block md:hidden lg:hidden xl:hidden 2xl:hidden">sm</div>
<div className="hidden md:block lg:hidden xl:hidden 2xl:hidden">md</div>
<div className="hidden lg:block xl:hidden 2xl:hidden">lg</div>
<div className="hidden xl:block 2xl:hidden">xl</div>
<div className="hidden 2xl:block">2xl</div>
</div>
);
}
由于使用的是 tailwind
,就可以很方便的在 className
中使用各种媒体查询前缀 tailwind 媒体查询
下面是我新增在项目 Readme 中的备注
xl:xxx (xl 尺寸下的样式) max-xl:xxx(小于 xl 时候的样式),tailwind 官网建议移动端优先,但我们的官网是 web 优先的,所以移动端的媒体查询就作为辅助副产品,先开发 web 端。
有一些模块如果 web 和 Mobile 差异较大的话,无法通过媒体查询修改状态可以参考 Swiper分为两个组件,然后通过媒体查询控制显隐。不要通过 js,因为有 ssr,会报警告 dom 不匹配的问题水合报错。
tsx
<div className="block lg:hidden">
<MobileSwiper active={active} setActive={setActive} setSwiperObj={setSwiperObj} />
</div>
<div className="hidden lg:block">
<WebSwiper active={active} setActive={setActive} setSwiperObj={setSwiperObj} />
</div>
网站的图片资源
官网一般会展示许许多多的图片素材,我们选用格式的时候推荐使用 webp
格式,现在大部分应该已经支持的了。我这个项目使用的图片都存储在腾讯云的对象存储中,将 figma 中的图片素材导出后上传到自己公司内的 cos 中。
腾讯云的 cos
提供了方便的数据万象功能 腾讯云 cos 图片基础压缩(WebP)服务 只需要在末尾加上字符串 ?imageMogr2/format/webp
即可进行图片转换成 webp
shell
http://example-1258125638.cos.ap-shanghai.myqcloud.com/sample.png?imageMogr2/format/webp
SEO 辅助工具
一个专门分析 SEO
的浏览器插件,也是看掘金小伙伴们发的文章了解到的
结语
以上就是本次 nextjs 官网的改造过程经验分享了,希望对大家有帮助~
悄悄吐槽一句:不知道是不是因为我用的是 Intel iMac,Next.js 14
的开发体验偶尔会突然卡顿,整个电脑都跟着变慢了......
我也试着在 Next.js 14
中启用了 Turbopack
(虽然它在这个版本中还属于实验性功能),结果体验确实不太理想。
考虑到 Next.js 15
才正式启用 Turbopack
,如果以后再让我从零搭建企业官网这类项目,我可能会倾向选用 Remix ------ 现在也就是 React Router v7)