Inner Skills 7: NextJS
七、Next.JS(非常重要)
💡
非常重要。
本章节只是你学习NextJS的指引,并不是NextJS的教程本身。
请你花足够多的时间,沿着指引,去系统性学习NextJS。
NextJS官方教程 https://nextjs.org/learn/ (这也是学习NextJS最好的教程,可以快速入门,预计需要10小时)
视频教程 https://www.youtube.com/watch?v=b4ba60j_4o8&list=PLC3y8-rFHvwhIEc4I4YsRz5C7GOBnxSJY (详细,预计需要20小时)
怎么才算学会?
你本人能够读懂每一行由AI工具生成的代码。

7.1 核心概念
💡
NodeJS、npm、NextJS 三者的关系?

就像盖房子一样!
想象你要建一座房子,这三个东西就是这样的关系:
NodeJS:地基和框架
这就像房子的地基和主体框架
提供了坚实的基础,让一切能够运行
没有它,其他部分都无法存在
NPM:工具箱和建材商店
这就像你的工具箱和一家便利的建材商店
当你需要门窗、瓷砖或其他配件时,NPM 帮你找到并安装它们
它让你不用自己制造每一个零件,可以直接使用别人做好的部分
NextJS:预制的房屋设计
这就像是一个现成的房屋设计方案
已经帮你规划好了房间布局、门窗位置等
让你不必从零开始设计,省时又省力
它们如何一起工作?
1.
首先安装 NodeJS(打好地基)
2.
NodeJS 自带 NPM(有了工具箱和建材商店)
3.
用 NPM 安装 NextJS(选用一个预制的房屋设计)
4.
然后开始建造你的网站,需要什么额外的功能,就用 NPM 去"购买"
7.1.1 小时入门 NextJS
7.1.1.1 环境准备
打开终端,输入npx create-next-app@latest,点击回车

一直点击回车,都选默认项

等待项目创建完成,记住项目的存储路径

打开 Cursor,打开项目(Open project)

选择刚刚创建的文件存储路径

查看已经创建的Next.JS项目

重点关注:
APP文件夹
解释:它是 Next.js 中路由的核心所在。在这个文件夹下,每个文件或文件夹都对应一个页面路由。例如,创建一个 home.js(或 home.tsx 等 )文件在 app 文件夹下,就可以通过浏览器访问对应的首页;如果创建子文件夹,如 app/blog/[slug].js ,可以实现动态路由,用于展示博客详情等。
比喻:相当于你家的 "房间分布图",每个 "房间"(文件 / 文件夹)都是一个网页。
举例:
创建 app/home.js → 访问 http://localhost:3000 就能看到这个页面。
创建 app/blog/[id].js → 访问 http://localhost:3000/blog/123 会根据 id 动态显示文章。
public 文件夹
解释:用于存放静态资源,如图像、字体、图标等。在页面中可以通过相对路径直接引用这些资源,例如在组件中使用 <img src="/logo.png" alt="公司 logo"> ,这里的 logo.png 就可以放在 public 文件夹下。
比喻:像你家的 "公共储物间",放图片、视频、PDF 等大家都能用的东西。
举例:
把 logo.png 放这里,代码里直接写 <img src="/logo.png" /> 就能用。
启动开发环境,查看当前界面,终端运行 npm run dev
运行 npm run dev

打开http://localhost:3000/ ,查看当前项目页面

启动开发环境后,你就可以正常访问 3000 端口网页了,3000 端口是开发中约定俗成的端口,我们会在 Cursor 启动一个终端窗口,来固定启动开发环境。

当你的代码发生了更新,比如将主页做成学员欢迎页
让 Cursor 生成代码

检查生成的代码

无误后点击接受

我们可以看到,本次修改的页面文件,在app 文件夹下的 page.tsx 文件,也就是主页

让我们回到开发界面,查看当前的代码效果

很好,界面已经成功完成了,在这一步,如果你的代码改动比较大,你需要重新启动开发环境
结合我们前面的知识,你需要在开发环境的终端窗口,先结束服务,再重新启动,快捷操作如下
鼠标来到开发环境的终端

按住 Ctrl + c ,停止服务 (Windows电脑也是 Ctrl + c )

按住方向按键的⬆️,出现上一条指令

💡
点击回车,此时开发环境就成功的重新启动了
7.1.1.2 项目结构 & 路由
口诀:文件=路由、文件夹=层级、layout 包壳 page。
├── app/ # ➜*App Router 核心* │ ├── layout.tsx # 所有子路由的根布局 │ ├── page.tsx # 访问 / 时渲染 │ └── globals.css ├── public/ # 静态资源 ├── tsconfig.json └── tailwind.config.ts
在 Next.JS 项目里,页面路由主要和 app 文件夹相关。打开项目目录,找到 app 文件夹,在里面新建一个文件,比如 about.tsx 。这就相当于创建了一个新的页面,用来展示 about 的内容。
Cursor 描述需求

查看代码增加内容

查看页面效果

知识拆解
首先我们在 page 文件夹中,创建了一个 about 文件夹

接着在 about 文件夹中,创建了 page 文件

最后在 http://localhost:3000/ 后添加 /about,查看页面

我们会使用这种方式,逐步增加网站的子页面。详细讲解下 Next.JS 的文件系统的路由机制
Next.js 会自动将 app 文件夹下的文件和文件夹结构映射为对应的 URL 路径
文件夹名对应 URL 路径中的路径段 。创建的 about 文件夹,就对应了 URL 中的 /about 路径部分。
page 文件是可公开访问的页面组件载体 。当在 about 文件夹中创建 page 文件时,它所导出的 React 组件内容,会被渲染到与该文件夹对应的 URL 路径页面上。比如 about/page.tsx 中导出的组件,会在访问 /about 时展示给用户。page文件就像是一个 "页面内容提供者" ,告诉浏览器在对应的 URL 路径下要显示什么。
7.1.1.3 组件
NextJS 里有两种组件(Components),分别是服务端组件和客户端组件。
NextJS默认 Server Components,仅在需要浏览器交互时才标注 "use client"。
服务端组件(Server Components)
特点:
运行在 Node 边缘 / Serverless。
可直接调用后端 SDK、数据库、文件系统。
生成纯 HTML,体积小,SEO 友好。
// app/users/page.tsx (Server 组件)
import { fetchUsers } from "@/lib/api";
export default async function Users() {
const users = await fetchUsers(); // 直接 await
return (
<ul>
{users.map(u => <li key={u.id}>{u.name}</li>)}
</ul>
);
}客户端组件 (Client Components)
"use client";
import { useState } from "react";
export default function Counter() {
const [n, setN] = useState(0);
return (
<button
className="rounded bg-blue-600 px-3 py-1 text-white"
onClick={() => setN(n + 1)}
>
点击 {n}
</button>
);
}共享布局 layout.tsx
// app/layout.tsx
export const metadata = {
title: "一小时 Next.js",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="zh">
<body className="prose mx-auto p-4">{children}</body>
</html>
);
}让我们先创建一个公共 component,点击后有页面的交互动效
Cursor 描述需求

查看代码修改


查看界面效果


接下来我们将解析代码,首先看 Cursor 生成的这个公共组件

我们可以看出,这个是签到按钮,具备了两种形态(签到/已签到)
查看 app 文件夹下的page,我们会发现,在最上方引入了创建的公共组件,并且添加了一个 div 用于展示


在 app/about 文件夹下的 page 文件同理

因此,用这种方法,能够复用功能完好的组件,加速开发流程
7.1.1.4 数据获取
如果在Server Component里,可以直接使用fetch()函数获取数据、以及调用API。
// app/api-example/page.tsx
export default async function ApiExample() {
const res = await fetch("https://jsonplaceholder.typicode.com/users", {
// 缓存 60 秒,等同 ISR
next: { revalidate: 60 },
});
const users: { id: number; name: string }[] = await res.json();
return (
<ul className="list-disc pl-5">
{users.map(u => (
<li key={u.id}>{u.name}</li>
))}
</ul>
);
}如果是在Client Component里,可以使用Ajax的方式获取数据。
要点
"use client" ------ 声明这是 Client Component。
用 useEffect 把网络请求放到浏览器执行,避免 SSR 时跑两遍。
本地维护 loading / error / data 三态即可;初学者先别上管理库。
与 Server 组件不同,这里无法使用 revalidate 等 Next.js 缓存指令,完全由浏览器缓存策略决定。
"use client";
import { useEffect, useState } from "react";
type User = { id: number; name: string };
export default function UsersClient() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then(r => {
if (!r.ok) throw new Error("请求失败");
return r.json();
})
.then((data: User[]) => setUsers(data))
.catch(err => setError(err))
.finally(() => setLoading(false));
}, []); // 只在首屏执行一次
if (loading) return <p>加载中...</p>;
if (error) return <p className="text-red-600">出错:{error.message}</p>;
return (
<ul className="list-disc pl-5">
{users.map(u => (
<li key={u.id}>{u.name}</li>
))}
</ul>
);
}7.1.1.5 public目录
在 Next.js 中,public 文件夹是一个特殊的目录,用于存放不需要经过 Webpack 等构建工具处理的静态资源。这些资源可以直接通过 URL 访问,是项目中存放公开静态文件的标准位置。
让我们在项目中,添加一个图片
Cursor 描述需求

查看代码修改

查看界面效果

代码解析
首先在 public 中添加了一张 1 图片

在 app 文件夹下的 page 实现导入


以上就是在做产品中,添加图片的方式,当你的产品逐渐功能开始完善,你将会在 public 中创建多层文件夹,来管理你的各项图片
7.1.1.6 内置优化 & 资源管理
NextJS 把性能优化做成了「默认开启」。只要用对官方组件,就能自动获得更小的包体、更快的首屏和更好的 SEO。下面列出初学者需要优先关注的4 个点------图片、链接、字体、元数据------每个都配一段可直接复制的代码。
<Image>------ 响应式图片 & 自动压缩
import Image from "next/image";
// pages 或 app 组件里
export default function Hero() {
return (
<Image
src="/banner.jpg" // 本地 /public 里的资源
alt="首页横幅"
width={1200}
height={400} // 固定基准尺寸
sizes="100vw" // 告诉浏览器在各断点占满宽度
priority // 首屏优先加载
placeholder="blur" // 先用模糊占位
className="rounded-xl"
/>
);
}自动格式转换:支持 AVIF/WebP,依据浏览器能力回落到 JPEG/PNG。
多尺寸裁切:只下载当前视口需要的尺寸,移动端节省流量。
远程图片:在 next.config.js 里 images.remotePatterns 白名单即可。
<Link>------ 预取 & 无刷新跳转
import Link from "next/link";
<Link href="/pricing" prefetch>价格计划</Link>prefetch 默认就开(视口内自动),当链接滚入可视范围时提前把目标页面 HTML + JS 都下载好;点击几乎 0 延迟。
next/font ------ 零 FOIT/FOUC 的可变量字体
// app/layout.tsx
import { Inter } from "next/font/google";
const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
display: "swap", // 避免闪白
});
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="zh" className={inter.variable}>
<body>{children}</body>
</html>
);
}字体文件 按需子集化,只打包用到的字形。
自动加 preload/font-display,首屏不闪。
也支持本地字体:next/font/local({ src: "./AlibabaSans.woff2" })。
元数据 & SEO ------ metadata / generateMetadata
// app/blog/[slug]/page.tsx
export const revalidate = 60; // ISR
export async function generateMetadata({ params }) {
const post = await fetchPost(params.slug);
return {
title: post.title,
description: post.excerpt,
openGraph: {
images: post.cover,
},
};
}Next .js 会把返回值注入 <head>,同时输出到 /_next/data/*.json 供客户端水合。
动态页面也能静态化:结合 revalidate,既有 SEO 又可频繁更新。
支持 Twitter Card、robots、viewport... 一律类型安全。
7.1.2 二十小时精通 Next.JS
创建一个 Next.JS 文件
1.在 vscode/Cursor 打开想要新建 Next.JS 的文件

2.并在该文件内新建一个终端并打开

3.输入 npx create-next-app@latest

4.按下图设定 yes/no(通过键盘左右键切换)

5.即可在以下路径找到你的文件

6.即可看到你创建的 nextJS 程序啦~

运行 Next.JS
cd 到项目文件夹,再输入npm run dev

npm run dev 和 npm build 区别总结:
命令
作用
适用环境
主要特点
npm run dev
运行本地开发服务器
开发环境
热重载、实时编译、调试友好
npm build
构建优化后的静态文件
生产环境
代码压缩、优化、打包成可部署版本
b.
然后在浏览器中转到 http://localhost:3000/ ,您将可以看到 Next.JS 为您设置的样式

Page(页面)
💡
比喻:房间
每个页面就像房子里的一个房间,有特定的用途和内容
你可以从一个房间走到另一个房间(导航)

你可以在这里发现"app"等文件夹,其中 app 文件夹包含全局 CSS 文件中的所有页面和组件。
如果仔细观察,就会发现有一个名为"page.tsx"的 JavaScript 文件。打开它时,您将看到我们最初启动 NextJS 应用时出现的样式页面代码。



API 路由
Next.JS 使用 app router(应用程序路由器),并用文件夹定义路由。即:Next.JS 的 API 路由本质上是一个基于文件系统的路由,每个文件都代表一个 API 端点。
💡
比喻:传话员
就像一个传话员,负责在前台(前端)和后厨(后端)之间传递信息
顾客(用户)通过传话员点餐,厨师(服务器)通过传话员送出菜品
1.
例如 page.tsx 位于我们的 app 文件夹中,这使得我们的 page.tsx 成为主页,可通过 http://localhost:3000/ 访问。

2.
此时,如果我们想去 localhost 3000/about
只需要在 app 文件夹中创建一个新文件夹,并将其命名为"about",再在"about"文件夹中创建一个名为"page.tsx"的新文件。这将是"about"路由对应的页面 http://localhost:3000/about

如果你想在"about"目录中添加更多嵌套路由,可以使用相同的逻辑。先在"about"文件夹内创建一个新的文件夹"projects",再这个文件夹下创建一个名为"page.tsx"的新文件。
3.
如果您想创建部分 URL 是动态的路由,只需在文件夹中使用方括号。
如:"localhost:3000/about/projects/[projectId]"
a.
在"projects"文件夹中,创建一个为"[projectId]"的文件夹。这将允许您捕获 URL 的 [projectId] 动态部分作为参数。
b.
这样,如果你导航到 localhost:3000/about/projects/ 后跟任意数字或单词,例如 123(localhost:3000/about/projects/123),它将提供"projectId"文件夹内的 page.tsx 文件。

c.
params(参数):在页面内部,我们可以访问关键字"params(参数)","params"对象用于捕获 Next.JS 中的动态路由参数,特别是在处理动态路线时。
1 在我们的示例中,代码引用了动态路由,localhost:3000/about/projects/[projectId],在这种情况下,"projectId"是 URL 的动态部分,并且 Next.JS 在访问路由时会自动用相应的值填充"params"对象,即 project Id: 123
💡
const **page** = ({ params }) => {
return <div>project Id: {params.projectId}</div>;
};
export default page;2 假设您想根据"projectId"从 API 或数据库中获取项目详细信息。您可以使用"params"对象来访问项目 ID,然后调用接口以检索该特定项目的数据。
💡
const ProjectPage = async ({ params }) => {
//获取服务器上的项目数据
const response = await fetch(\`/API/projects/\${params.projectid}\`);
const projectData = await response.JSon();
};动态路由非常适用于具有单个实体页面的应用,例如博客文章、产品或用户资料,这些页面数据经常变化但布局或设计保持一致。
如果没有动态路由,您将需要为每个实体手动创建单独的页面,这导致大量静态页面,维护既耗时又难以扩展。
布局系统(Layouts 文件)
📌
比喻:房子的框架结构
就像房子的墙壁和走廊,决定了各个房间如何连接
所有房间共享同样的屋顶和地基(页眉、页脚)

在 Next.JS 中,布局是一种在应用程序的不同页面之间共享一致 UI 的方式,只需将布局视为页面的模板(Templates)即可。
RootLayout(根布局)**:**在代码中,该布局的文件由 Next.JS 自动生成,它采用"children"参数。

这意味着这个布局模板将适用于任何地方,无论是在"home"目录(http://localhost:3000/)还是\"about\"目录(http://localhost:3000/about)。
布局文件通常用于需要出现在每个页面上的元素,例如导航栏、页脚和侧边栏。这可以防止重复并确保 UI 保持一致。

字体
如果我们去检查我们的网站,我们可以看到字体、Geist Mono 和 sans 已经实现。

提供一个例子~:假设您不知道,它已经包含 Google 字体。您想导入"inter"作为您的字体,可以从"next/font/google 目录中获取它,然后创建一个常量,再在类名中应用它,就可以使用全局 CSS 来设置字体。
//从"next/font/google目录中获取inter
import { Inter } from "next/font/google";
//创建一个常量
const inter = Inter({
subsets: ["latin"], //**只会加载拉丁字符**,减少不必要的字体文件,提高性能。
display: "swap", //加载策略,先用系统字体,加载完成后替换为
Inter,避免空白问题。
});
//把 inter.className 应用到 HTML 标签,使整个页面都会继承 Inter 字体。
export default function **RootLayout**({ children }) {
return (
<html lang="en" className={inter.className}>
<body>
<Navbar />
{children}
</body>
</html>
);
}