为什么要在服务器上渲染
一句话总结
SSR是让第一个响应字节中包含内容的事情。作为回报,会产生生成字符串的代码,在那里会出现XSS和水合不一致。
有什么不同
客户端渲染(CSR)的第一响应是这样的。
<body><div id="root"></div><script src="/app.js"></script></body>
**没有内容。**浏览器收到JS,执行,调用API,然后才出现文字。这段时间用户看到的是空白屏幕。而且阅读HTML不仅仅是人类。
- 搜索引擎和链接预览 — 很多不运行JavaScript的爬虫。如果OG标签和正文不在第一个HTML中,就不在了。
- 慢速设备·网络 — JS软件包到达并运行之前都是空白屏幕。
- JS失败的话——什么都不出来
SSR在第一次响应中包含完整的HTML。然后JavaScript到达,只在已经存在的DOM上附加事件——这就是激活。
不是重新画,而是把手伸进已经画好的东西里。
所以产生的问题1 — XSS
服务器渲染最终是字符串拼接。
`<h1>${user.name}</h1>`
user.name这个<img src=x onerror=alert(1)>这样的话就直接执行了。必须亲自做前端框架代替阻止的事情。
在HTML正文中插入时必须更改的五个字。
& → & (먼저 바꿔야 한다)
< → <
> → >
" → "
' → '
&如果不先换的话已经制作好的<去&lt;又变得一团糟。顺序是规则的一部分。
而且**每个位置的规则都不一样。**HTML正文、属性值、URL、JavaScript字符串、CSS都要求不同的逃避字符。没有一个函数可以全部用一个。
问题2 — 种植初始状态时
服务器已经把数据拿来了,所以客户端不需要再调用。所以植入HTML。
<script>window.__STATE__ = {"name":"...</script><script>alert(1)</script>"}</script>
**在数据中</script>如果包含的话,脚本标签就结束了。**之后是新的脚本。虽然是JSON,所以感觉很安全,但完全不是。
阻止的方法。
JSON.stringify(state).replace(/</g, '\u003C')
<将转换为Unicode escape字符时,JSON值保持不变,标签也不会断开。<!--哇<script也一起被堵住了。
更安全的办法是放在未执行的标签中。
<script type="application/json" id="state">{...}</script>
type这个application/json如果这样,浏览器将无法运行。在客户端中JSON.parse(el.textContent)读作罗。但是</script>仍然需要避难所。
问题3 — 水合不一致
是最难找到的种类。
**服务器生成的HTML和客户端第一次生成的HTML没有必要不同。**如果不同,框架会发出警告,最坏的情况是把那个部分重新画一遍。
原因几乎总是这四个中的一项。
- 时间 —
new Date().toLocaleString(). 服务器绘制的视图和客户端绘制的视图不同 - 随机数 —
Math.random()制作的id - 本地化·时区 — 服务器为UTC,浏览器为KST
- 浏览器专用值 —
window.innerWidth,localStorage
**渲染函数必须是纯粹的。**相同的输入,相同的输出。
如果需要时间或随机数,**在服务器上生成,放入状态中发送下来。**不调用在渲染函数中。在这个练习的第5阶段,评分器Date.now哇Math.random更换后再渲染两次进行比较——在渲染中呼叫它们就会立即显示出来。
流媒体SSR
如果制作完整个页面后发送的话,最慢的数据之一会抓住全部。
[셸: <head>, 스타일, 상단바] ← 즉시 보낼 수 있다
[본문: DB 조회 300ms] ← 이것 때문에 셸까지 늦어진다
流媒体首先发送一个头部,然后发送准备好的片段。浏览器会按照接收到的顺序解析,因此开始更早地接收CSS和字体。
这就是React的renderToPipeableStream科<Suspense>是做的事情。和前面在SSE实训中看到的原理一样,也像屏蔽一样——代理缓冲和gzip。
现金——SSR中最危险的地方
SSR页面容易混淆**个性化内容。**根据登录名、购物车数量、权限的不同,会有不同的菜单。
对于那个回答Cache-Control: public如果发生这种情况,**CDN会把A的页面交给B。**这是实际发生的事故,发现时已经太晚了。
익명·동일한 페이지 Cache-Control: public, s-maxage=60, stale-while-revalidate=300
로그인 사용자 페이지 Cache-Control: private, no-store
**基本值private, no-store所以,选择只公开可以公开的,然后打开比较安全。**相反的话,总有一天会漏出来。
什么时候不是SSR呢
没有必要全部做SSR。
- 静态生成(SSG)—如果内容经常不变,在构建时创建。最便宜最快
- CSR — 登录后像仪表板一样不需要SEO,是互动性很强的画面
- SSR — 随着内容因每个请求而不同,必须包含在第一个HTML中的画面
SSR会花服务器费用。每次请求都会花费渲染费用。如果没有缓存,流量就会直接占用CPU。选择的标准不是“是否快”,而是“第一页HTML必须有内容吗”。
现场—在打开SSR之前要确定三件事
如果不一起决定粘贴的日子,以后一定会被咬到的。
- 缓存键 — 响应是否因用户而异。如果不同的话
private, no-store,对所有人来说都一样的话s-maxage. 如果不确定的话,就关起来再开始。 - 服务器上没有的API —
window·localStorage·document引用代码混入服务器捆绑包的话,整个页面都会死掉。在入口点过滤一次,在CI中确认。 - 数据晚了的时候——要抓住整个页面,还是只清空那个部分,然后在客户端填充?
这三者不是代码,而是决定。如果不提前决定,在出现障碍的日子里会当场决定。