你将了解
getStaticPaths()
,这是Next.js的核心原则之一
你将提高你对Next.js的一般知识和信心
你将获得一个可快速复制的例子,用于你自己学习Next.js的目的
📝
先决条件
你应该熟悉什么是Next.js,以及为什么你应该考虑使用它。
你应该对以下内容有一定的了解
路由
和
动态路由
是什么意思,以及Next.js。
在这个例子中,我使用了TypeScript。但你不一定要熟悉TypeScript。我将讨论使用JavaScript时将会省略的代码。另外,只要你看到关于任何文件的
.tsx
,如果你使用的是JavaScript,你可以直接用
.js
。
🎯
目标
本快速指南旨在帮助你管理数据的获取,这些数据可用于Next.js的动态路由中的预渲染目的。我们将讨论一些理论以及一个实际的例子。
当我们专注于所需代码的实际逻辑时,我不会做任何CSS样式设计。当你在使用我们在本教程中讨论的技术时,请自由地在你自己的项目的前端发挥创意。
虽然React本身使用基于代码的方法来实现任何路由意图,但Next.js利用文件系统来实现路由的概念。
因此,你可能对React中基于代码的路由很熟悉,它可能看起来与此类似:
基于代码的React路由的例子
/about
通过这种基于代码的方法,例如,你能够通过
/
,从主路由导航到
about
页面。
在这个React的例子中,你也能够找到一个动态的路由方法,即
:productId
路径。
不过,在Next.js中,我们不再使用这种基于代码的路由了。相反,这个React框架利用了基于文件的路由。这意味着你直接通过页面文件来设置你的路由。
考虑以下
pages
文件夹,其中包含子文件夹和文件:
基于文件的Next.js路由的例子
index.tsx
文件相当于上面React Routing例子中的
/
路径。因此,你将能够通过
/user-profile
,到达
user-profile.tsx
文件中的内容--就是这样!
另一方面,如果你想接触到一些嵌套的内容,你可以使用
/stars/[id]
,以便在相应的页面文件中找到这些内容。
也许你注意到,我对
[id].tsx
和
[something].tsx
都使用了方括号。这是为了在Next.js中设置动态路由而需要的。
从技术上讲,你可以为
[id]
,插入任何你想要的输入,页面将为这个特定的路径加载。
只要记住,如果这个动态路由需要一个有效的输入
[id]
(也许是某种现有的产品ID,我们想获取相应的数据),那么就可能出现错误。
✂️使用动态路由在Next.js中获取数据
想象一下,你将这种动态路由方法应用于一个列出了一堆不同商品的商店页面。每个项目都有一个链接,以获取关于该特定项目的更多信息。
在这个链接元素中,你可以用一个有效的参数(比如说相应的产品ID)引导用户进入一个动态路由。对于这种情况,动态路由是最好的方法。
❗
getStaticProps()
如何工作?
有了这个功能,你可以在构建时预先渲染一个页面。这对于搜索引擎优化(SEO)的目的是有用的,例如,总体上可以产生一个更好的用户体验。
应该被预渲染的数据通常可以在一些数据库中找到,比如说。就像
getStaticProps()
,你可以直接在这个函数中编写任何服务器端的代码,以达到获取数据的目的(而不是在后端接触到一个API路由,然后通过任何需要的服务器端操作)。
关于
getStaticProps()
,还有更多要说的。如果你对这些东西很陌生,我强烈建议你看看
Next.js官方
关于这个主题的
文档
。
❓
getStaticPaths()
的目的是什么?
虽然
getStaticProps()
本身似乎已经为我们的页面做了所有需要做的工作,但当我们在动态路由页面上单独使用这个函数时,我们会遇到一个错误。错误信息实际上会叫你注意到这个具体的事实,即
getStaticPaths()
是缺失的。
服务器错误的屏幕截图。SSG是静态网站生成(Static-Site Generation)的缩写
getStaticProps()
利用了静态网站生成的概念。所以Next.js会在构建时预先渲染相应的页面。但在动态路由的情况下,Next.js本身并不知道要预渲染哪些路径。相反,你必须介入并提供帮助--而这正是 的用武之地。
getStaticPaths()
因此,通过
getStaticPaths
,你可以指定动态路由的哪些路径应该被预渲染,以及/或如何处理未知的路径。
📋 快速的附带说明
如果你正在使用
getServerSideProps()
,它可以用于类似于
getStaticProps()
的原因,你会注意到实际上不需要
getStaticPaths()
。这是为什么呢?
getServerSideProps()
因为Next.com并不使用静态生成原则。Next.js不构建页面,而是在每次请求时用返回的数据预先渲染页面。这就是所谓的服务器端渲染。
在使用
getServerSideProps()
,我们不需要告诉Next.js哪些路径必须静态预渲染,因为这个函数本来就没有这个东西。
如果你想阅读更多关于这个函数的信息,我可以再次推荐Next.js的
服务器端渲染
的官方文档。不过,这不在本快速指南的范围内,我在下面的步骤中都不需要
getServerSideProps()
。
🔧 如何设置我们的项目
在这个例子中,我们将重现一个小的动态路由案例。为此,我在
pages
文件夹中准备了一个子文件夹
test
。
pages
文件夹是由Next.js自动创建的。
在
test
文件夹中,我插入了
[something].tsx
文件(如果你使用的是JavaScript而不是TypeScript,那么
[something].js
)。
在我们Next.js应用程序的根层也有一个
backendData
文件夹,其中有
some-backend-data.json
文件(因此不在
pages
文件夹中)。这个文件将为我们提供数据,我们将动态地插入。
🔨 后台
JSON
数据的设置
在这个例子中,我将创建一些虚拟数据,这些数据将被嵌入到
backendData
文件夹中的
some-backend-data.json
。这样,我们就可以重现一种情况,即你可以在后端访问某种你想在前端使用的数据。
下面是
some-backend-data.json
文件的样子:
"stars"
:
[
"id"
:
"St2-18"
,
"name"
:
"Stephenson 2-18"
,
"description"
:
"Stephenson 2-18 is a red supergiant (RSG) or possible extreme red hypergiant (RHG) star in the constellation of Scutum."
,
"link"
:
"https://en.wikipedia.org/wiki/Stephenson_2-18"
"id"
:
"UY-SC"
,
"name"
:
"UY Scuti"
,
"description"
:
"UY Scuti is an extreme red hypergiant or red supergiant star in the constellation Scutum."
,
"link"
:
"https://en.wikipedia.org/wiki/UY_Scuti"
"id"
:
"RSGC1"
,
"name"
:
"RSGC1-F01"
,
"description"
:
"RSGC1-F01 is a red supergiant located in the RSGC1 open cluster in the constellation of Scutum."
,
"link"
:
"https://en.wikipedia.org/wiki/RSGC1-F01"
/backendData/some-backend-data.json
中的数据
在这个文件中,你会发现一些
JSON
格式的数据。有
"stars"
,这只是一个有三个对象的数组。这三个对象都有相同的格式,包括一个
id
,一个
name
,一个
description
,和一个
link
,指向一个外部网页。
正如你现在可能已经发现的那样,这些实际上是我们宇宙中的一些真实的星星。
在真实世界的情况下,你可能会有某种与数据库的连接,但从技术上讲,你从这个数据库接收的实际数据可以像这个例子中的格式。所以这对我们的例子设置来说已经足够了。
🔑 进口和接口
作为下一步,我们可以深入到实际创建Next.js
[something].tsx
动态路由。让我们从这个例子所需的进口开始:
import { GetStaticProps, GetStaticPaths } from 'next';
import { useRouter } from 'next/router';
import path from 'path';
import fs from 'fs/promises';
interface starInterface {
id: string
name: string
description: string
link: string
/pages/test/[something].tsx中的进口和TypeScript接口
请记住,我在这里使用的是TypeScript。如果你使用的是JavaScript,当然也是可以的。只要记住,你不需要interface starInterface
或import { GetStaticProps, GetStaticPaths } from 'next'
。
💎 如何创建获取数据的函数
下一步,我将准备一个名为getData()
的async
函数,它将对getStaticProps()
和getStaticPaths()
函数有所帮助。这看起来会很混乱,特别是如果你从来没有接触过后端JavaScript代码,比如说你在任何Node.js应用程序中都会有这样的代码。
请再忍耐几秒钟。你不需要详细了解下面的代码。我们只需要知道getData()
函数的结果是什么:
async function getData() {
const filePath = path.join(process.cwd(), 'backendData', 'some-backend-data.json')
const fileData = await fs.readFile(filePath)
const data = JSON.parse(jsonData.toString())
return data
在**/pages/test/[something].tsx**中的getData()函数
你可以看到,有三个变量。filePath
,fileData
, 和data
。通过filePath
,我们只是关注我们的JSON
数据所放置的文件。所以我们的目标是当前工作目录(cwd),然后是backendData
文件夹,然后是JSON
文件。
通过fileData
,我们正试图读取这个文件,并提取存储在其中的实际JSON
数据。
我们需要data
,以转换这个fileData
,这样我们就可以在接下来的步骤中实际使用它。
总而言之,getData()
基本上只是为我们提供了来自some-backend-data.json
文件的数据,所以我们可以在getStaticProps()
以及getStaticPaths()
中利用它。它没有更多的内容。
🔨设置为getStaticProps()
在我们实现了getData()
(当我们试图获取我们的假后台数据时,它将派上用场),接下来我们将创建getStaticProps()
函数。
在这里,我们将使用getStaticProps()
,为我们的动态路由中的特定获取的数据启用预渲染。
在我们直接进入下面的代码示例之前,先快速思考一下我们实际上想要完成什么。
用户应该被引导到这个特定的动态路由,它由URL中的唯一标识符表示。我的意思是,我们希望/test/St2-18
和test/UY-SC
通向同一个动态页面。
然而,用户在那里看到的数据应该是不同的,因为我们想分别获取St2-18
和UY-SC
的数据。
我们有一个getData()
函数,它可以帮助我们接触到后台数据。但我们仍然要知道我们想从我们的假后台提取哪些确切的数据。
对于这一步,我们可以从URL中提取特定的标识符,例如St2-18
,并将其与我们提取的getData()
结果数据相结合。
从那里我们可以搜索包含我们想在后端getData()
结果中显示的数据的特定对象。
现在,让我们回到我们的代码例子,看看这个过程的运作。
请看下面的代码部分,我们实现了getStaticProps()
:
export const getStaticProps: GetStaticProps = async (context) => {
const itemID = context.params?.something;
const data = await getData();
const foundItem = data.stars.find((item: starInterface) => itemID === item.id);
if (!foundItem) {
return {
props: { hasError: true },
return {
props: {
specificStarData: foundItem
getStaticProps()函数在**/pages/test/[something].tsx**中。
对于JavaScript,你可以直接省略GetStaticProps
,作为getStaticProps()
的类型。
getStaticProps()
可以为我们提供一个 参数,通过它我们可以到达一些有用的方法。现在,我们只需要明白,通过 ,我们能够访问 ,然后在之后接触到我们特定路径的当前标识符,而 是放置者。context
context
params
something
请记住,这个文件实际上被称为[something].tsx
,这就是为什么我们在这种情况下访问something
。
通过这种方法,我们成功地从我们的URL中提取了我们需要的信息,以便在我们的后台数据阵列中搜索特定的对象。然后我们将这些信息保存在itemID
变量中。
假设用户将手伸向了/test/St2-18
,那么itemID
就会持有St2-18
的值。
由于我们有方便的getData()
函数,我们可以通过这个函数获得我们的后端数据并将其保存到data
。
由于我们现在有itemID
以及data
,我们可以将这两个变量结合起来,创建foundItem
。这就把包括itemID
的对象作为一个id
。
通过if
语句,我们正在检查foundItem
是否实际存在。或者换句话说,我们检查我们的后端数据是否包含有我们通过itemID
提取的相应的id
的数据。
如果找不到数据,我们就返回这个布尔值hasError
,值为true
。这有助于我们在前台管理这种情况。
如果有数据,那么我们就把我们的foundItem
返回给前台。请记住,你在这个props
对象中返回的所有内容实际上都会暴露给前端。所以不要返回任何凭证(例如个人API密钥)。
🔨设置为getStaticPaths()
在我们前往动态页面的前端部分之前,我们仍然需要实现getStaticPaths()
函数:
export const getStaticPaths: GetStaticPaths = async () => {
const data = await getData();
const pathsWithParams = data.stars.map((star: starInterface) => ({ params: { something: star.id }}))
return {
paths: pathsWithParams,
fallback: true
getStaticPaths() 函数在**/pages/test/[something].tsx**中。
对于JavaScript,你可以直接省略GetStaticPaths
作为getStaticPaths()
的类型。你也可以为JavaScript删除starInterface
。
在getStaticPaths()
函数中,我们要告诉Next.js哪些路径应该被预渲染。
对于这一步,我们要用getData()
来访问我们的后台数据,正如你在getStaticProps()
中看到的那样。
getStaticPaths()
要求在返回中为 的具体形式。你实际上有两个选择。paths
第一个是我在这个例子中使用的方法:paths: [{ params: { something: star.id } }]
。它应该是一个数组,每个你想让Next.js预渲染的路径都有一个对象。
第二个选项是使用路径字符串,比如:paths: ['/test/St2-18', '...', '...']
。
这两种技术实现了相同的行为,所以只要选择你喜欢的那一种。
什么是fallback
属性?
重要的是要理解,你不需要包括每个应该被预渲染的路径。当你有很多情况需要考虑,而不希望所有东西都被预渲染时,这一点特别有帮助。
为了处理这种情况,fallback
属性特别有用
你可以在Next.js官方文档中详细阅读关于fallback
。
用我自己的话来说,我会这样解释:
fallback
设置为 ,每当用户试图通过paths属性访问一个没有被 识别的路径时,就会自动导致404错误页面。false
getStaticPaths()
fallback
设置为 ,当用户试图访问一个不存在于 中的路径时,不会自动导致404错误页面。true
getStaticPaths()
这样,我们仍然可以联系到前端,并且能够通过显示某种加载顺序来处理那里的情况,例如。
如果没有任何数据可以被获取,你也可以在前端显示一个错误,当没有特定路径参数的有效项目数据时
fallback
设置为 ,每当用户试图访问一个不存在于 的路径时,不会自动导致404错误页面。'blocking'
getStaticPaths()
这与 设置为 类似,但现在我们基本上省略了任何手动加载过程。相反,浏览器只是多花一点时间来获取数据,然后显示准备好的页面。fallback
true
当你不想向用户呈现 "正在加载...... "时,这很有帮助,例如,只是让他们在页面加载成功之前多等一会儿。如果找不到数据,那么我们仍然有机会在前台创建一个手动错误。
由于我们有一个如此小的数据集,我们只是给每一个可能的路径getStaticPaths()
。所以我们在技术上不需要太关注fallback
这个属性。
尽管如此,我还是将fallback
设置为true
,以显示你如何处理这种手动错误以及可能发生的加载序列。
📐 如何配置前台
在最后一步,我们将配置我们的实际页面。这里的任何内容都将是用户将看到的前台内容:
function projectPage(props: { specificStarData: starInterface,
hasError: boolean }) {
const router = useRouter();
if (props.hasError) {
return <h1>Error - please try another parameter</h1>
if (router.isFallback) {
return <h1>Loading...</h1>
return (
<h1>{props.specificStarData.name}</h1>
<p>{props.specificStarData.description}</p>
<a href={props.specificStarData.link}>More Information here (link)</a>
</div>
export default projectPage;
在**/pages/test/[something].tsx中的projectPage()函数
对于JavaScript来说,你不需要在函数的参数中提到starInterface
以及boolean
。
在上面的代码中,你可以找到我们的specificStarData
以及hasError
,它们都持有一些值。除此之外,我们利用useRouter()
钩子,以便访问isFallback
,这有助于我们处理任何fallback
的情况。
请记住,如果你不能提供每个动态路由的预加载,来自getStaticPaths()
的fallback
可以被设置为true
或'blocking'
。在这些情况下,这将防止你的页面崩溃。
相反,当用户访问这个特定的动态路由,然后提供相应的信息时,它将在空中加载一段时间。
对于这种潜在的加载序列,我们使用router.isFallback
,以便为用户返回带有某种加载指示的JSX。
如果因为用户试图访问一个找不到数据的动态路径而实际出现了错误,hasError
,表明实际出现了错误。
假设用户实际上到达了一个可以获取数据的动态路径,实际的预期JJSX输出将被返回。
在所有的配置步骤之后(有fallback: true
),我们收到/test/St2-18
路径的这个输出。
/test/St2-18的结果
如果我们试图输入一个无效的参数,它首先会尝试加载,然后返回我们的手动设置的错误信息。
结果为**/test/this-will-produce-an-error**
🎲 如何测试fallback
属性
基本上就是这样了!结果是一个动态路由,它使用getStaticProps()
以及getStaticPaths()
,以便对从我们的虚拟后端获取的数据进行预渲染。
当你第一次使用getStaticPaths()
,我建议尝试不同的fallback
值(true
,false
,'blocking'
),以了解你的应用程序的行为究竟如何变化。
由于我们在例子中使用的是fallback: true
,我们也能够在三个路径中只插入一个可能的路径,而不会让我们的应用程序崩溃。
因此,假设我们将getStaticPaths()
内的paths
属性改为paths: ['/test/St2-18']
。虽然我们之前插入了所有的路径,但现在我们只是用我之前提到的字符串方法使用一个路径,而不是{ params: { something: star.id }}
的格式。
通过这种配置,你仍然可以访问/test/UY-SC
,例如,但你会注意到,Loading...
的信息会出现短暂的时间,因为我们在if-statement中用router.isFallback
准备了这种情况。在数据加载后,它将成功显示在屏幕上。
当使用fallback: 'blocking'
和paths: ['/test/St2-18']
,你会注意到,你看不到任何Loading...
。相反,在改变浏览器的内容之前,浏览器只是多花了一点时间来加载数据。
至于你喜欢哪种方式,就看你自己了。
虽然这个例子展示了getStaticProps()
以及getStaticPaths()
的基本部分,但关于这些Next.js函数,仍有更多内容需要阅读。
不过,你在这里所读到的内容已经足够你在很多情况下开始使用getStaticProps()
和getStaticPaths()