共计 6607 个字符,预计需要花费 17 分钟才能阅读完成。
😂 良久前写了对于
getStaticProps
和getStaticPaths
的内容,然而半年过来了源码解析就始终遗记了,不久前有人揭示才想起来,补下坑。
本文次要是解读下 getStaticProps
、getStaticPaths
相干的源码,不理解这两个 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
是在动静页面的路由参数previewData
和preview: preview
模式的相干数据locales, locale
和defaultLocale
多语言相干参数
执行实现后 getStaticProps
的返回值会被放入 pageProps
中。
再看看 invalidKeys
相干局部,除了 revalidate
、props
、redirect
和 notFound
外别的属性都会被视为非法。
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));
}
而后还有对于 notFound
和 redirect
的解决:
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
相干的参数来进行标识。
当然这里省略很多的校验,比方 getStaticProps
和 getServerSideProps
抵触、getStaticPaths
的查看、notFound
和 redirect
不能同时存在等。
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.js
的 devServer
中。在 next.js
遇到动静路由时,会依照 buildStaticPaths
和 getStaticProps
来决定是否启用 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
的返回值,并对其返回值进行查看:
getStaticPaths
能够为async
办法getStaticPaths
承受两个参数:locales
和defaultLocale
- 返回值必须为
{paths: Array, fallback: boolean | 'blocking'}
构造
而在拿到 toPrerender
之后,next.js
会将其转换为 prerenderPaths
和 encodedPrerenderPaths
,这两个 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
。
总结
getStaticProps
、getStaticPaths
相干的源码其实大部分都是在解决对于数据查看、解决这类的事件,因为这两个 API
的指摘也都很简略:getStaticPaths
负责为动静路由的 SSG
场景提供页面列表,getStaticProps
则为 SSG
页面提供对应的页面数据。