next.js 源码解析 - getStaticProps、getStaticPaths 篇

这篇具有很好参考价值的文章主要介绍了next.js 源码解析 - getStaticProps、getStaticPaths 篇。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

😂 好久前写了关于 getStaticPropsgetStaticPaths 的内容,然而半年过去了源码解析就一直忘记了,不久前有人提醒才想起来,补下坑。

本文主要是解读下 getStaticPropsgetStaticPaths 相关的源码,不了解这两个 API 的建议先看下之前的文章再看。👀

getStaticProps

首先 getStaticProps 是应用于 SSG 场景,我们先看下 packages/next/server/render.tsx 中相关的代码:

const isSSG = !!getStaticProps;
const pageIsDynamic = isDynamicRoute(pathname);

if (isSSG && !isFallback) {
    let data: UnwrapPromise<ReturnType<GetStaticProps>>;

    try {
        data = await getStaticProps!({
            ...(pageIsDynamic ? { params: query as ParsedUrlQuery } : undefined),
            ...(isPreview ? { preview: true, previewData: previewData } : undefined),
            locales: renderOpts.locales,
            locale: renderOpts.locale,
            defaultLocale: renderOpts.defaultLocale
        });
    } catch (staticPropsError: any) {
        // ....
    }
    // ...
}

isFallback 可以先不管。可以看到 getStaticProps 同样可以为异步函数,而是否为 SSG 就是由是否存在 getStaticProps 函数来决定的,SSG 场景下的 pageIsDynamic 则必须配合 getStaticPaths 使用,可以看到 getStaticProps 会接收几个参数:

  • params 是在动态页面的路由参数
  • previewDatapreview: preview 模式的相关数据
  • locales, localedefaultLocale 多语言相关参数

执行完成后 getStaticProps 的返回值会被放入 pageProps 中。

再看看 invalidKeys 相关部分,除了 revalidatepropsredirectnotFound 外别的属性都会被视为非法。

const invalidKeys = Object.keys(data).filter(
    key => key !== 'revalidate' && key !== 'props' && key !== 'redirect' && key !== 'notFound'
);

if (invalidKeys.includes('unstable_revalidate')) {
    throw new Error(UNSTABLE_REVALIDATE_RENAME_ERROR);
}

if (invalidKeys.length) {
    throw new Error(invalidKeysMsg('getStaticProps', invalidKeys));
}

然后还有关于 notFoundredirect 的处理:

if ('notFound' in data && data.notFound) {
    if (pathname === '/404') {
        throw new Error(`The /404 page can not return notFound in "getStaticProps", please remove it to continue!`);
    }

    (renderOpts as any).isNotFound = true;
}

if ('redirect' in data && data.redirect && typeof data.redirect === 'object') {
    checkRedirectValues(data.redirect as Redirect, req, 'getStaticProps');

    if (isBuildTimeSSG) {
        throw new Error(
            `\`redirect\` can not be returned from getStaticProps during prerendering (${req.url})\n` +
                `See more info here: https://nextjs.org/docs/messages/gsp-redirect-during-prerender`
        );
    }

    (data as any).props = {
        __N_REDIRECT: data.redirect.destination,
        __N_REDIRECT_STATUS: getRedirectStatus(data.redirect)
    };
    if (typeof data.redirect.basePath !== 'undefined') {
        (data as any).props.__N_REDIRECT_BASE_PATH = data.redirect.basePath;
    }
    (renderOpts as any).isRedirect = true;
}

notFound 会使用 renderOpts.isNotFound 来标识,而 redirect 则会在 props 中通过 __N_REDIRECT 相关的参数来进行标识。

当然这里省略很多的校验,比如 getStaticPropsgetServerSideProps 冲突、getStaticPaths 的检查、notFoundredirect 不能同时存在等。

props.pageProps = Object.assign({}, props.pageProps, 'props' in data ? data.props : undefined);

然后其中还包含了一部分与 revalidate 相关的内容,主要是一些检测和值的处理,主要与 ISR 相关的此处先跳过。

getStaticPaths

getStaticPaths 的相关的调用源码主要在 packages/next/build/utils.ts 文件中的 buildStaticPaths 中,buildStaticPaths 会在两个时候被调用,一个是 next.js 构建的时候,第二个是 next.jsdevServer 中。在 next.js 遇到动态路由时,会按照 buildStaticPathsgetStaticProps 来决定是否启用 SSG 模式,启用则会调用 buildStaticPaths 获取该动态路由所对应的需要构建的所有静态页面。

if (getStaticPaths) {
    staticPathsResult = await getStaticPaths({ locales, defaultLocale });
}

if (!staticPathsResult || typeof staticPathsResult !== 'object' || Array.isArray(staticPathsResult)) {
    throw new Error(
        `Invalid value returned from getStaticPaths in ${page}. Received ${typeof staticPathsResult} ${expectedReturnVal}`
    );
}

const invalidStaticPathKeys = Object.keys(staticPathsResult).filter(key => !(key === 'paths' || key === 'fallback'));

if (invalidStaticPathKeys.length > 0) {
    throw new Error(
        `Extra keys returned from getStaticPaths in ${page} (${invalidStaticPathKeys.join(', ')}) ${expectedReturnVal}`
    );
}

if (!(typeof staticPathsResult.fallback === 'boolean' || staticPathsResult.fallback === 'blocking')) {
    throw new Error(`The \`fallback\` key must be returned from getStaticPaths in ${page}.\n` + expectedReturnVal);
}

const toPrerender = staticPathsResult.paths;

if (!Array.isArray(toPrerender)) {
    throw new Error(
        `Invalid \`paths\` value returned from getStaticPaths in ${page}.\n` +
            `\`paths\` must be an array of strings or objects of shape { params: [key: string]: string }`
    );
}

buildStaticPaths 第一部分是获取 getStaticPaths 的返回值,并对其返回值进行检查:

  1. getStaticPaths 可以为 async 方法
  2. getStaticPaths 接受两个参数:localesdefaultLocale
  3. 返回值必须为 {paths: Array, fallback: boolean | 'blocking'} 结构

而在拿到 toPrerender 之后,next.js 会将其转换为 prerenderPathsencodedPrerenderPaths,这两个 set 的数据集基本一致,只是一个 path 为已经被解码,一个没有,猜测是为了性能考虑空间换时间。

toPrerender.forEach(entry => {
    if (typeof entry === 'string') {
        entry = removeTrailingSlash(entry);

        const localePathResult = normalizeLocalePath(entry, locales);
        let cleanedEntry = entry;

        if (localePathResult.detectedLocale) {
            cleanedEntry = entry.slice(localePathResult.detectedLocale.length + 1);
        } else if (defaultLocale) {
            entry = `/${defaultLocale}${entry}`;
        }

        const result = _routeMatcher(cleanedEntry);
        if (!result) {
            throw new Error(`The provided path \`${cleanedEntry}\` does not match the page: \`${page}\`.`);
        }

        // If leveraging the string paths variant the entry should already be
        // encoded so we decode the segments ensuring we only escape path
        // delimiters
        prerenderPaths.add(
            entry
                .split('/')
                .map(segment => escapePathDelimiters(decodeURIComponent(segment), true))
                .join('/')
        );
        encodedPrerenderPaths.add(entry);
    } else {
        // ...
    }
});

针对 string 类型的 entry,简单的处理下语言、路径即可。

const _validParamKeys = Object.keys(_routeMatcher(page));
if (typeof entry === 'string') {
    // ...
} else {
    const invalidKeys = Object.keys(entry).filter(key => key !== 'params' && key !== 'locale');

    if (invalidKeys.length) {
        throw new Error('...');
    }

    const { params = {} } = entry;
    let builtPage = page;
    let encodedBuiltPage = page;

    _validParamKeys.forEach(validParamKey => {
        const { repeat, optional } = _routeRegex.groups[validParamKey];
        let paramValue = params[validParamKey];
        if (
            optional &&
            params.hasOwnProperty(validParamKey) &&
            (paramValue === null || paramValue === undefined || (paramValue as any) === false)
        ) {
            paramValue = [];
        }
        if ((repeat && !Array.isArray(paramValue)) || (!repeat && typeof paramValue !== 'string')) {
            throw new Error('...');
        }
        let replaced = `[${repeat ? '...' : ''}${validParamKey}]`;
        if (optional) {
            replaced = `[${replaced}]`;
        }
        builtPage = builtPage
            .replace(
                replaced,
                repeat
                    ? (paramValue as string[]).map(segment => escapePathDelimiters(segment, true)).join('/')
                    : escapePathDelimiters(paramValue as string, true)
            )
            .replace(/(?!^)\/$/, '');

        encodedBuiltPage = encodedBuiltPage
            .replace(
                replaced,
                repeat
                    ? (paramValue as string[]).map(encodeURIComponent).join('/')
                    : encodeURIComponent(paramValue as string)
            )
            .replace(/(?!^)\/$/, '');
    });

    if (entry.locale && !locales?.includes(entry.locale)) {
        throw new Error('...');
    }
    const curLocale = entry.locale || defaultLocale || '';

    prerenderPaths.add(`${curLocale ? `/${curLocale}` : ''}${curLocale && builtPage === '/' ? '' : builtPage}`);
    encodedPrerenderPaths.add(
        `${curLocale ? `/${curLocale}` : ''}${curLocale && encodedBuiltPage === '/' ? '' : encodedBuiltPage}`
    );
}

而对于 Object 类型的 entry,则会先检查确保是 {params, locale} 结构,然后使用 params 对动态路由进行替换拼接。 _validParamKeys 是该动态路由页面中的参数的 key 数组。然后一样是路径和语言的处理。最终的返回值如下:

return {
    paths: [...prerenderPaths],
    fallback: staticPathsResult.fallback,
    encodedPaths: [...encodedPrerenderPaths]
};

当需要时 next.js 就会使用这里的 paths 来生成对应的静态页面,从而实现动态路由的 SSG

总结

getStaticPropsgetStaticPaths 相关的源码其实大部分都是在处理关于数据检查、处理这类的事情,因为这两个 API 的指责也都很简单:getStaticPaths 负责为动态路由的 SSG 场景提供页面列表,getStaticProps 则为 SSG 页面提供对应的页面数据。文章来源地址https://www.toymoban.com/news/detail-649555.html

到了这里,关于next.js 源码解析 - getStaticProps、getStaticPaths 篇的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 使用 Flask 部署 Next.js

    原文 使用 Flask 部署 Next.js Flask 和 Next.js 是两个独特的开源 Web 框架,分别构建在 Python 和 JavaScript 编程语言之上。 您可以在没有 Next.js 的情况下构建 Flask 应用程序,也可以在没有 Flask 的情况下构建 Next.js 应用程序。但是,您可能会发现自己使用 Flask 构建了一个应用程序,

    2024年02月12日
    浏览(48)
  • Next.js 学习笔记(六)——缓存

    Next.js 可通过缓存渲染工作和数据请求来提高应用程序的性能并降低成本。本页将深入介绍 Next.js 缓存机制、可用于配置这些机制的 API 以及它们之间的交互方式。 需要知道 :本页将帮助你了解 Next.js 的工作原理,但这并 不是 使用 Next.js 提高工作效率的必要知识。Next.js 的大

    2024年02月01日
    浏览(48)
  • 为什么选择 Next.js 框架?

    Next.js 框架作为一种强大而受欢迎的工具,为开发人员提供了许多优势和便利。本文将探讨 Next.js 框架的优点,并解释为什么选择 Next.js 是一个明智的决策。 文档:https://nextjs.org/docs Next.js 框架提供了先进的服务端渲染(SSR)和静态生成(SSG)能力,使得我们能够在服务器上生

    2024年02月12日
    浏览(51)
  • 新版 Next.js 从入门到入土

    本教程用的Next.js 是 13 版本 完善的React项目,搭建轻松 自带数据同步,解决服务端渲染最大难点 丰富的插件 灵活配置 手动创建 初始化 安装所需要的依赖包 增加快捷命令 创建测试文件 在根目录下创建pages文件夹,并在该文件下创建 index.js pages 文件夹是Next 规定的,在这个

    2024年02月10日
    浏览(50)
  • 微信小程序实现PDF预览功能——pdf.js(含源码解析)

    前言 前一段时间遇到了一个需求,关于 pdf 文件的预览,客户要求如下: 只能在微信小程序内预览,不能调起本地浏览器预览; 需要让用户强制阅读 10s 后才算阅读完成,进而进行下一步操作; 用户不能下载预览的 pdf 文件; 因为一些原因(此处省略一万字🐎),这个项目

    2023年04月09日
    浏览(41)
  • 使用 Next.js 连接 mysql 数据库

    本文主要为大家介绍,如何使用 Next 框架实现一个简单的 后端接口 ,并且从 数据库 中请求数据返回给前端。 项目创建完成后在 app 文件下新建api文件夹,在 api 文件夹下新建 getData 文件夹,在 getData 文件夹下新建 route.js,这里面用于存储我们的接口信息,如下 注意: 在

    2024年02月22日
    浏览(64)
  • Remix 和 Next.js 中实现依赖注入

    在 Remix 中实现依赖注入需要使用到 context 。下面是一个简单的示例: 首先,在项目根目录下创建 context.js 文件:

    2024年02月10日
    浏览(37)
  • Next.js 13.5 正式发布,速度大幅提升!

    9 月 19 日,Next.js 13.5 正式发布,该版本通过以下方式提高了本地开发性能和可靠性: 本地服务器启动速度提高 22% :使用App和Pages Router可以更快地进行迭代 HMR(快速刷新)速度提高 29% :在保存更改时进行更快的迭代 内存使用量减少 40% :在运行 next start 时测量 优化的包导

    2024年02月08日
    浏览(48)
  • Next.js使用Supabase实现Github登录

    Next.js 有许多 OAuth 认证方案来实现 Github 或者 Google 登录,比较常见的有 next-auth、clerk、supabase等。Supabase提供了很多的核心服务,包括 PostgreSQL 数据库、身份验证、文件存储等。 本文将介绍如何使用 Supabase 实现 Github 登录,您将学到: 使用 OAuth 认证登录。 使用 Github 注册自

    2024年02月05日
    浏览(52)
  • React+Node——next.js 构建前后端项目

    一、安装全局依赖 二、创建next项目 三、加载mysql依赖 四、运行项目 五、创建db文件目录,目录下创建index.ts 六、创建pages文件目录,目录下创建api文件目录,api目录下创建user.ts 请求地址 http://localhost:3000/api/user 七、在pages目录下创建user.tsx 页面访问地址 http://localhost:3000/user

    2024年02月07日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包