React, Vue, Angular 三足鼎立之后,前端界又开始“卷起来了”,不过战火已经单页渲染蔓延到了服务端渲染建站。
NextJs成名已久,功能全面,astro Island 独步天下,qwik No hydration 异军突起。remix 守正出奇,无招胜有招。
今天我们来一块说道说道。
SSR (server-side rendering)相对比较好理解,它算是是SPA大行其道之前主流的方式,简单来说就是服务端,拉取数据组装页面,返回前端HTML。
这种方式优点在于简单直接,每次访问都能够拿到最新数据。但缺点是有点费网,如果流量大,用户分布广就需要费不少功夫优化,这里不再展开。
SSG(static site generation)顾名思义,就是提前生成静态的网站。优点是性能好,HTML推送到CDN,成本体验也是最佳。这里的问题是适用场景有限,一般用作内容不太变化的场景。
ISR(Incremental Static Regeneration)渐进式的静态内容生成。应该是NextJS的首创,从一定程度上优化了SSG了的问题。简单来说就是提供一种机制能够在server中自动的执行SSG,这点优点也很明显,一是能够让内容尽量保持新鲜,而是从访问行为上仍然保持静态访问。
照搬next的思路,有两种方式:
简单来说就是类似js setInterval的方式按照一定是时间段刷新server端的构建。
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// revalidation is enabled and a new request comes in
export async function getStaticProps() {
const res = await fetch('https://.../posts')
const posts = await res.json()
return {
props: {
posts,
},
// Next.js will attempt to re-generate the page:
// - When a request comes in
// - At most once every 10 seconds
revalidate: 10, // In seconds
}
}
// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// the path has not been generated.
export async function getStaticPaths() {
const res = await fetch('https://.../posts')
const posts = await res.json()
// Get the paths we want to pre-render based on posts
const paths = posts.map((post) => ({
params: { id: post.id },
}))
// We'll pre-render only these paths at build time.
// { fallback: blocking } will server-render pages
// on-demand if the path doesn't exist.
return { paths, fallback: 'blocking' }
}
export default Blog
而按需的方式是算是NextJS对上述方式的优化。机制server 对外提供http的API。内容变动调用这个API就好。
// pages/api/revalidate.js
export default async function handler(req, res) {
// Check for secret to confirm this is a valid request
if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
return res.status(401).json({ message: 'Invalid token' })
}
try {
// this should be the actual path not a rewritten path
// e.g. for "/blog/[slug]" this should be "/blog/post-1"
await res.revalidate('/path-to-revalidate')
return res.json({ revalidated: true })
} catch (err) {
// If there was an error, Next.js will continue
// to show the last successfully generated page
return res.status(500).send('Error revalidating')
}
}
当然ISR仍然是SSG的范畴,复杂场景仍然无法应对。
这个严格来说是站点的部署形态,算是新时代边缘计算的一种应用场景。类似以前静态的资源推送到CDN,让用户能够就近享受最佳的体验。现在动态网站也能够在边缘渲染,让用户享受到更佳的体验。
这里的问题仍在在于数据,除非是经过特意的改造,一般网站的数据仍需要请求到一个中心化的源服务中。
一方面用户体验仍然不佳,另一方面源服务压力仍然巨大,成本不低。
上面NextJs的ISR或多或少也是为了解决这个问题。当然另一个更彻底的思路,在边缘的节点上也能有数据持久化的能力,例如cloudflare,或者使用一些分布式的数据库,这里不再展开。
这些更现代的建站方式确实很炫酷,但是也不是没有缺点。一点就是页面数据会变大(移动站点不太友好),另一方面首次可交互时间也会延后。
这里实际上就涉及Hydration注水的概念。
Hydration注水,大家可能较少听到,但是它却是现代前端spa,mpa同构框架的关键。
同一份代码,先server端跑生成一份一定状态计算后的HTML,然后需要在前端“活过来”的过程大概就称之为注水了。
这里不同的框架实现的细节不同,但是通用的问题是,事件在注水之后才能交互。页面越复杂这段时间越长,越影响体验,这点也是各大框架大显身手的地方。
渐进可选式的注水,这里的代表就是大名鼎鼎的React,借助于fiber架构,React能够打断传统递归式的注水,让应用能够优先处理交互事件,这里框架层面比较复杂,具体效果怎么样也有待观察。
Islands Architecture隔离交互组件。astro框架首打的特性。
这里理解也比较简单,让静态页面保持静态,只让页面上需要复杂交互的才去走注水的那一套,保持交互性。
无交互场景
---
// Example: Use a static React component on the page, without JavaScript.
import MyReactComponent from '../components/MyReactComponent.jsx';
---
<!-- 100% HTML, Zero JavaScript loaded on the page! -->
<MyReactComponent />
有交互的场景。
---
// Example: Use a dynamic React component on the page.
import MyReactComponent from '../components/MyReactComponent.jsx';
---
<!-- This component is now interactive on the page!
The rest of your website remains static and zero JS. -->
<MyReactComponent client:load />
最快的注水,就是无注水