技术选型不是为了简历好看,是为了解决问题
上周和一个前同事约饭,他现在在一家创业公司带前端团队。
聊到一半他突然问我:“我们准备把项目重构成 Next.js,上服务端渲染,你觉得怎么样?”
我放下筷子:“你们现在遇到什么坑了要重构?”
他挠挠头:“坑倒是没有……就是现在出去面试,人家都问有没有 Next.js 经验,我们不用会不会显得技术栈太旧了。”
“那你们产品要 SEO 吗?用户网络环境咋样?”
“用户都在一二线城市,网速挺好的。SEO 的话……产品得登录才能用,搜索引擎也爬不到。”
我看着他,不知道该说什么。
又一个被技术流行绑架的老同事。
一、SSR不是银弹,它是一把双刃剑
这两年,Next.js、Nuxt.js确实火得不行。
打开技术社区,满屏都是“从CSR迁移到SSR后,FCP提升50%”、“SSR才是前端正确的打开方式”。
但你有没有发现,很少有人告诉你:为了这点性能提升,你和你团队接下来半年要填多少坑。
1. 服务器账单:以前不要钱,现在要钱了
纯静态页面放OSS上,流量不大时一年可能就几百块。
上了SSR呢?
- 你需要一台服务器,或者云函数实例
- 你需要考虑并发,一台扛不住要上负载均衡
- 你还要担心服务器宕机,得配监控、告警、容灾
我一个朋友的项目,上了SSR后第一个月,云账单从300涨到了3000。老板拿着账单问他:“用户感受到变快了吗?多出来的钱能从收入里赚回来吗?”
他答不上来。
更扎心的是,后来发现大部分用户都是从首页跳详情页,首屏优化的收益根本覆盖不了成本。
2. 复杂度转移:以前是纯前端问题,现在是全栈问题
CSR项目,前端只管写页面,接口调不通那是后端的事。
SSR项目呢?
// 以前写页面,岁月静好
function UserProfile({ userId }) {
const [user, setUser] = useState(null)
useEffect(() => {
fetch(`/api/user/${userId}`).then(setUser)
}, [userId])
return <div>{user?.name}</div>
}
// 上了SSR之后,噩梦开始
export async function getServerSideProps({ params }) {
try {
// 要处理接口超时
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 3000)
const res = await fetch(`http://internal-api/user/${params.id}`, {
signal: controller.signal
}).catch(() => null)
clearTimeout(timeoutId)
if (!res || !res.ok) {
// 降级策略怎么写?
return { props: { user: null, fallback: true } }
}
const user = await res.json()
return { props: { user } }
} catch (error) {
// 服务端报错,用户看到什么?
return { notFound: true }
}
}
function UserProfile({ user, fallback }) {
// 客户端还要再校验一遍状态
const [mounted, setMounted] = useState(false)
useEffect(() => {
setMounted(true)
}, [])
if (!mounted) {
return <div>加载中...</div> // 防止hydrate报错
}
return <div>{user?.name}</div>
}
数据在服务端取,取不到怎么办?页面直接500还是降级成CSR? 服务端取的接口超时了,是要等待还是超时返回? 服务端和客户端的状态怎么对齐?一不小心就 hydrate 报错
我一个同事转做Next.js项目后发朋友圈:“自从上了SSR,我不仅要写React,还要会配Nginx、懂PM2、会分析内存泄漏。工资没涨,责任翻倍。”
底下点赞的全是前端。
3. 开发体验的割裂感
以前写CSR,window、document随便用,反正都在浏览器里。
写了SSR之后:
// 以前一行代码
const width = window.innerWidth
const token = localStorage.getItem('token')
const height = document.getElementById('app').offsetHeight
// 现在
const width = typeof window !== 'undefined' ? window.innerWidth : 1024
const token = typeof window !== 'undefined'
? localStorage.getItem('token')
: null
const [height, setHeight] = useState(0)
useEffect(() => {
setHeight(document.getElementById('app')?.offsetHeight || 0)
}, [])
- 第三方库如果不兼容服务端渲染,要动态导入
- localStorage、sessionStorage都不能直接用
- 路由跳转要小心,服务端没有history API
原本简单的逻辑,现在要写一堆防御代码。你在给代码做安检,但业务逻辑一点没变复杂。
二、你真的需要SSR吗?问自己三个扎心的问题
每次有人问我该不该上SSR,我都让他先回答三个问题。回答完,80%的人自己就放弃了。
问题一:你的产品靠搜索引擎吃饭吗?
这是最硬性的指标,也是最容易被拿来当借口的。
如果你的产品是:
- 内容型网站(博客、新闻、官网)
- 电商网站(需要被搜索引擎收录商品页)
那SSR确实有必要。因为爬虫可能不执行JS,或者执行不完整。
但如果你的产品是:
- 需要登录的后台管理系统
- 工具类、游戏类H5
- B端SaaS应用
- 社区类App的H5版(用户得先登录)
搜索引擎根本爬不到,SEO就是伪需求。
别拿SEO当借口,你只是想让简历里多一行Next.js。
问题二:你的首屏速度真的慢到不能忍了吗?
很多时候,我们觉得首屏慢,其实不是因为CSR不行,是代码写得太糙。
我见过一个“慢”的项目,分析下来:
// 问题代码示例
function App() {
const [data, setData] = useState(null)
const [user, setUser] = useState(null)
const [config, setConfig] = useState(null)
useEffect(() => {
// 串行调用,一个等一个
fetch('/api/data').then(res => res.json()).then(data => {
setData(data)
return fetch('/api/user')
}).then(res => res.json()).then(user => {
setUser(user)
return fetch('/api/config')
}).then(res => res.json()).then(setConfig)
// 图片没处理
new Image().src = 'https://example.com/big-banner.png'
// 第三方脚本同步加载
const script = document.createElement('script')
script.src = 'https://analytics.com/sdk.js'
document.head.appendChild(script)
}, [])
return <div>...</div>
}
这些问题,优化代码比换架构性价比高得多。
我去年优化过一个Vue2项目,纯CSR,首屏从3.2秒优化到1.1秒,只做了四件事:
// 优化后
useEffect(() => {
// 1. 并行调用
Promise.all([
fetch('/api/data'),
fetch('/api/user'),
fetch('/api/config')
]).then(...)
// 2. 图片转WebP + 懒加载
// 3. 第三方脚本异步
const script = document.createElement('script')
script.async = true
script.src = 'https://analytics.com/sdk.js'
// 4. 路由懒加载
const List = lazy(() => import('./pages/List'))
}, [])
没动架构,没重构,没加班。
问题三:你的团队准备好了吗?
这是最容易被忽略的,也是上线后最痛苦的。
上SSR意味着你的前端团队要开始写服务端代码:
- 有人写过Node.js吗?
- 有人配过Nginx吗?
- 有人处理过内存泄漏吗?
# 线上出问题了,你能处理吗?
curl -X POST https://your-site.com/api/user -H "Content-Type: application/json" -d '{"id":123}'
# 如果返回 502
# 是Node进程挂了?Nginx配置错了?接口超时了?
# 登录服务器
ssh user@your-server
pm2 logs
df -h # 磁盘满了?
free -m # 内存泄漏?
top # CPU爆了?
- 线上出问题了,有人能在凌晨两点爬起来回滚吗?
如果答案都是“没有”,那SSR上线的那天,就是团队噩梦的开始。
技术选型不仅要看技术好不好,还要看团队接不接得住。
三、那些比SSR更香的选择
如果你确实有性能痛点,但又不是非要SSR,其实有很多折中方案。
方案一:静态站点生成(SSG)
如果你的内容是静态的,或者更新不频繁,SSG是完美的选择。
// next.config.js
module.exports = {
// 构建时生成HTML
exportPathMap: async function() {
return {
'/': { page: '/' },
'/about': { page: '/about' },
'/blog/1': { page: '/blog/[id]', query: { id: '1' } },
}
}
}
- 构建时生成HTML,部署在CDN上
- 首屏速度极快,SEO友好
- 没有服务器成本,没有运维负担
Next.js、Nuxt.js、VitePress都支持。既有了SSR的首屏优势,又保留了CSR的简单部署。
方案二:静态部署 + 客户端渲染
大部分场景,这才是最优解。
// index.html
<!DOCTYPE html>
<html>
<head>
<!-- 骨架屏,让用户看到东西 -->
<style>
.skeleton { background: #f0f0f0; height: 20px; margin: 10px; }
</style>
</head>
<body>
<div id="root">
<div class="skeleton"></div>
<div class="skeleton"></div>
<div class="skeleton"></div>
</div>
<!-- 资源放CDN,并行加载 -->
<link rel="preconnect" href="https://api.example.com">
<script src="https://cdn.example.com/react.js" async></script>
<script src="https://cdn.example.com/app.js" async></script>
</body>
</html>
- HTML放CDN,全球加速
- 接口走API网关,BFF层做聚合
- 配合预加载、懒加载、骨架屏,体验一点都不差
我现在的项目就是这种方案:React + Vite,打包后放OSS,CDN加速。首屏1.2秒,月PV百万,服务器成本主要是BFF层的几个云函数,加起来不到500块。
够用就好,别为了炫技给自己挖坑。
方案三:部分页面SSR,大部分页面CSR
如果你确实有少数页面需要SEO(比如官网、 landing page),其他页面需要登录。
// next.config.js
module.exports = {
// 只有这些页面走SSR
pageExtensions: ['ssr.js', 'page.js'],
async rewrites() {
return [
// landing page走SSR
{
source: '/',
destination: '/landing.ssr',
},
// 其他页面走静态CSR
{
source: '/app/:path*',
destination: '/app.html',
}
]
}
}
那可以只把这几个页面抽出来做SSR,剩下的保持CSR。
Next.js支持多页应用模式,Vue也有混合渲染方案。没必要为了10%的页面,让90%的页面承担复杂度。
四、写在最后:技术选型不是为了简历好看
写这篇文章,不是为了否定SSR。
SSR是好技术,Next.js是好框架。我在合适的项目里也用它,确实能解决问题。
但我不喜欢一种风气:明明是个简单的H5活动页,非要上Next.js;明明是个后台管理系统,非要搞服务端渲染;明明首屏已经1.5秒了,非要重构到1.2秒。
为了那0.5秒的优化,搭进去团队半年的维护成本,值吗?
技术选型的标准,不是“别人都用”,也不是“大厂在用”,而是:
- 能解决我们现在的痛点吗?
- 团队能驾驭吗?
- 维护成本扛得住吗?
- 换来的收益对得起付出的代价吗?
别让技术选型,变成一场给简历镀金的表演。
最后想问问大家: 你们见过哪些“没必要上SSR但硬上”的项目?踩过哪些坑?欢迎在评论区互相伤害。
如果你也看不惯那些为了炫技而复杂化的技术选型,欢迎关注我,一起聊聊真实的前端。