【问题标题】:Next.js: Skipping generating static sites at build time (instead: only at runtime, as getStaticProps has no data in build phase)Next.js:在构建时跳过生成静态站点(相反:仅在运行时,因为 getStaticProps 在构建阶段没有数据)
【发布时间】:2021-09-07 12:46:28
【问题描述】:

我想为不同的客户使用相同的应用程序,其中每个客户都有不同的数据库(可能是内部部署的)。 因此,我在构建阶段(例如 CI/CD 中)没有可用于创建静态站点的数据。

我考虑过通过环境变量跳过在getStaticProps 中生成站点。 在构建网站时,我可以告诉 Next.js 不要使用任何数据,如下所示:

export const getStaticProps: GetStaticProps<HomePageProps> = async () => {
  const isBuildPhase = process.env.IS_BUILD;

  const data = isBuildPhase ? null : await fetchData();

  return {
    props: {
      data: data ?? null,
    },
    revalidate: 5 * 60,
  };
};

现在,我只想在运行时创建站点(使用next start),因为在运行时,构建的应用程序可以访问其数据库。在每个getStaticProps 中,将配置环境变量以便获取数据。当应用程序最初启动时,它会在访问时生成所有静态站点。

这种方法有很大的缺点吗?

这个问题有没有更好的解决方案?

【问题讨论】:

  • 作为替代方案,您是否考虑过使用getServerSideProps 代替?
  • @juliomalves 当然,我在应用程序的某些部分使用服务器端渲染,但在我看来,拥有静态网站是更好的用户体验。

标签: javascript reactjs typescript next.js


【解决方案1】:

我们有类似的要求,即应用需要为多个租户页面提供服务,但每个租户的数据都不同。这意味着应用在构建时无法访问数据,只能在运行时访问。

我们利用getStaticPathsgetStaticProps 来做到这一点。

getStaticPaths

此方法返回一个fallback 选项(true || false || blocking),我们可以使用它来决定在 UX 上显示加载器还是阻止直到实际页面加载。

export async function getStaticPaths() {
  // Return empty paths because we don't want to generate anything on build
  // { fallback: blocking } will server-render pages
  // on-demand if the path doesn't exist.
  return {
    paths: [],
    fallback: 'blocking',
  };
}

使用此方法时,无需维护IS_BUILD env 变量。 (根据您的具体用例,您可以选择使用或不使用它)

getStaticProps

此方法根据租户的 URL 路径参数进行实际数据获取。

export async function getStaticProps({ params }) {
  // Run your data fetching code here
  const data = await fetch(params);
  return {
    props: data,
     // Next.js will attempt to re-generate the page:
    // - When a request comes in
    // - At most once every 10 seconds
    revalidate: 10, // In seconds
    notFound: !data,
  };
}

我们还利用Incremental Static Regeneration,确保我们每revalidate 秒刷新一次页面。

页面

单模板租户

当所有租户共享相同的组件时,这非常简单:

const SingleTenantPage = ({ data }) => {

  return <Component {...data} />;
};

export default SingleTenantPage;

多模板租户

实际的页面,只是解析props(从getStaticProps传下来),然后为那个页面加载合适的组件。

通过这种方式,我们为多个租户利用一条路线。 example.com/app/tenantA, example.com/app/tenantB, example.com/app/tenantC 所有 3 条路线都可以在 pages/app/[slug]/index.js 之外提供服务。

使用dynamic 导入,我们确保只加载特定租户的组件。 (也有助于代码拆分)

// Dynamic Import so we load only the required bundles
const templates = {
  tenantA: dynamic(() => import(`../templates/tenantA`)),
  tenantB: dynamic(() => import(`../templates/tenantB`)),
  tenantC: dynamic(() => import(`../templates/tenantC`)),
};

const MultiTenantPage = ({ data }) => {
  // Provided template is present in the data
  // template: 'tenantA' || 'tenantB' || 'tenantC'
  const { template, ...rest } = data || {};

  // If the template doesn't exist, show a 404 Page instead
  const Component = templates[template] || (() => <Error statusCode="404" />);

  return <Component {...rest} />;
};

export default MultiTenantPage;

多租户页面非常适合我们。我们使用更大的revalidate 值,因为数据不会经常更改。由于所有动态导入,我们唯一需要注意的是有点复杂测试用例。

上述解决方案与您考虑的用例非常相似,尽管没有额外的环境变量 (IS_BUILD)

【讨论】:

  • 这个答案太好了,谢谢!一个问题:我认为getStaticPaths 只适用于动态页面(如[someId].tsx)。你如何将它用于非动态的(如index.tsx)?就我而言,这些网站还可以显示客户特定数据。
  • 你是对的。 getStaticPaths 仅适用于动态页面,因为它必须返回路径。这是设计使然。 (IMO 感觉更像是一种限制)。但是有一个解决方法,通过使用动态页面[index].tsx,在getStaticPaths 中返回[],在getStaticProps 中添加一个检查以匹配路由if (params.path !== '/customer') 并验证路径。您仍然可以为您的页面保留一个路径,并为与您的 if 语句不匹配的路径返回 404。
  • 您能详细说明一下吗?如何以这种方式创建索引页面?这是一个包含两个链接的示例,一个指向/test,一个指向/test/1 - 只有/test/1 选项有效:stackblitz.com/edit/nextjs-pwjzop?file=pages%2Findex.js
  • 使用catch all route[[...index]].js,您可以使用单个文件同时访问 /test/test/1
  • 我认为您的建议在技术上可行,但我不会使用它。它只是感觉太老套了,而且可能弊大于利。例如,当您最初想要访问这样一个静态站点时,您不会被重定向(单击其中一个按钮):stackblitz.com/edit/nextjs-yn8leh?file=pages%2Findex.js 我想我将不得不等到 Next.js 决定直接集成此类功能,我创建了一个功能请求:github.com/vercel/next.js/discussions/28959你觉得我还是应该接受你的回答吗?
猜你喜欢
  • 2023-03-22
  • 2020-07-22
  • 2021-07-08
  • 2019-01-05
相关资源
最近更新 更多