We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
我们是用 SSR 除了 SEO,最重要的就是性能优化,也就是白屏时间
客户端渲染优化常规的思路像 拆分chunk 、资源上 CDN、图片压缩,异步import 之类的,nuxt 已经有很好的支持了
在服务端渲染中,核心的逻辑就是在服务器上,把之前在客户端进行的实例化转变成在服务器中执行 vnode to htmlstring
那么每次在向服务端请求页面的时候,都会去执行这个行为,之前通过客户端渲染作为开发者无法干预,现在变成了服务端,在实现的时候是不是就可以把这些已经 render 好的 htmlstring 缓存起来,下次请求或者其他人请求的时候直接 return 就可以急速相应了
对应到nuxt实际项目中,开发者能做的就有这三道缓存(route、component、api)
路由缓存,项目中的 about 、home、info 类型的页面,一般在确定好之后,页面这些常态的页面都不会怎么去变动,那当请求过来的时候,每次都去执行 ssr render 就显得有点浪费我们的 cpu 了。于是,页面缓存就横空出世
在 server 端加一个劫持 route 的中间件
pageCache 的实现思路
import { LRUCache } from 'lru-cache'; // 设置缓存池 const cache = new LRUCache({ maxAge: 1000 * 60 * 2, // 有效期2分钟 max: 1000 // 最大缓存数量 }); // 需要缓存的路由列表 const cacheRouteList = ['/vote'] export default defineEventHandler((event) => { try { const { req, res } = event.node; const cacheKey = req.url; if (!cacheRouteList.includes(cacheKey)) return const cacheData = cache.get(cacheKey); if (cacheData) { console.log('hit cache'); return res.end(cacheData, 'utf8'); } // 劫持 res end 函数,设置缓存 const originalEnd = res.end; res.end = function (data,) { cache.set(cacheKey, data); originalEnd.call(res, ...arguments); }; } catch (error) { console.log('cache error', error.message); } });
和页面缓存的背景一致,项目内的有些组件开发过程也有类似的需求
在翻阅资料的时候,看到 nuxt2 是通过设置 vue ssr render 中的 this.options.render.bundleRenderer 实现的,并且配合 组件内serverCacheKey 这个配置来控制是否缓存,but 这两个配置在 vue3 和 nuxt3 中都找不到,寻思良久,nuxt 提供了 <ClientOnly> 组件来实现只在客户端渲染,那不是也可以用高阶组件来实现这样的功能
this.options.render.bundleRenderer
serverCacheKey
<ClientOnly>
组件结构
核心实现
<script> import { defineComponent, useSSRContext, useSlots, getCurrentInstance, h } from 'vue'; import { getCacheKey, getCachedComponent, renderSlot, cache } from './helpers.js'; export default defineComponent({ name: 'ComCache', props: { tag: { type: String, default: 'div' }, noCache: { type: Boolean, default: false }, cacheKey: { type: String, default: '' } }, async setup(props) { const slots = useSlots(); if (!slots.default) { return () => ''; } const defaultSlot = slots.default(); const first = defaultSlot[0]; const isServer = import.meta.server; if (isServer && !props.noCache) { const debug = false; const cacheKey = getCacheKey(props, first, debug); if (!cacheKey) { return () => h(props.tag, slots.default()); } const currentInstance = getCurrentInstance(); const ssrContext = useSSRContext(); if (!ssrContext) { if (debug) { console.warn('Failed to get SSR context.'); } return () => h(props.tag, slots.default()); } const genComp = async () => { if (!currentInstance?.parent) { if (debug) { console.warn('Failed to get parent component in Cacheable component.'); } return; } const cached = getCachedComponent(cacheKey); if (cached) { console.log(cacheKey + ' is cached'); return cached; } const data = await renderSlot(slots, currentInstance.parent); try { cache.set(cacheKey, data); } catch (e) { if (e instanceof Error) { console.error(e.message); } } return data; }; try { const comp = await genComp(); if (comp) { return () => h(props.tag, { innerHTML: comp }); } } catch (e) { if (debug) { console.error('Failed to get component from cache.'); } if (e instanceof Error) { console.error(e.message); } } } // 客户端渲染需要 return () => h(props.tag, slots.default()); } }); </script> // 使用方式 <ComCache> <Your Com> </ComCache>
可以看到,整个实现就是通过劫持 render slot 来实现的,值得一提的是,组件的 key 是由 组件名、组价props组合 hash 而成的,变化props 参数缓存就失效会进行重新缓存
仅考虑在服务端发起请求的case,这个直接上代码
import { defineStore } from 'pinia'; import { LRUCache } from 'lru-cache'; const cache = new LRUCache({ maxAge: 1000 * 60 * 2, // 有效期2分钟 max: 1000 // 最大缓存数量 }); export const useMainStore = defineStore('vote', () => { const info = ref({}); const setInfoData = async (params) => { // 客户端就不执行了 if (!import.meta.server) return; console.log('setInfoData start'); const url = 'https://www.app.com/api/list?id=' + params; if (cache.get(url)) { console.log('setInfoData hit cache'); info.value = cache.get(url); return; } const data = await useFetch(url); cache.set(url, info.value = data.data.value); console.log('set cache success'); }; return { info, setInfoData }; });
这个代码执行的时候,组件是直接在 setup 中发起,接口如果返回的比较慢的话,会直接影响页面的数据会吐,(毕竟 js 是单线程)于是有了第二版,代理转发
业务组件的请求在 onMouted 的钩子执行,action 修改为
onMouted
const setInfoData = async () => { const data = await $fetch('/api/hello', { body: { hello: 'world' }, method: 'POST' }); voteData.value = data; };
/api/hello 这个接口的定义在
/api/hello
import { LRUCache } from 'lru-cache'; const cache = new LRUCache({ maxAge: 1000 * 60 * 2, // 有效期2分钟 max: 1000 // 最大缓存数量 }); export default defineEventHandler(async (event) => { const req = await readBody(event); const cacheKey = event.node.req.url + '@' + JSON.stringify(req); if (cache.get(cacheKey)) { console.log('iscache', cacheKey); return cache.get(cacheKey); } const data = await $fetch('https://qqlykm.cn/api/weather/get'); cache.set(cacheKey, data); return { data }; });
这样就不会影响页面的加载了,两种方式,如果接口内容需要直出,就使用第一种,否则就第二
The text was updated successfully, but these errors were encountered:
https://www.hai-fe.com/docs/nuxt/apiCache.html
Sorry, something went wrong.
No branches or pull requests
前言
我们是用 SSR 除了 SEO,最重要的就是性能优化,也就是白屏时间
客户端渲染优化常规的思路像 拆分chunk 、资源上 CDN、图片压缩,异步import 之类的,nuxt 已经有很好的支持了
那除了常规的这种优化,还能再怎么优化呢
在服务端渲染中,核心的逻辑就是在服务器上,把之前在客户端进行的实例化转变成在服务器中执行 vnode to htmlstring
那么每次在向服务端请求页面的时候,都会去执行这个行为,之前通过客户端渲染作为开发者无法干预,现在变成了服务端,在实现的时候是不是就可以把这些已经 render 好的 htmlstring 缓存起来,下次请求或者其他人请求的时候直接 return 就可以急速相应了
对应到nuxt实际项目中,开发者能做的就有这三道缓存(route、component、api)
route 缓存
路由缓存,项目中的 about 、home、info 类型的页面,一般在确定好之后,页面这些常态的页面都不会怎么去变动,那当请求过来的时候,每次都去执行 ssr render 就显得有点浪费我们的 cpu 了。于是,页面缓存就横空出世
在 server 端加一个劫持 route 的中间件
pageCache 的实现思路
组件缓存
和页面缓存的背景一致,项目内的有些组件开发过程也有类似的需求
在翻阅资料的时候,看到 nuxt2 是通过设置 vue ssr render 中的
this.options.render.bundleRenderer
实现的,并且配合 组件内serverCacheKey
这个配置来控制是否缓存,but 这两个配置在 vue3 和 nuxt3 中都找不到,寻思良久,nuxt 提供了<ClientOnly>
组件来实现只在客户端渲染,那不是也可以用高阶组件来实现这样的功能组件结构
核心实现
可以看到,整个实现就是通过劫持 render slot 来实现的,值得一提的是,组件的 key 是由 组件名、组价props组合 hash 而成的,变化props 参数缓存就失效会进行重新缓存
api 缓存
仅考虑在服务端发起请求的case,这个直接上代码
这个代码执行的时候,组件是直接在 setup 中发起,接口如果返回的比较慢的话,会直接影响页面的数据会吐,(毕竟 js 是单线程)于是有了第二版,代理转发
业务组件的请求在
onMouted
的钩子执行,action 修改为/api/hello
这个接口的定义在这样就不会影响页面的加载了,两种方式,如果接口内容需要直出,就使用第一种,否则就第二
The text was updated successfully, but these errors were encountered: