假设我们有以下页面:
/profile-edit
:一个私人页面,用户可以在其中编辑他的个人资料,我们称他为“Sam James”。
/profile/sam-james
:公开可用的个人资料页面(如 LinkedIn、Facebook 等,通常应该具有非常好的 SEO 和性能)
路线规则可能如下所示:
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
"/edit-profile": { ssr: false },
"/profile/**": { swr: true },
// OR?
// "/profile/**": { static: true },
},
});
公共用户配置文件页面(例如
profile/sam-james
)通过静态或SWR 进行缓存。这应该会大大提高性能和 SEO 得分。
现在这是棘手的部分:每次用户更新他的个人资料时,他当前的公共个人资料页面都应该失效,并且在下一次请求该页面时,应该使用新数据更新缓存。
您可以在 Nuxt 3 中手动在服务器上进行这种失效吗?
Next.js 已经具有此功能,并将其称为“按需重新验证”:https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regenesis#on-demand-revalidation
我找到了您正在寻找的案例的部分解决方案。没有带缓存的公共 api,但有一个解决方法。首先,您设置硝基存储(fs、redis 等)
nitro: {
storage: {
db: {
driver: 'fs',
base: './.data/db'
}
},
},
routeRules: {
'/profile/**': {
swr: 3600,
cache: {
base: 'db',
},
}
}
然后按照上面的示例将routeRules分配给特定存储。
然后设置以下服务器路由(project/server/routes/invalidate.ts)
export default defineEventHandler(async () => {
const storage = await useStorage('db');
const keys = await storage.getKeys();
const keysToInvalidate = [];
for (let key of keys) {
if (key.startsWith('nitro:routes:_:_:profile')) {
keysToInvalidate.push(key);
}
}
for (let key of keysToInvalidate) {
storage.removeItem(key);
}
return JSON.stringify(keysToInvalidate);
});
然后当你想要失效时你需要调用http://website/invalidate 这将刷新与个人资料页面相关的所有键。但是,您必须对浏览器缓存进行一些操作。我还不确定如何修复浏览器缓存,但简单的 ctrl + shift + r 有助于重置它,您会看到更新的页面。遗憾的是我到目前为止找不到更好的解决方案。
您可以使用此 Nuxt 模块:https://nuxt-multi-cache.dulnan.net/,它具有适合您的情况的“清除”API。这里是 purge api 文档:https://nuxt-multi-cache.dulnan.net/features/api
另一种定制的缓存整个页面的解决方案,无需前端缓存并具有重置缓存功能:
server/middleware/cache.ts
:
import {defineEventHandler, setResponseHeaders} from "h3";
import {ServerResponse} from "http";
interface CachedItem {
data: string;
headers: Record<string, string>,
statusCode: number,
setAt: number,
}
export default defineEventHandler( async (event) => {
if (process.env.NODE_ENV !== 'production') {
return;
}
if (!(event.path && (
event.path.startsWith('/search') ||
event.path.startsWith('/card') ||
event.path.startsWith('/service') ||
event.path.startsWith('/article')
))) {
// Filter only pages we want to cache
return;
}
const cache = useStorage('db');
const cacheKey = 'mobicard.com.ua-nuxt-cache-' + event.path;
if (await cache.hasItem(cacheKey)) {
console.log(`Has item by key: ${cacheKey}. Returning cached`);
const item = await cache.getItem(cacheKey) as CachedItem;
const time = new Date().getTime() / 1000;
if (item.setAt && (time - item.setAt) < 60 * 60 * 24) {
event.node.res.statusCode = item.statusCode;
setResponseHeaders(event, {
'Content-Type': 'text/html;charset=utf-8',
...item.headers,
});
return item.data;
} else {
console.log('Skip cached item b/c of too old');
}
}
const _end = event.res.end;
event.res.end = function(
arg1: Function | any,
arg2?: Function | string,
arg3?: Function
) {
if (typeof arg1 === 'function') {
// No chunk provided, we have to end here.
return _end.call(event.node.res, arg1)
}
const response = event.node.res as ServerResponse;
const item : CachedItem = {
data: arg1,
headers: response.getHeaders(),
statusCode: response.statusCode,
setAt: new Date().getTime() / 1000,
}
cache.setItem(cacheKey, item);
return _end.call(event.node.res, arg1, arg2, arg3)
}
});
和
server/routes/invalidate.ts
用于重置缓存:
import {createError, defineEventHandler} from "h3";
export default defineEventHandler(async (event) => {
const storage = await useStorage('db');
const url = new URL('http://localhost/' + event.path);
if (!url.searchParams.get('path')) {
return createError({statusCode: 404, statusMessage: 'Not found'});
}
const path = (url.searchParams.get('path') as string);
const keys = await storage.getKeys();
const keysToInvalidate = [];
for (const k of keys) {
if (k.includes(path)) {
keysToInvalidate.push(k);
}
}
for (const key of keysToInvalidate) {
await storage.removeItem(key);
}
return JSON.stringify(keysToInvalidate);
})
类似于 http://.../invalidate?path=%part-of-page-path%