您可以在 Nuxt 3 中使用混合渲染根据用户操作使 SWR/静态路由无效吗?

问题描述 投票:0回答:3

假设我们有以下页面:

/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

vue.js nuxt.js nuxt3.js
3个回答
2
投票

我找到了您正在寻找的案例的部分解决方案。没有带缓存的公共 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 有助于重置它,您会看到更新的页面。遗憾的是我到目前为止找不到更好的解决方案。


0
投票

您可以使用此 Nuxt 模块:https://nuxt-multi-cache.dulnan.net/,它具有适合您的情况的“清除”API。这里是 purge api 文档:https://nuxt-multi-cache.dulnan.net/features/api


0
投票

另一种定制的缓存整个页面的解决方案,无需前端缓存并具有重置缓存功能:

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%

© www.soinside.com 2019 - 2024. All rights reserved.