Skip to content

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>
  );
}

Released under the MIT License.