MST

星途 面试题库

面试题:复杂场景下 Next.js 的 getStaticProps 与 getStaticPaths 协同设计

设想一个电商 Next.js 应用,产品分类页面和每个产品详情页面都需要静态生成。产品分类可能会动态更新,且不同分类下产品数量庞大。此外,某些产品可能有特定的变体,这些变体也需要单独的静态页面。请设计一个完整的方案,利用 getStaticProps 和 getStaticPaths 来实现这个需求,包括数据缓存策略、增量静态再生的设计以及应对高并发访问的优化措施,并详细说明每一步的设计思路和可能遇到的问题及解决方案。
30.7万 热度难度
前端开发Next.js

知识考点

AI 面试

面试题答案

一键面试

1. 使用 getStaticPropsgetStaticPaths 实现静态生成

getStaticPaths 设计思路

  • 对于产品分类页面,由于分类可能动态更新,我们可以从数据库或 API 获取所有分类数据。例如,假设我们有一个数据库存储分类信息,我们可以编写如下代码获取分类路径:
export async function getStaticPaths() {
    const res = await fetch('https://your-api.com/categories');
    const categories = await res.json();
    const paths = categories.map(category => ({
        params: { categoryId: category.id.toString() }
    }));
    return { paths, fallback: false };
}
  • 对于产品详情页面,我们需要考虑不同分类下产品数量庞大的情况。可以先获取每个分类下的产品列表,然后生成对应的路径。例如:
export async function getStaticPaths() {
    const categoryRes = await fetch('https://your-api.com/categories');
    const categories = await categoryRes.json();
    const paths = [];
    for (const category of categories) {
        const productRes = await fetch(`https://your-api.com/products?categoryId=${category.id}`);
        const products = await productRes.json();
        for (const product of products) {
            paths.push({
                params: { productId: product.id.toString() }
            });
        }
    }
    return { paths, fallback: false };
}
  • 对于产品变体,假设变体信息存储在产品对象中,我们可以在生成产品路径时,同时生成变体路径。例如:
export async function getStaticPaths() {
    const categoryRes = await fetch('https://your-api.com/categories');
    const categories = await categoryRes.json();
    const paths = [];
    for (const category of categories) {
        const productRes = await fetch(`https://your-api.com/products?categoryId=${category.id}`);
        const products = await productRes.json();
        for (const product of products) {
            paths.push({
                params: { productId: product.id.toString() }
            });
            if (product.variants) {
                for (const variant of product.variants) {
                    paths.push({
                        params: { productId: product.id.toString(), variantId: variant.id.toString() }
                    });
                }
            }
        }
    }
    return { paths, fallback: false };
}

可能遇到的问题及解决方案

  • 性能问题:如果分类和产品数量过多,生成路径的过程可能会很耗时。解决方案是可以在服务器端进行缓存,例如使用 Redis 缓存分类和产品数据,减少数据库或 API 的请求次数。同时,可以分页获取数据,分批次生成路径。
  • 动态更新问题:如果分类或产品数据更新,已生成的路径可能不准确。可以通过设置较短的缓存时间或者使用增量静态再生来解决。

getStaticProps 设计思路

  • 对于产品分类页面,getStaticProps 可以根据 params.categoryId 获取该分类下的产品列表。例如:
export async function getStaticProps(context) {
    const { categoryId } = context.params;
    const res = await fetch(`https://your-api.com/products?categoryId=${categoryId}`);
    const products = await res.json();
    return {
        props: {
            products
        },
        revalidate: 60 * 5 // 5分钟后重新验证
    };
}
  • 对于产品详情页面,getStaticProps 根据 params.productId 获取产品详细信息。如果有变体,根据 params.variantId 获取变体信息。例如:
export async function getStaticProps(context) {
    const { productId, variantId } = context.params;
    const res = await fetch(`https://your-api.com/products/${productId}`);
    const product = await res.json();
    let variant;
    if (variantId) {
        variant = product.variants.find(v => v.id.toString() === variantId);
    }
    return {
        props: {
            product,
            variant
        },
        revalidate: 60 * 5 // 5分钟后重新验证
    };
}

可能遇到的问题及解决方案

  • 数据一致性问题:由于设置了 revalidate 时间,在重新验证前,页面显示的数据可能不是最新的。可以通过在管理后台更新数据时,主动触发页面重新生成(如果支持的话)。
  • API 调用失败:如果 API 调用失败,getStaticProps 会返回错误。可以设置重试机制,例如使用 axios - retry 库,对失败的 API 调用进行重试。

2. 数据缓存策略

  • 服务器端缓存:使用 Redis 等缓存工具,缓存分类数据、产品列表数据和产品详情数据。在 getStaticPathsgetStaticProps 中优先从缓存获取数据,如果缓存不存在,再从数据库或 API 获取,并将获取到的数据存入缓存。例如,使用 ioredis 库:
const Redis = require('ioredis');
const redis = new Redis();

export async function getStaticPaths() {
    let categories = await redis.get('categories');
    if (!categories) {
        const res = await fetch('https://your-api.com/categories');
        categories = await res.json();
        await redis.set('categories', JSON.stringify(categories));
    } else {
        categories = JSON.parse(categories);
    }
    const paths = categories.map(category => ({
        params: { categoryId: category.id.toString() }
    }));
    return { paths, fallback: false };
}
  • 客户端缓存:可以使用浏览器的 localStoragesessionStorage 缓存一些不敏感且变动不大的数据,如分类列表。在页面加载时,先从本地缓存获取数据,减少服务器请求。但要注意设置合适的缓存过期时间。

3. 增量静态再生设计

  • 原理:Next.js 的增量静态再生允许在页面已经生成静态 HTML 后,在后台重新生成该页面。我们在 getStaticProps 中设置 revalidate 字段,如上面代码示例中的 revalidate: 60 * 5,表示 5 分钟后重新验证数据。
  • 设计思路:对于产品分类页面和产品详情页面,根据数据变化的频率设置不同的 revalidate 时间。例如,对于变化频繁的分类,设置较短的 revalidate 时间;对于相对稳定的产品详情,设置较长的 revalidate 时间。
  • 可能遇到的问题及解决方案:在重新验证期间,如果数据发生多次变化,可能导致页面数据不是最新的。可以通过结合管理后台,在数据更新时主动触发页面重新生成,或者缩短 revalidate 时间,但这可能会增加服务器负载。

4. 应对高并发访问的优化措施

  • 负载均衡:使用 Nginx 等负载均衡器,将高并发请求均匀分配到多个服务器实例上,避免单个服务器过载。
  • CDN:启用 CDN(如 Cloudflare、阿里云 OSS 等),缓存静态资源(如 HTML、CSS、JS 文件),将资源缓存到离用户更近的节点,加快用户访问速度,减轻服务器压力。
  • 数据库连接池:在获取数据时,使用数据库连接池(如 mysql -2 的连接池),避免在高并发时频繁创建和销毁数据库连接,提高数据库访问效率。
  • 优化静态文件:压缩和优化 CSS、JS 文件,减少文件大小,加快页面加载速度。可以使用工具如 terser 压缩 JS,css - minimizer - webpack - plugin 压缩 CSS。