MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

结合 getStaticProps 和 getStaticPaths 打造高性能 Next.js 应用

2023-07-043.7k 阅读

Next.js 中的数据获取基础

在 Next.js 应用开发中,数据获取是构建功能丰富且高性能应用的关键环节。Next.js 提供了几种不同的数据获取方法,以满足不同的应用场景。

1. getStaticProps

getStaticProps 是 Next.js 中用于在构建时获取数据的函数。它在服务器端运行,将获取到的数据作为属性传递给 React 组件。这意味着页面在构建阶段就已经有了所需的数据,能够以最快的速度展示给用户,非常适合用于内容驱动的页面,比如博客文章、产品页面等,这些页面的数据相对稳定,不需要频繁更新。

export async function getStaticProps() {
    const res = await fetch('https://api.example.com/data');
    const data = await res.json();

    return {
        props: {
            data
        },
        revalidate: 60 * 60 * 24 // 一天后重新验证数据
    };
}

function MyPage({ data }) {
    return (
        <div>
            {data.map(item => (
                <p key={item.id}>{item.title}</p>
            ))}
        </div>
    );
}

export default MyPage;

在上述代码中,getStaticProps 函数通过 fetch 从 API 获取数据,然后将数据作为 props 返回给 MyPage 组件。同时,revalidate 选项设置了数据的重新验证时间,这里设置为一天。这意味着在构建后的一天内,页面访问将直接使用构建时的数据,一天后,如果有新的请求,Next.js 将重新获取数据并更新页面。

2. getStaticPaths

getStaticPathsgetStaticProps 紧密配合,主要用于动态路由页面。在 Next.js 中,如果页面的路径是动态生成的(例如 /posts/[id]),getStaticPaths 函数用于生成所有可能的路径,以便 Next.js 在构建时为这些路径预渲染页面。

export async function getStaticPaths() {
    const res = await fetch('https://api.example.com/posts');
    const posts = await res.json();

    const paths = posts.map(post => ({
        params: { id: post.id.toString() }
    }));

    return { paths, fallback: false };
}

export async function getStaticProps({ params }) {
    const res = await fetch(`https://api.example.com/posts/${params.id}`);
    const post = await res.json();

    return {
        props: {
            post
        }
    };
}

function PostPage({ post }) {
    return (
        <div>
            <h1>{post.title}</h1>
            <p>{post.content}</p>
        </div>
    );
}

export default PostPage;

在这个例子中,getStaticPaths 函数从 API 获取所有文章数据,然后为每篇文章生成一个路径对象。fallback 设置为 false 表示只预渲染 paths 数组中指定的路径。getStaticProps 函数则根据 params.id 获取具体文章的数据并传递给 PostPage 组件。

结合 getStaticProps 和 getStaticPaths 提升性能

getStaticPropsgetStaticPaths 结合使用,可以为动态路由页面带来出色的性能表现。

1. 预渲染大量动态页面

假设我们有一个博客应用,有大量的文章需要展示。通过 getStaticPaths 生成所有文章的路径,getStaticProps 在构建时获取每篇文章的数据并预渲染页面。这样,当用户访问具体文章页面时,页面已经在服务器端渲染好,能够快速呈现给用户,大大提高了用户体验。

// pages/post/[id].js
export async function getStaticPaths() {
    const response = await fetch('https://jsonplaceholder.typicode.com/posts');
    const posts = await response.json();

    const paths = posts.map(post => ({
        params: { id: post.id.toString() }
    }));

    return { paths, fallback: false };
}

export async function getStaticProps({ params }) {
    const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${params.id}`);
    const post = await response.json();

    return {
        props: {
            post
        }
    };
}

function Post({ post }) {
    return (
        <div>
            <h1>{post.title}</h1>
            <p>{post.body}</p>
        </div>
    );
}

export default Post;

在上述代码中,getStaticPaths 从 JSONPlaceholder API 获取所有文章并生成路径。getStaticProps 根据文章 ID 获取具体文章内容。这种方式适用于文章数据相对稳定,更新频率较低的场景。

2. 数据缓存与更新策略

通过 getStaticPropsrevalidate 选项,我们可以实现数据的缓存与更新策略。如前文所述,设置 revalidate 为一定时间间隔,在这个时间内,页面使用构建时的数据,超过这个时间,Next.js 会在有新请求时重新获取数据。

export async function getStaticProps() {
    const res = await fetch('https://api.example.com/data');
    const data = await res.json();

    return {
        props: {
            data
        },
        revalidate: 3600 // 每小时重新验证数据
    };
}

这种策略在数据更新频率不高,但又需要一定实时性的场景下非常实用。例如,新闻网站的文章列表页面,文章内容可能几个小时更新一次,通过设置合适的 revalidate 时间,可以在保证性能的同时,让用户看到相对最新的数据。

3. 处理复杂的数据依赖关系

在实际应用中,页面可能依赖多个数据源的数据。getStaticPropsgetStaticPaths 可以处理这种复杂的数据依赖。

// pages/product/[id].js
export async function getStaticPaths() {
    const productRes = await fetch('https://api.example.com/products');
    const products = await productRes.json();

    const paths = products.map(product => ({
        params: { id: product.id.toString() }
    }));

    return { paths, fallback: false };
}

export async function getStaticProps({ params }) {
    const productRes = await fetch(`https://api.example.com/products/${params.id}`);
    const product = await productRes.json();

    const reviewsRes = await fetch(`https://api.example.com/products/${params.id}/reviews`);
    const reviews = await reviewsRes.json();

    return {
        props: {
            product,
            reviews
        }
    };
}

function ProductPage({ product, reviews }) {
    return (
        <div>
            <h1>{product.name}</h1>
            <p>{product.description}</p>
            <h2>Reviews</h2>
            {reviews.map(review => (
                <div key={review.id}>
                    <p>{review.text}</p>
                </div>
            ))}
        </div>
    );
}

export default ProductPage;

在这个产品页面的例子中,getStaticPaths 生成产品页面的路径。getStaticProps 不仅获取产品本身的数据,还获取产品的评论数据,满足了页面复杂的数据依赖需求。

优化策略与注意事项

在使用 getStaticPropsgetStaticPaths 构建高性能 Next.js 应用时,有一些优化策略和注意事项需要关注。

1. 数据获取性能优化

  • 减少请求次数:如果页面依赖多个数据源,可以考虑在后端进行数据聚合,通过一次请求获取所有数据。例如,在 GraphQL 服务器中,可以通过一个查询获取多个相关的数据片段。
  • 缓存控制:利用浏览器缓存和服务器端缓存机制,减少重复的数据获取。对于静态资源(如图片、CSS、JavaScript),合理设置缓存头,延长缓存时间。
  • 使用 CDN:将静态资源部署到 CDN(内容分发网络),利用 CDN 的全球节点网络,加速资源的分发和加载。

2. 处理大数据量

当使用 getStaticPaths 生成大量路径时,需要注意构建时间和内存消耗。如果路径数量过多,可以考虑采用分页策略,分批生成路径。

export async function getStaticPaths() {
    const res = await fetch('https://api.example.com/products');
    const products = await res.json();

    const pageSize = 100;
    const totalPages = Math.ceil(products.length / pageSize);

    const paths = [];
    for (let page = 0; page < totalPages; page++) {
        const start = page * pageSize;
        const end = start + pageSize;
        const pageProducts = products.slice(start, end);

        pageProducts.forEach(product => {
            paths.push({
                params: { id: product.id.toString() }
            });
        });
    }

    return { paths, fallback: false };
}

上述代码通过分页的方式,将大量产品路径分批生成,避免一次性生成过多路径导致的性能问题。

3. 错误处理

在数据获取过程中,可能会出现各种错误,如网络错误、API 响应错误等。需要在 getStaticPropsgetStaticPaths 中妥善处理这些错误。

export async function getStaticProps() {
    try {
        const res = await fetch('https://api.example.com/data');
        if (!res.ok) {
            throw new Error('Failed to fetch data');
        }
        const data = await res.json();

        return {
            props: {
                data
            },
            revalidate: 3600
        };
    } catch (error) {
        return {
            props: {
                error: 'Failed to load data'
            },
            revalidate: 3600
        };
    }
}

在这个例子中,通过 try - catch 块捕获 fetch 可能出现的错误,并将错误信息传递给组件,以便在页面上显示友好的错误提示。

4. 与客户端渲染的平衡

虽然 getStaticPropsgetStaticPaths 适用于大多数静态渲染场景,但对于一些需要实时数据的场景,如实时聊天、实时股票行情等,可能需要结合客户端渲染(如 useEffect 钩子在客户端获取数据)。要根据具体业务需求,平衡好静态渲染和客户端渲染,以达到最佳的性能和用户体验。

实际案例分析

1. 电商产品页面

假设我们正在构建一个电商网站,产品页面是非常重要的部分。每个产品页面需要展示产品的详细信息、图片、价格、库存以及用户评价等内容。

// pages/product/[id].js
export async function getStaticPaths() {
    const res = await fetch('https://api.ecommerce.com/products');
    const products = await res.json();

    const paths = products.map(product => ({
        params: { id: product.id.toString() }
    }));

    return { paths, fallback: false };
}

export async function getStaticProps({ params }) {
    const productRes = await fetch(`https://api.ecommerce.com/products/${params.id}`);
    const product = await productRes.json();

    const reviewsRes = await fetch(`https://api.ecommerce.com/products/${params.id}/reviews`);
    const reviews = await reviewsRes.json();

    const inventoryRes = await fetch(`https://api.ecommerce.com/products/${params.id}/inventory`);
    const inventory = await inventoryRes.json();

    return {
        props: {
            product,
            reviews,
            inventory
        },
        revalidate: 3600
    };
}

function ProductPage({ product, reviews, inventory }) {
    return (
        <div>
            <h1>{product.name}</h1>
            <img src={product.imageUrl} alt={product.name} />
            <p>Price: {product.price}</p>
            <p>Inventory: {inventory.count}</p>
            <h2>Reviews</h2>
            {reviews.map(review => (
                <div key={review.id}>
                    <p>{review.text}</p>
                </div>
            ))}
        </div>
    );
}

export default ProductPage;

在这个电商产品页面的实现中,getStaticPaths 生成所有产品页面的路径。getStaticProps 从不同的 API 端点获取产品详细信息、用户评价和库存数据,并通过 revalidate 设置每小时重新验证数据,以确保库存等数据的相对实时性。

2. 新闻文章列表与详情页面

新闻网站通常有大量的文章,需要快速展示文章列表和文章详情。

// pages/article/[id].js
export async function getStaticPaths() {
    const res = await fetch('https://api.news.com/articles');
    const articles = await res.json();

    const paths = articles.map(article => ({
        params: { id: article.id.toString() }
    }));

    return { paths, fallback: false };
}

export async function getStaticProps({ params }) {
    const res = await fetch(`https://api.news.com/articles/${params.id}`);
    const article = await res.json();

    return {
        props: {
            article
        },
        revalidate: 7200 // 两小时重新验证数据
    };
}

function ArticlePage({ article }) {
    return (
        <div>
            <h1>{article.title}</h1>
            <p>{article.content}</p>
        </div>
    );
}

export default ArticlePage;
// pages/articles.js
import Link from 'next/link';
import { useEffect, useState } from'react';

export async function getStaticProps() {
    const res = await fetch('https://api.news.com/articles');
    const articles = await res.json();

    return {
        props: {
            articles
        },
        revalidate: 3600
    };
}

function ArticlesPage({ articles }) {
    return (
        <div>
            <h1>Latest Articles</h1>
            {articles.map(article => (
                <Link href={`/article/${article.id}`} key={article.id}>
                    <a>{article.title}</a>
                </Link>
            ))}
        </div>
    );
}

export default ArticlesPage;

在新闻应用中,文章列表页面通过 getStaticProps 获取文章列表数据,并设置每小时重新验证。文章详情页面通过 getStaticPathsgetStaticProps 生成路径并获取具体文章内容,设置两小时重新验证数据,以平衡性能和数据实时性。

总结实践要点

结合 getStaticPropsgetStaticPaths 打造高性能 Next.js 应用,关键在于理解它们的工作原理和适用场景。在实践中,要根据具体业务需求合理设置数据获取逻辑、缓存策略和错误处理机制。同时,注意优化数据获取性能,处理大数据量场景,以及平衡静态渲染和客户端渲染的使用。通过精心设计和优化,能够构建出既快速又功能丰富的 Next.js 应用,为用户提供卓越的体验。无论是电商、新闻、博客还是其他类型的应用,这种数据获取和渲染方式都能发挥重要作用,帮助开发者提升应用的性能和竞争力。