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

Next.js 静态生成进阶:getStaticPaths 的核心用法揭秘

2022-12-277.7k 阅读

Next.js 静态生成进阶:getStaticPaths 的核心用法揭秘

理解 Next.js 中的静态生成

在深入探讨 getStaticPaths 之前,我们先来回顾一下 Next.js 的静态生成概念。静态生成是指在构建时生成 HTML 页面,而不是在请求时生成。这一特性使得 Next.js 应用具有出色的性能和 SEO 友好性。

在 Next.js 中,使用 getStaticProps 函数可以实现静态生成。getStaticProps 会在构建时运行,它可以从外部数据源(如 CMS、数据库)获取数据,并将数据传递给页面组件。例如:

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

  return {
    props: {
      data
    },
    revalidate: 60 // 每 60 秒重新验证一次(如果使用增量静态再生)
  };
}

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

export default MyPage;

动态路由与静态生成的挑战

当我们的应用有动态路由时,例如博客文章页面可能具有类似 /posts/[id] 的路由结构,静态生成就面临一个挑战:如何为每个动态路由生成对应的静态页面?这就是 getStaticPaths 发挥作用的地方。

getStaticPaths 用于定义一组路径,Next.js 会为这些路径在构建时生成静态页面。它主要在动态路由页面(例如 pages/posts/[id].js)中使用。

getStaticPaths 的基本用法

  1. 函数签名 getStaticPaths 是一个异步函数,它返回一个对象,该对象包含 pathsfallback 两个属性。
export async function getStaticPaths() {
  return {
    paths: [],
    fallback: false
  };
}
  1. paths 属性 paths 是一个数组,数组中的每个元素是一个对象,对象包含 params 属性,params 是一个对象,它对应动态路由的参数。例如,对于 /posts/[id] 动态路由,params 应该包含 id 属性。
export async function getStaticPaths() {
  const paths = [
    { params: { id: '1' } },
    { params: { id: '2' } }
  ];

  return {
    paths,
    fallback: false
  };
}
  1. fallback 属性 fallback 决定了当用户请求一个在 paths 中未定义的路径时的行为。
  • fallback: false:如果 fallbackfalse,当用户请求一个不存在于 paths 中的路径时,Next.js 会返回 404 页面。
  • fallback: true:如果 fallbacktrue,当用户请求一个不存在于 paths 中的路径时,Next.js 会先显示一个 loading 状态,然后在后台生成该页面的静态版本,并将生成的页面返回给用户。之后,该页面会被缓存,后续请求该路径时会直接返回缓存的页面。
  • fallback: 'blocking':如果 fallbackblocking,当用户请求一个不存在于 paths 中的路径时,Next.js 会等待该页面在后台生成静态版本,然后再将页面返回给用户。这种方式不会显示 loading 状态,用户会直接看到生成好的页面。

从数据源获取路径

在实际应用中,我们通常不会手动定义 paths,而是从数据源(如数据库)中获取。假设我们有一个博客应用,文章数据存储在一个 JSON 文件中。

import path from 'path';
import fs from 'fs';

export async function getStaticPaths() {
  const postsDirectory = path.join(process.cwd(), 'posts');
  const fileNames = fs.readdirSync(postsDirectory);

  const paths = fileNames.map(fileName => {
    const id = fileName.replace(/\.md$/, '');
    return {
      params: { id }
    };
  });

  return {
    paths,
    fallback: false
  };
}

结合 getStaticProps 使用

getStaticPaths 通常与 getStaticProps 一起使用。getStaticProps 可以根据 getStaticPaths 中定义的路径参数获取具体的数据。

import path from 'path';
import fs from 'fs';
import matter from'matter-js';

export async function getStaticPaths() {
  const postsDirectory = path.join(process.cwd(), 'posts');
  const fileNames = fs.readdirSync(postsDirectory);

  const paths = fileNames.map(fileName => {
    const id = fileName.replace(/\.md$/, '');
    return {
      params: { id }
    };
  });

  return {
    paths,
    fallback: false
  };
}

export async function getStaticProps({ params }) {
  const postFilePath = path.join(process.cwd(), 'posts', `${params.id}.md`);
  const fileContents = fs.readFileSync(postFilePath, 'utf8');
  const { data, content } = matter(fileContents);

  return {
    props: {
      title: data.title,
      content
    }
  };
}

const PostPage = ({ title, content }) => {
  return (
    <div>
      <h1>{title}</h1>
      <div dangerouslySetInnerHTML={{ __html: content }} />
    </div>
  );
};

export default PostPage;

增量静态再生与 getStaticPaths

增量静态再生是 Next.js 9.5 引入的特性,它允许在页面构建后重新生成页面。当使用增量静态再生时,getStaticPaths 中的 fallback 属性会有不同的行为。

  1. fallback: true 与增量静态再生 如果 fallback: true 且启用了增量静态再生(通过在 getStaticProps 中设置 revalidate 属性),当请求一个不存在于 paths 中的路径时,Next.js 会先显示 loading 状态,然后在后台生成页面。如果在生成页面过程中,getStaticProps 中的 revalidate 时间到了,Next.js 会再次调用 getStaticProps 来更新页面数据。

  2. fallback: 'blocking' 与增量静态再生fallback: 'blocking' 且启用增量静态再生时,请求不存在于 paths 中的路径会等待页面生成。同样,当 revalidate 时间到了,getStaticProps 会被再次调用以更新数据。

处理复杂的动态路由

  1. 多级动态路由 假设我们有一个电商应用,产品分类结构可能是 /category/[categoryId]/product/[productId]。在这种情况下,getStaticPaths 需要生成所有可能的路径组合。
// 获取所有分类
const categories = await fetch('https://api.example.com/categories').then(res => res.json());
// 获取每个分类下的所有产品
const paths = [];
for (const category of categories) {
  const products = await fetch(`https://api.example.com/categories/${category.id}/products`).then(res => res.json());
  for (const product of products) {
    paths.push({
      params: {
        categoryId: category.id.toString(),
        productId: product.id.toString()
      }
    });
  }
}

return {
  paths,
  fallback: false
};
  1. 动态参数依赖 有时,动态路由参数之间可能存在依赖关系。例如,我们有一个 /language/[language]/country/[country] 的路由,不同语言下可选的国家可能不同。在这种情况下,我们需要根据第一个参数获取第二个参数的可能值。
// 获取所有语言
const languages = await fetch('https://api.example.com/languages').then(res => res.json());
const paths = [];
for (const language of languages) {
  // 获取该语言下的所有国家
  const countries = await fetch(`https://api.example.com/languages/${language.id}/countries`).then(res => res.json());
  for (const country of countries) {
    paths.push({
      params: {
        language: language.id.toString(),
        country: country.id.toString()
      }
    });
  }
}

return {
  paths,
  fallback: false
};

性能优化与注意事项

  1. 路径数量限制 虽然 Next.js 可以处理大量路径,但如果路径数量过多,构建时间可能会显著增加。在这种情况下,可以考虑使用 fallback: truefallback: 'blocking' 来减少构建时生成的页面数量。

  2. 缓存与更新 当使用增量静态再生时,要注意 revalidate 时间的设置。如果设置得过短,可能会导致频繁调用外部数据源,增加服务器负载;如果设置得过长,数据更新可能不及时。

  3. 数据源可靠性 由于 getStaticPathsgetStaticProps 依赖外部数据源获取数据,要确保数据源的可靠性和稳定性。可以考虑设置适当的错误处理机制,例如在获取数据失败时返回默认数据或显示友好的错误页面。

实践案例:构建一个多语言博客

假设我们要构建一个多语言博客,每个博客文章有不同语言的版本,并且 URL 结构为 /[locale]/posts/[id]

  1. 设置项目结构 我们可以在 pages 目录下创建 [locale] 目录,然后在其中创建 posts 目录,并在 posts 目录下创建 [id].js 文件。

  2. getStaticPaths 实现

import { locales } from '../locales'; // 假设 locales 数组包含所有支持的语言代码
import { getAllPostIds } from '../lib/api'; // 从 API 获取所有文章 ID 的函数

export async function getStaticPaths() {
  const postIds = await getAllPostIds();
  const paths = [];
  for (const locale of locales) {
    for (const { id } of postIds) {
      paths.push({
        params: {
          locale,
          id
        }
      });
    }
  }

  return {
    paths,
    fallback: false
  };
}
  1. getStaticProps 实现
import { getPostData } from '../lib/api'; // 根据 ID 和语言代码获取文章数据的函数

export async function getStaticProps({ params }) {
  const postData = await getPostData(params.id, params.locale);

  return {
    props: {
      postData
    }
  };
}

const PostPage = ({ postData }) => {
  return (
    <div>
      <h1>{postData.title}</h1>
      <p>{postData.content}</p>
    </div>
  );
};

export default PostPage;

通过以上步骤,我们就可以构建一个支持多语言的静态博客应用,并且利用 getStaticPathsgetStaticProps 实现静态生成和页面数据获取。

总结 getStaticPaths 的关键要点

  1. 路径定义getStaticPaths 用于定义 Next.js 在构建时生成静态页面的路径,paths 数组中的每个元素对应一个动态路由参数的组合。
  2. fallback 选项fallback 决定了未定义路径的处理方式,false 返回 404,true 显示 loading 并后台生成,blocking 等待后台生成。
  3. 结合数据源:通常从数据库、CMS 等数据源获取路径信息,以动态生成页面路径。
  4. getStaticProps 协同getStaticPaths 定义路径,getStaticProps 根据路径参数获取具体数据,二者紧密配合实现静态生成。
  5. 性能与优化:注意路径数量对构建时间的影响,合理设置 fallbackrevalidate 以优化性能和数据更新。

掌握 getStaticPaths 的核心用法对于构建高效、灵活的 Next.js 静态生成应用至关重要,无论是简单的博客还是复杂的电商应用,它都能帮助我们更好地实现动态路由的静态生成需求。