在本教程中,我将带您了解 Next.js 的基础知识,并指导您创建您的第一个全栈应用程序。在本教程结束时,您将有信心开始使用 Next.js 构建您自己的全栈应用程序。
因此,让我们直接进入并一起释放 Next.js 的力量。
(NextJs教程:
以下是我们将介绍的内容:
-
我们要建造什么?
-
入门
-
-
如何在 Next.js 中创建自定义导航栏
-
如何在 Next.js 中创建 API 路由
-
如何建立主页
-
Next.js 中的 App Router 是什么?
-
如何增强 Next.js 代码库的模块化和可维护性
-
如何创建动态角色页面
-
如何在 Next.js 中创建动态 API 路由
-
如何在 Next.js 中创建动态 UI 路由
-
Next.js 中有什么
generateStaticParams? -
dynamicParamsNext.js 生成静态页面的目的是什么? -
如何生成静态页面
generateStaticParams -
如何建立测验部分
-
如何在 Next.js 中创建客户端组件
-
结论
好吧,让我们开始吧!
我们要建造什么?
在本教程中,我们将创建一个引人入胜的应用程序来展示有关恶搞之家角色的信息。此外,我们将包括一个测验部分,用户可以在其中测试他们对节目的了解。
为了让您简单熟悉,我们将避免使用数据库,而是使用本地 JSON 数据。通过消除数据库集成的复杂性,我们可以专注于掌握 Next.js 的基本概念。
申请预览
入门
要开始学习本教程,我强烈建议使用我专门为本教程创建的入门样板。它已经包括必要的依赖项和文件夹结构,从而节省了您宝贵的时间,无需从头开始设置您的项目。
只需从 GitHub 存储库中克隆入门样板,然后按照教程进行操作。这样,您就可以专注于学习和实施这些概念,而不会陷入设置细节中。
-
入门样板:
-
最终版本:
设置启动样板并在本地计算机上成功运行后,您应该能够看到初始页面。此页面标志着我们教程的开始,并将作为我们旅程的起点。
样板的初始页面
从这里开始,我们将逐步构建现有代码并在我们的应用程序中实现一些很酷的功能。让我们开始吧,马上开始吧!
如何在 Next.js 中创建共享布局
通常在您的应用程序中,您有跨多个页面共享的元素,例如导航栏或页脚。手动将这些元素添加到每个页面可能既乏味又容易出错。幸运的是,Next.js 提供了一种便捷的方式来创建可在整个应用程序中重复使用的共享布局。
第一种布局称为根布局。顾名思义,此布局在我们应用程序的所有页面之间共享。它作为最顶层的布局,为我们的整个应用程序提供了一致的结构。Root Layout 是必需的,我们需要确保它包含必要的 HTML 和 body 标签。
接下来,让我们考虑应用程序中的各个路由段。每个段都可以选择定义自己的布局。这些布局类似于根布局,将在该段内的所有页面之间共享。这允许您为应用程序的不同部分设置特定的布局,同时仍然在每个部分中保持一致的结构。
现在,打开app/layout.js并向其中添加以下代码:
// ? app/layout.js
const inter = Inter({ subsets: ['latin'] })
export const metadata = {
title: 'Family Guy',
description: 'Come here and learn more about Family Guy!',
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>
<Navigation />
{children}
</body>
</html>
)
}
您在这里看到的组件是 Root Layout 组件,它在为整个应用程序创建共享布局方面起着至关重要的作用。让我们仔细看看它的结构和功能。
在组件中,您定义metadata对象,其中包含应用程序的默认元数据标签。该title属性指定您的应用程序的标题,而该description属性提供简短的描述。这些元数据标签对于搜索引擎优化 (SEO) 很重要,如果需要可以针对特定路线覆盖这些标签。
在RootLayout函数内部,您可以使用html和body标记构建 HTML 文档。您将标记lang的属性设置html为"en"以指示内容是英文的。
在body标记中,您包括Navigation从目录导入的组件components。该组件代表您的导航栏,并将在您的应用程序的所有页面之间共享。通过将其包含在此处,您可以确保它在整个应用程序中始终如一地显示。
propchildren是一个特殊的 prop,代表组件内呈现的内容RootLayout。这允许您在共享布局中嵌套其他组件和内容。
最后,您导出RootLayout组件,使其可用于整个应用程序。
如何在 Next.js 中创建自定义导航栏
在本节中,您将为您的应用程序创建一个简单的导航栏组件。导航栏将包含一个徽标和一个将用户带到测验部分的链接。打开components/Navigation.jsx并添加以下代码:
// ? components/Navigation.jsx
export const Navigation = () => {
return (
<div className="sticky top-0 backdrop-blur-xl bg-[rgba(0,0,0,0.8)] border-b border-slate-800 z-50">
<Container className="flex justify-between py-5">
<Link href="/">
<Image src="/logo.png" alt="Family Guy" width={70} height={50} />
</Link>
<Link
href="/quiz"
className="flex items-center justify-center gap-1 px-5 font-semibold text-black transition-colors bg-green-500 rounded-md duration-600 hover:bg-green-600"
>
<TbArrowBigRightFilled className="text-lg" />
Take a Quiz
</Link>
</Container>
</div>
)
}
现在你有了Navigation在整个应用程序中共享的粘性组件。如果你打开你的本地服务器,你应该能够看到以下结果:
粘性导航栏预览
祝贺你到目前为止的进步!您已经为您的 Next.js 应用程序成功创建了一个带有导航栏的共享布局。这种共享布局可确保所有页面的一致性,从而更轻松地管理整个应用程序中的导航栏等元素。
现在,是时候专注于构建主页来显示字符了。为了在主页上显示这些字符,您需要创建一个 API 路由来从您的本地 JSON 文件中检索所有字符,从而允许您使用相关信息动态填充主页。
如何在 Next.js 中创建 API 路由
Next.js 中的路由是一个基本概念,它决定了如何访问应用程序的不同部分。当你在 Next.js 的目录中创建一个文件夹时app,它会自动成为一个路由。但是您可以灵活地定义它应该是 UI 路由还是 API 路由。
命名路由文件夹中的文件page.jsx会将其转换为 UI 路由。这意味着它将充当带有 UI 组件的常规页面。另一方面,如果你将文件命名为route.js,它就变成了一个 API 路由。这表示它将处理 API 请求和响应。
请务必记住,在单个目录中,您可以拥有 UI 路由或 API 路由,但不能同时拥有两者。这种清晰的分离允许在构建 Next.js 应用程序时有一个干净和有组织的结构。
在下一节中,您将在 Next.js 中创建您的第一个 API 路由。Next.js 中的 API 路由提供了一种在应用程序中创建服务器端端点的简单方便的方法。
使用 API 路由,您可以定义处理 HTTP 请求和响应的自定义路由,允许您获取或修改数据、执行服务器端计算或与外部服务集成。
这些路由被编写为 JavaScript 函数,在云中自动部署为无服务器函数。API 路由在您的前端 Next.js 应用程序中提供类似后端的功能,使您能够构建动态和交互式 Web 应用程序,而无需单独的服务器。
如何建立主页
在本节中,您将创建一个 API 路由,使您能够检索存储在本地 JSON 文件中的所有可用字符。通过实施此 API 路由,您将能够获取字符并将其显示在应用程序的主页上。
如何创建字符 API 路由
为了确保 API 代码和 UI 代码之间的清晰分离,您将在目录中放置所有 API 路由app/api。
通过采用这种方法,您可以有效地将与 API 相关的功能与用户界面隔离开来,从而促进更好的组织和可维护性。
本节将指导您完成创建 Characters API 路由的过程。只需打开app/api/characters/route.js文件并添加以下代码:
// ? app/api/characters/route.js
export async function GET() {
return NextResponse.json({ characters: characters.data })
}
在此代码片段中,您将导入一个名为 .json 的 JSON 文件characters.json。该文件包含有关您要在应用程序中使用的字符的数据。
NextResponse接下来,您将从模块中导入对象next/server。此对象提供用于处理 Next.js 应用程序中的服务器响应的函数。
之后,您定义一个名为 的异步函数GET()。此函数与 HTTP GET 请求方法相关联,该方法通常用于从服务器检索数据。
在函数内部GET(),您使用该NextResponse.json()函数来构造服务器响应。您传递一个具有名为 的属性的对象characters,该对象保存文件中的数据characters.json。然后从函数返回此响应。
简而言之,这段代码正在创建一个响应 GET 请求的 API 路由。当向该路由发出 GET 请求时,它会返回一个包含文件数据的 JSON 响应characters.json。这允许您从您的应用程序中获取字符数据并将其用于我们代码的其他部分。
现在,是时候测试您的 API 路由并确保一切正常运行了。为了简化此过程,您将使用浏览器本身来发出 API 请求。打开浏览器并输入以下 URL:http://localhost:3000/api/characters。
执行此操作后,您将被定向到一个页面,您可以在其中观察 API 请求的结果。这一步允许我们验证 API 路由是否按预期工作并且它是否成功获取字符数据:
浏览器中的 JSON 数据
这是包含字符列表的 JSON 数据。如果 JSON 数据在您的浏览器中看起来很奇怪,请确保在您的浏览器上安装 JSON Formatter 扩展。我使用的是谷歌浏览器,所以我在我的浏览器上使用了这个 JSON Formatter。
如何在首页显示字符
现在您已经设置了 API,让我们为我们的主页创建 UI 并显示字符。为此,打开app/page.jsx文件并添加以下代码片段:
// ? app/page.jsx
async function getAllCharacters() {
const data = await fetch(`${endpoint}/characters`)
if (!data.ok) {
throw new Error('Failed to fetch data')
}
return data.json()
}
export default async function Page() {
const data = await getAllCharacters()
return (
<main>
<Container className="grid grid-cols-2 gap-1 py-5 md:grid-cols-3 lg:grid-cols-4">
{data?.characters?.map(item => {
return (
<Link
href={`/characters/${item.slug}`}
key={item.name}
className="overflow-hidden rounded-md"
>
<Image
src={item.avatar}
alt=""
className="transition-all duration-500 hover:scale-110 hover:-rotate-2"
width={500}
height={500}
/>
</Link>
)
})}
</Container>
</main>
)
}
在上面的代码片段中,您有一个名为 React 的组件Page,它被定义为一个异步函数。该组件负责渲染首页 UI。
首先,您调用了一个异步函数getAllCharacters,该函数使用“fetch”函数向 API 端点发出异步 HTTP 请求。此请求的响应存储在data变量中。
接下来,您将进行错误处理检查。如果 HTTP 响应返回错误(状态代码不是 200),我们将抛出一个错误,表明数据获取失败。
转到组件Page,它等待调用函数的结果getAllCharacters 。结果数据存储在data变量中。
return 语句呈现主页的 UI。它使用一个main标签作为顶级容器和一个Container组件来保存具有多列的网格布局。
在 中Container,您映射对象characters中的数组data并生成项目列表。对于每个角色,我们创建一个“链接”组件,用作指向特定角色页面的可点击链接。链接的 URL 是根据角色的 slug 属性生成的。
在 中Link,您有一个Image显示角色头像图像的组件。
总的来说,此代码从 API 端点获取数据,特别是字符数据。然后,它使用此数据动态呈现角色化身的网格布局,并带有指向各个角色页面的可点击链接。
主页
您的主页现在看起来很棒,但您可能已经注意到我们获取数据的方式有些不寻常。通常,您可能熟悉使用 useEffect 挂钩从 API 获取数据。但在这种情况下,您没有使用任何钩子——但您的代码运行良好。
在下一节中,我们将仔细研究这个组件中究竟发生了什么。通过检查代码及其执行,您将对 Next.js 机制有更深入的了解。
Next.js 中的 App Router 是什么?
Next.js 中的 App Router 引入了一种利用 React 的最新功能开发应用程序的新范例。如果您已经熟悉 Next.js,您会发现 App Router 代表了基于文件系统的现有 Pages Router 的自然演变。
默认情况下,App Router 基本上使您能够在服务器上运行 React 代码,因此您在服务器上获取数据并且只将静态 HTML 返回给客户端。这意味着我们有一个服务器组件,它从服务器检索数据并在服务器端呈现其内容。
有一个需要考虑的警告:您将无法访问服务器组件中的 React 状态和 React Hooks 等客户端功能,因为它们仅在服务器上运行。
如果要使用客户端功能,则必须通过"use client"在文件顶部添加来在组件文件中指定它。
Next.js 中服务器端渲染的意义何在?
在 Next.js 中,SSR 允许服务器生成网页的 HTML 内容并将其发送到浏览器。这意味着当您访问 Next.js 网站时,您不必等待 JavaScript 代码在浏览器上加载和执行就可以看到任何内容。相反,服务器会发送一个预呈现的 HTML 页面,该页面几乎可以立即显示。
SSR 的优势在于它可以缩短网页的初始加载时间,提供更快、更无缝的用户体验。它还有助于搜索引擎优化 (SEO),因为搜索引擎可以轻松抓取和索引服务器呈现的 HTML 内容。
Next.js 中的服务器端渲染方法
Next.js 提供了几种渲染页面的方法。这些方法中的每一个都有特定的用途,并且可以在不同的场景中使用:
-
静态站点生成 (SSG):静态生成是一种服务器端呈现方法,其中 Next.js 在构建时生成 HTML。在构建过程中,Next.js 从 API 或其他数据源获取数据并预呈现 HTML 页面。然后可以根据请求将这些预呈现的页面提供给客户端。SSG 适用于内容不经常变化的网站。
-
服务器端渲染 (SSR):服务器端渲染是 Next.js 在每个请求上生成 HTML 的另一种方法。当用户访问页面时,Next.js 获取数据并在服务器上呈现 HTML,然后再将其发送到客户端。SSR 对于内容更新频繁或用户体验个性化的网站很有用。
-
增量静态重新生成 (ISR): ISR 是 Next.js 中的一项功能,它允许您按需静态生成页面,而不是在构建时。这意味着您的站点可以同时静态生成和动态生成。
现在我们对 Next.js 中的服务器端渲染有了更好的理解,我们可以继续下一节。
如何增强 Next.js 代码库的模块化和可维护性
为了避免代码重复并增强代码的可重用性,您可以在 Next.js 项目中采用模块化的方法。通过将常用功能隔离getAllCharacters在一个单独的模块中,您可以方便地在代码库的多个部分访问和重用它们。
您可以在项目中进行快速调整。首先,导航到app/page.jsx文件并找到getAllCharacters顶部的函数。从文件中删除这个函数。
接下来,打开文件并从那里lib/characters.js导出函数。getAllCharacters通过将函数移动到单独的模块,您可以轻松地在代码库的不同部分导入和使用它:
// ? lib/characters.js
import { endpoint } from '@/utils/endpoint'
export async function getAllCharacters() {
const data = await fetch(`${endpoint}/characters`)
if (!data.ok) {
throw new Error('Failed to fetch data')
}
return data.json()
}
现在让我们getAllCharacters从中导入函数lib/characters.js并在内部使用它app/page.jsx:
// ? app/page.jsx
import { getAllCharacters } from '@/lib/characters'
export default async function Page() {
const data = await getAllCharacters()
return (
<main>
//content went here ...
</main>
)
}
这样,您将可以在整个代码库中访问此获取功能。
如何创建动态角色页面
恭喜您在本教程中达到了这一点!到目前为止,您已经对 Next.js 的基础知识有了深入的了解。
在本节中,您将创建一个动态 API 路由。此路线将使您能够单独获取每个角色的数据,并随后构建一个用户界面 (UI) 以向您的用户展示这些角色。
如何在 Next.js 中创建动态 API 路由
通过在 Next.js 中创建动态 API 路由,您可以根据角色的 slug 获取角色数据。为此,您需要使用方括号来命名您的文件夹,向 Next.js 表明这是一个动态路由。通过相应地命名文件夹,您可以在代码中访问此动态值,从而允许您检索和显示所需角色的数据。
打开api/characters/[slug]/route.js并添加以下代码段:
// ? api/characters/[slug]/route.js
export async function GET(req, { params }) {
try {
const character = characters.data.find(item => item.slug === params.slug)
if (!character) {
return new NextResponse('not found', { status: 404 })
}
const character_qoutes = qoutes.data.filter(
item => item.character_id === character.id,
)
return NextResponse.json({
character,
character_qoutes: character_qoutes.length > 0 ? character_qoutes : null,
})
} catch (error) {
return new NextResponse('Internal Server Error', { status: 500 })
}
}
在上面的代码片段中,您有一个名为GET处理 Next.js API 路由中的 GET 请求的异步函数。让我们一步一步地分解它:
-
您使用 Next.js 文件系统(和)从它们各自的 JSON 文件中导入
characters和数据。quotes``@/data/characters.json``@/data/quotes.json -
该函数接收两个参数:(
req代表传入的请求)和一个对象params,该对象包含从请求 URL 中提取的动态参数。 -
characters在 try-catch 块内,代码尝试通过将slug参数 fromparams与每个字符对象的属性进行比较来查找数据中的字符slug。 -
如果未找到任何字符,代码将使用包
NextResponse中的类返回状态代码为 404 的“未找到”响应next/server。 -
如果找到一个字符,代码将继续根据
quotes与character_id找到的字符的id. -
过滤后的字符引号分配给
character_quotes变量。 -
最后,代码使用 返回一个 JSON 响应
NextResponse.json(),包括character对象和character_quotes数组(或者null如果没有找到引号)。
Next.js 自动从 URL 中提取动态参数并使它们在params对象中可用。在此代码中,您slug使用访问参数params.slug。这允许您从 URL 中检索特定字符的 slug,并使用它来查找数据中的相应字符characters。
现在您可以测试此端点以查看结果,在浏览器中打开http://localhost:3000/api/characters/peter-griffin,您应该能够看到以下 JSON 数据:
浏览器中的 JSON 数据
如何在 Next.js 中创建动态 UI 路由
现在您的 API 已设置并能够获取角色数据,是时候创建一个动态 UI 页面来展示此数据了。
创建动态 UI 页面的过程与您在设置动态 API 路由时所做的非常相似。但这一次,您将使用page.jsx而不是route.js生成 UI 路由。
打开app/characters/[slug]/page.jsx并添加以下代码段:
// ? app/characters/[slug]/page.jsx
import { getAllCharacters } from '@/lib/characters'
export const dynamicParams = false
export async function generateStaticParams() {
const { characters } = await getAllCharacters()
return characters.map(character => ({ slug: character.slug }))
}
export async function getCharacterBySlug(slug) {
const data = await fetch(`${endpoint}/characters/${slug}`)
if (!data.ok) {
throw new Error('Failed to fetch data')
}
return data.json()
}
export default async function Page({ params }) {
const { character, character_qoutes } = await getCharacterBySlug(params.slug)
return (
<Container className="flex flex-col gap-5 py-5" as="main">
<div className="flex flex-col gap-2">
<h1 className="text-2xl font-semibold capitalize">{character.name}</h1>
<ul className="flex gap-1 text-sm">
{character.occupations.map(item => {
return (
<li
key={item}
className="p-2 text-gray-300 bg-gray-800 rounded-md"
>
{item}
</li>
)
})}
</ul>
</div>
<p className="text-sm leading-6">{character.description}</p>
<ul className="grid gap-2 sm:grid-cols-2">
{character.images.map(image => {
return (
<li
key={image}
className="relative flex overflow-hidden bg-gray-900 rounded-xl"
>
<Image
className="transition-all duration-500 hover:scale-110 hover:rotate-2"
src={image}
alt=""
width={760}
height={435}
/>
</li>
)
})}
</ul>
{character.skills && (
<>
<h2 className="text-xl font-bold">Power and Skills</h2>