好久前写了关于 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 页面提供对应的页面数据。

next.js 源码解析 - getStaticProps、getStaticPaths 篇的更多相关文章

  1. underscore.js源码解析(五)—— 完结篇

    最近公司各种上线,所以回家略感疲惫就懒得写了,这次我准备把剩下的所有方法全部分析完,可能篇幅过长...那么废话不多说让我们进入正题. 没看过前几篇的可以猛戳这里: underscore.js源码解析( ...

  2. underscore.js源码解析(四)

    没看过前几篇的可以猛戳这里: underscore.js源码解析(一) underscore.js源码解析(二) underscore.js源码解析(三) underscore.js源码GitHub地 ...

  3. underscore.js源码解析(三)

    最近工作比较忙,做不到每周两篇了,周末赶着写吧,上篇我针对一些方法进行了分析,今天继续. 没看过前两篇的可以猛戳这里: underscore.js源码解析(一) underscore.js源码解析(二 ...

  4. underscore.js源码解析(二)

    前几天我对underscore.js的整体结构做了分析,今天我将针对underscore封装的方法进行具体的分析,代码的一些解释都写在了注释里,那么废话不多说进入今天的正文. 没看过上一篇的可以猛戳这 ...

  5. jQuery2.x源码解析(DOM操作篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) jQuery这个类库最为核心重要的功能就是DOM ...

  6. underscore.js源码解析(一)

    一直想针对一个框架的源码好好的学习一下编程思想和技巧,提高一下自己的水平,但是看过一些框架的源码,都感觉看的莫名其妙,看不太懂,最后找到这个underscore.js由于这个比较简短,一千多行,而且读 ...

  7. Vue.js源码解析-Vue初始化流程

    目录 前言 1. 初始化流程概述图.代码流程图 1.1 初始化流程概述 1.2 初始化代码执行流程图 2. 初始化相关代码分析 2.1 initGlobalAPI(Vue) 初始化Vue的全局静态AP ...

  8. egg.js源码解析之render()

    作为阿里的开源node.js框架,我觉得egg.js是很有前途的,故而学之,github上down了一个项目下来(https://github.com/easy-team/egg-vue-webpac ...

  9. Vue.js 源码分析(十七) 指令篇 v-if、v-else-if和v-else 指令详解

    v-if 指令用于条件性地渲染一块内容.这块内容只会在指令的表达式返回true值的时候被渲染. v-else-if,顾名思义,充当 v-if 的“else-if 块”,可以连续使用: 也可以使用 v- ...

  10. Vue.js 源码分析(三) 基础篇 模板渲染 el、emplate、render属性详解

    Vue有三个属性和模板有关,官网上是这样解释的: el ;提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标 template ;一个字符串模板作为 Vue 实例的标识使用.模板将会 ...

随机推荐

  1. 2022-09-28:以下go语言代码输出什么?A:1 1;B:1 2;C:2 2;D:不确定。 package main import ( “fmt“ ) func main() { var

    2022-09-28:以下go语言代码输出什么?A:1 1:B:1 2:C:2 2:D:不确定. package main import ( "fmt" ) func main() ...

  2. MySQL的varchar存储原理:InnoDB记录存储结构

    摘要:varchar(M) 能存多少个字符,为什么提示最大16383?innodb怎么知道varchar真正有多长?记录为NULL,innodb如何处理?某个列数据占用的字节数非常多怎么办?影响每行实 ...

  3. django之drf(部分讲解)

    序列化类常用字段和字段参数 drf在Django字段类型的基础上派生了自己的字段类型以及字段参数 序列化器的字段类型用于处理原始值和内部数据类型直接的转换 还可以用于验证输入.以及父对象检索和设置值 ...

  4. Burpsuite抓包工具的使用

    一.打开工具 1处箭头为 代理127.0.0.1 端口8080 2处箭头为 证书 将证书ca下载到桌面上 选择第一个 选择下载到桌面即可 可以修改其后缀为der 此即为证书文件 此处使用火狐浏览器为示 ...

  5. # 代码随想录算法训练营Day28 回溯算法|93.复原IP地址 78.子集 90.子集II

    代码随想录算法训练营 93.复原IP地址 题目链接:93.复原IP地址 给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式. 有效的 IP 地址 正好由四个整数(每个整数位于 0 到 ...

  6. python -----类反射

    #反射#描述:反射就是指在程序运行时,动态的去确定对象的类型,并且可以通过字符串的形式去调用对应的属性# ,方法,导入模块,是一种基于字符串的事情驱动# class User:# def __init ...

  7. “AI Earth”人工智能创新挑战赛:助力精准气象和海洋预测Baseline[1]、NetCDF4使用教学、Xarray 使用教学,针对气象领域.nc文件读取处理

    1."AI Earth"人工智能创新挑战赛:助力精准气象和海洋预测Baseline[1].NetCDF4使用教学.Xarray 使用教学,针对气象领域.nc文件读取处理 比赛官网: ...

  8. zabbix监控服务器php

    1 修改php配置文件 /apollo/env/php/thirdparty.php-7.3/etc/php-fpm.conf,添加pm.status pm.status_path = /phpfpm ...

  9. 精彩回顾 | 金蝶云苍穹技术开放日xUser Club广州站

    6月14日,以"项目实践案例:性能优化与实践"为主题的金蝶云·苍穹技术开放日广州站圆满落幕.此次活动吸引了50多位开发者到场,大家不仅聆听了开发者关于"代码检查.性能优化 ...

  10. 安装部署 Kubernetes 仪表板(Dashboard)

    简介 Kubernetes 仪表板(Dashboard)是基于网页的 Kubernetes 用户界面. 你可以使用仪表板: 展示了 Kubernetes 集群中的资源状态信息和所有报错信息. 把容器应 ...