hiccLoghicc log by wccHipo日志

卷起来,前端建站SSG,SSR,ISR,Hydration, Island...一网打尽

toc

Intro

React, Vue, Angular 三足鼎立之后,前端界又开始“卷起来了”,不过战火已经单页渲染蔓延到了服务端渲染建站。

NextJs成名已久,功能全面,astro Island 独步天下,qwik No hydration 异军突起。remix 守正出奇,无招胜有招。

今天我们来一块说道说道。

让建站酷起来

SSR

SSR (server-side rendering)相对比较好理解,它算是是SPA大行其道之前主流的方式,简单来说就是服务端,拉取数据组装页面,返回前端HTML。

这种方式优点在于简单直接,每次访问都能够拿到最新数据。但缺点是有点费网,如果流量大,用户分布广就需要费不少功夫优化,这里不再展开。

SSG

SSG(static site generation)顾名思义,就是提前生成静态的网站。优点是性能好,HTML推送到CDN,成本体验也是最佳。这里的问题是适用场景有限,一般用作内容不太变化的场景。

ISR

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的范畴,复杂场景仍然无法应对。

Edge Rendering

这个严格来说是站点的部署形态,算是新时代边缘计算的一种应用场景。类似以前静态的资源推送到CDN,让用户能够就近享受最佳的体验。现在动态网站也能够在边缘渲染,让用户享受到更佳的体验。

这里的问题仍在在于数据,除非是经过特意的改造,一般网站的数据仍需要请求到一个中心化的源服务中。

一方面用户体验仍然不佳,另一方面源服务压力仍然巨大,成本不低。

上面NextJs的ISR或多或少也是为了解决这个问题。当然另一个更彻底的思路,在边缘的节点上也能有数据持久化的能力,例如cloudflare,或者使用一些分布式的数据库,这里不再展开。

酷的“代价”

这些更现代的建站方式确实很炫酷,但是也不是没有缺点。一点就是页面数据会变大(移动站点不太友好),另一方面首次可交互时间也会延后。

这里实际上就涉及Hydration注水的概念。

Hydration

Hydration注水,大家可能较少听到,但是它却是现代前端spa,mpa同构框架的关键。

同一份代码,先server端跑生成一份一定状态计算后的HTML,然后需要在前端“活过来”的过程大概就称之为注水了。

这里不同的框架实现的细节不同,但是通用的问题是,事件在注水之后才能交互。页面越复杂这段时间越长,越影响体验,这点也是各大框架大显身手的地方。

Selective Hydration

渐进可选式的注水,这里的代表就是大名鼎鼎的React,借助于fiber架构,React能够打断传统递归式的注水,让应用能够优先处理交互事件,这里框架层面比较复杂,具体效果怎么样也有待观察。

Islands Architecture

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 />

No hydration

最快的注水,就是无注水😄。这就是qwik 框架的主要卖点。

const Counter = component$(() => { const store = useStore({ htmlCount: 0, cmpCount: 0, }); return ( <div> <button onClick$={() => store.htmlCount++}>{store.htmlCount}</button> <CmpButton onClick$={() => store.cmpCount++}>{store.cmpCount}</CmpButton> </div> ); });

语法和react类似,区别于$的标注,框架依赖这些标注,在构建的时候会将这些组件或者逻辑代码独立成单个js。

<div> <button q:obj="1" on:click="./chunk-a.js#Counter_button_onClick[0]">0</button> <div>...</div> </div>

事件在点击之后或者prefetch,才会懒加载对应的js。以此来实现无注水。

没噱头的remix

remix 是react-router团队的新作。

Focused on web standards and modern web app UX, you’re simply going to build better websites

从介绍上来看似乎没什么噱头,但似乎这就是它们最大的卖点。坚持标准的语法,只使用经典有效的手段。从remix-vs-next的文章来看效果确实不错,守正出奇。

上面只是蜻蜓点水介绍了现代框架中比较新潮点。每个地方深挖下去都有不错的收获。花拳百出,核心都是为了提高开发体验,增强用户体验。


参考