在2022年底,Next.js团队启动了下一个稳定版本:next.js 13.虽然此更新具有许多改进的功能,但设置了一个新功能,以重新定义您的下一步构建方式:目录beta。
App Beta不使用默认值。它需要启用,然后可以逐步采用。在本文中,我们将查看新的应用程序结构,并将现有的Hygraph项目转换为此新结构。我们将逐步进行此逐步,看看增量采用如何真正起作用。
必需的知识
为什么要应用程序结构?
新的应用程序目录结构不仅仅是您项目的重组(尽管是,并且重组感觉很强大)。这种新方法利用React Server Components带来了许多新功能和心理模型。使用服务器组件,开发人员可以在不需要发送给客户端的React组件中执行业务逻辑。这允许将少量的JavaScript发送到浏览器。如果您有前端需求,则可以使用单独发送到浏览器的客户端组件。这应该将您的捆绑尺寸降低到更易于管理的状态。只是您需要进行交互的代码。
总体数据提取模型也发生了变化。 Next已从节点和浏览器API扩展了native fetch() method,并添加了其他缓存和重复数据删除功能。这意味着您可以在各种文件中创建有关数据的请求,但是该请求只会向您的API发射一次。获取您的博客文章以在主页上创建列表并创建博客页面列表?现在,尽管在多个位置编写了代码,但这是S 1 API请求。这为开发人员提供了很多便利,同时创造了更好的构建和服务时间。
还有更多新的和改进的功能,但是要使用它们,我们需要能够升级我们的项目以使用这种新结构。它并不像将页面文件移至应用程序目录那样简单,所以让我们挖掘并更新内容。
设置下一个。JS13项目与应用程序目录一起使用
要开始,我们需要一个next.js项目。尽管使用数据获取的任何下一个项目都可以使用此过程,但让我们从hygraph-examples GitHub repository的简单下一个示例开始。运行以下命令:
npx degit hygraph/hygraph-examples/with-nextjs with-nextjs
cd with-nextjs
在安装之前,我们需要更新package.json文件中的一些依赖项。让我们在^13.0.0
旁边升级并删除react
和react-doc
依赖项。
您的软件包现在应该看起来像这样:
{
"name": "hygraph-with-nextjs",
"private": true,
"license": "MIT",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"graphql-request": "^1.8.2",
"next": "^13.0.0"
}
}
现在我们可以通过运行npm run dev
启动开发服务器。立即加载本地版本,我们介绍了第一个更新。由于以前没有运行下一个13,因此链接组件结构错误。在下一个13中,我们不再需要在链接组件中具有锚固标签。这是一个很好的更新,但是我们需要更改这些参考。使用链接组件的唯一位置是/src/pages/index.js
中的主页。打开该文件,然后删除<a></a>
标签以使网站运行
// Old
<Link key={slug} href={`/products/${slug}`}>
<a>{name}</a>
</Link>
// New
<Link key={slug} href={`/products/${slug}`}>
{name}
</Link>
现在该站点运行,让我们从页面目录进行更新到新的应用程序目录。
从 /页面转换为 /应用
在我们可以转换为应用程序目录之前,我们需要让我们知道我们将使用此实验功能。为此,我们需要一个配置文件。在我们项目的根本上,让我们创建一个next.config.js
文件。在该文件中,添加以下基本配置。
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
appDir: true,
},
};
module.exports = nextConfig;
从那里,我们可以创建我们的应用程序目录。在SRC目录中,添加一个app
目录。我们将把我们的每条路线都移至该目录。一件好事是,我们的每条旧路线都会在我们进行这些更改时继续工作,因为两个目录都可以同时工作。
创建根布局组件
整体结构的第一个重大变化之一是需要一个根布局。这将是所有页面的标记的脚手架。这可以非常简单,但是具有每条路线所需的任何全局信息。 RootLayout
功能接受儿童对象。孩子对象将包含我们通过创建的每个页面添加的服务器组件。
export default function RootLayout({children}) {
return (
<html lang="en">
<body>
{ children }
</body>
</html>
)
}
使用此布局,我们现在可以添加第一页。
添加主页
而不是/pages
目录的index.js
约定,应用程序目录中的每个页面都将命名为page.js
。在应用程序目录的根部,创建一个新的page.js
文件。这将是我们的主页。
创建此页面后,接下来会在控制台中丢下错误。现在有一条冲突的路线:/src/pages/index.js
与/src/app/page.jsx
相同。目前,将/src/pages/index.js
重命名为oldIndex.js
。我们稍后将删除此文件,但是此文件是我们新文件的蓝图。
我们重命名文件后,我们会收到一个新的错误。 page.jsx
文件没有导出反应组件。实际上,它出口任何东西,因为它是空白的。让我们修复并用H1的产品导出一个简单的反应组件。
export default async function Page () {
return (
<>
<h1>Products</h1>
</>
)
}
现在,主页应具有H1而不是旧主页中的产品列表。让我们获取产品列表。
在旧索引文件中,我们导出一个称为getStaticProps
的函数,以获取并将数据传递到我们的页面组件。在新结构中,我们不需要以任何特定方式导出任何东西或命名。相反,我们可以创建一个常规的异步函数来获取我们的数据。
在新的page.jsx
文件中的服务器组件之前,请创建一个名为getProducts
的新异步函数。这将对项目的Hygraph端点运行并返回产品数组。然后,我们可以在服务器组件中运行该函数以循环通过数据并显示每个页面的链接。
import Link from 'next/link';
const getProducts = async () => {
const response = await fetch('https://api-eu-central-1.hygraph.com/v2/ck8sn5tnf01gc01z89dbc7s0o/master', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
query: `{
products {
slug
name
id
}
}`
})
})
const {data} = await response.json()
return data.products
}
export default async function Page () {
const products = await getProducts()
return (
<>
<h1>Products</h1>
<ul>
{products.map(product => (
<li key={product.id}>
<Link href={`/products/${product.slug}`}>{product.name}</Link>
</li>
))}
</ul>
</>
)
}
我们的旧版本和新版本之间发生了一些变化。
- 由于我们不使用
getStaticProps
,因此我们不再需要以特定的方式构建回报。相反,我们只是返回一系列产品 - 使用
fetch
代替GraphQLRequest
。通过使用fetch,我们允许下一步缓存信息,如果我们以后执行相同的获取,它将不会单独呼叫,而是使用此数据
保存下来后,我们将拥有一个功能正常的主页。这些链接甚至将我们带到与旧动态路线相关的页面,因为/app
和/pages
可以逐步工作。这可能是一个很好的停止点,但也可以将我们的动态路线转换为。
转换动态产品路线
就像主页一样,我们将从设置结构开始。嘿,目录上发生在文件上的动态路线括号,而是在目录上发生。因此,/pages/products[slug].js
将变为/app/products/[slug]/page.jsx
。这允许更具灵活性和添加自定义,共同确定的功能,例如错误页面,加载页和自定义布局。
创建新文件并添加一个像主页一样的简单组件。
export default async function Page() {
return (
<>
<h1>This is a product</h1>
</>
)
}
这次,应用程序会立即错误,因为静态页面是重建的,但是如果您接下来重新启动,它将与以前有关冲突文件的错误。删除或重命名文件,我们将继续前进。
该文件的整体结构有显着差异。就像主页一样,我们不再需要GetStaticProps功能,但我们也不需要Getstatic Paths。这些都是在服务器上渲染的所有文件,因此我们可以将所有这些信息移动到可能在请求时发生的单个异步函数。
新的服务器组件为我们提供了一个参数对象,我们可以使用该对象来获取当前路由的sl。我们可以将其传递到一个新的GetProduct()函数中,并将其用作对Hygraph查询的变量。这将获取该特定产品的数据。然后,我们可以在组件生成的标记中使用它来显示标题,描述和价格。
async function getProduct(params) {
const response = await fetch('https://api-eu-central-1.hygraph.com/v2/ck8sn5tnf01gc01z89dbc7s0o/master', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
query: `{
product(where: {slug: "${params.slug}"}) {
name
description
price
}
}`,
variables: {
slug: params.slug
}
})
})
const {data} = await response.json()
return data.product
}
export default async function Page({params}) {
const product = await getProduct(params)
return (
<>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>{product.price}</p>
</>
)
}
我们现在有所有页面一起工作。让我们进一步迈出一步,并为发现的任何产品添加自定义的404页。
设置自定义404页
由于新的应用结构,我们可以为所有相对文件分类。在这种情况下,如果我们想要自定义404页,我们可以在/products/[slug]
目录中添加not-found.jsx
模板。
文件将导出服务器(或客户端)组件,目前,我们将有一个非常简单的消息。在这种情况下,由于我们要寻找一种产品,所以请具体说明,而不是找到一个简单的页面。
export default function NotFound() {
return (
<>
<h1>404</h1>
<p>Product not found</p>
</>
)
}
现在,导航到不匹配Hygraph中任何sl的路线:尝试http://localhost:3000/products/sdlfkjl/
我们没有获得404页,而是收到一个未手持错误。这是因为我们需要下一步说明我们找不到它。在这种情况下,如果我们对Hygraph的查询不返回结果,我们需要投掷404。
调用getProduct()函数后,我们可以检查产品是否定义,如果不是,则从next/navigation
运行NotFound()函数。这将为我们做一些事情。它将将浏览器重定向到NOTUND组件,并在这些路由上自动添加“ NoIndex”元标记。这是一个很好的SEO练习,将使您的网站404页被搜索引擎索引。
export default async function Page({params}) {
const product = await getProduct(params)
if (!product) notFound()
return (
<>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>{product.price}</p>
</>
)
}
现在,我们有一个功能齐全的404页!
从这里,您可以删除 /页目录并转到您的应用程序。您仍将使用页面目录以及您要使用的任何静态路线,但大多数东西都可以用于Next的新版本。
下一步
这样,我们已经将Simple Hygraph Next.js示例从 /页面转换为 /应用程序。在这一点上,我建议您查看您可以在共关联目录中创建的所有不同文件类型。
- 添加一个head.jsx文件以指定每个产品页面的元数据,例如标题,描述等。
- 添加一个loading.jsx文件,用于加载页面,用于较长的查询。
- 添加一个error.jsx文件以处理其他错误类型。