03 - NextJS Essentials (App Router)

02 - NextJS Essentials (App Router)
03 - NextJS Essentials (App Router)
type
Post
status
Published
date
Dec 17, 2025
slug
nextjs-002
summary
02 - NextJS Essentials (App Router)
tags
Next.js
category
编程学习
icon
password
😀
 

001 Module Introduction

002 Starting Setup

一、Next.js项目获取与准备

(一)项目获取渠道

  1. Code Sandbox在线项目
      • 优势:无需在本地系统安装任何软件,可直接在平台上跟随课程操作,降低了学习的环境搭建门槛。
      • 用途:适合暂时不想在本地配置环境,或希望快速上手跟随课程实践的学习者。
  1. 本地启动项目
      • 基础属性:本质是一个默认的Next.js项目,课程讲师对其进行了轻微整理(如清理冗余内容),作为课程该章节的起始项目。
      • 创建命令:通过npx create-next-app命令创建,与Next.js官方文档推荐的项目初始化方式一致,保证了项目的规范性。

(二)本地项目编辑与配置

  1. 使用的编辑器:Visual Studio Code(VS Code),讲师在整个课程中都会使用该编辑器,建议学习者统一工具以减少操作差异。
  1. 项目修改内容
      • 应用程序文件夹(app文件夹)内容调整,适配课程初始教学需求。
      • 默认页面显示内容更改,去除无关默认信息,突出后续教学重点。
      • 公共文件夹(public文件夹)中添加自定义logo,替换默认图标。
      • 覆盖eslintrc.json文件中的规则,统一代码检查标准,避免不必要的语法提示干扰。

二、本地项目环境搭建步骤

(一)终端导航与依赖安装

  1. 终端选择与导航
      • 可使用系统默认终端/命令提示符,手动导航到本地项目所在文件夹;也可使用VS Code集成终端,其会自动定位到当前打开的项目目录,操作更便捷。
  1. 依赖安装命令:运行npm install,确保项目所需的所有依赖包(如Next.js核心包、React相关包等)都已正确安装,Code Sandbox在线项目无需执行此步骤,平台已预先配置好依赖。

(二)开发服务器启动与项目预览

  1. 启动命令:运行npm run dev,启动Next.js开发服务器,该命令会启动本地开发环境,支持代码热更新(修改代码后页面自动刷新)。
  1. 预览方式
      • 服务器启动成功后,终端会显示可访问的预览地址(通常为http://localhost:3000)。
      • 在浏览器中输入该地址,即可看到讲师准备的Next.js基础初始项目界面,后续课程将基于此界面展开教学。
  1. 特殊说明:Code Sandbox在线项目无需手动启动开发服务器,平台已自动启动并提供预览窗口,直接在预览窗口查看项目效果即可。
 
🗒️
npx create-next-app@latest my-app cd my-app npm run dev

003 Understanding File-based Routing & React Server Components - 文件路由与 React 服务器组件

一、核心知识点详解

(一)Next.js 项目关键目录:app 文件夹

  1. 核心地位
    1. app 文件夹是现代 Next.js 项目中最重要的文件夹,用于定义网站的页面结构与路由规则,开发者需在此配置希望展示在网站上的所有页面。
  1. 核心作用
      • 统一管理页面相关文件,明确页面与文件路径的对应关系,简化路由配置流程。
      • 支持嵌套路由设计,可通过文件夹嵌套实现复杂的页面层级(如 /app/about/team 对应 /about/team 路由)。

(二)Next.js 保留文件名:page.js 与 layout.js

  1. page.js 的核心特性
      • 保留属性:page.js 是 Next.js 官方定义的保留文件名,与 layout.js 等文件共同构成页面渲染的基础。
      • 核心功能:告知 Next.js “该文件对应一个需渲染的页面”,是页面路由的“入口标识”。
      • 文件本质:本质是存储 React 组件函数的文件,符合 React 开发者熟悉的组件编写逻辑,但存在特殊处理机制(即服务器组件特性)。
  1. 其他保留文件(补充)
      • layout.js:用于定义页面共享布局(如导航栏、页脚),可嵌套使用,实现布局复用,减少重复代码。
      • 注意:保留文件名不可随意修改,否则 Next.js 无法识别对应的功能逻辑(如页面渲染、布局生效)。

(三)React 服务器组件(React Server Components, RSC)

  1. 定义与依赖
      • 服务器组件是一种无法仅通过 React 单独实现的组件类型,必须依赖 Next.js 的支持才能生效,是 Next.js 对 React 组件模型的扩展。
      • 外观特性:从代码编写层面看,与普通 React 组件无明显差异(同样是函数组件,返回 JSX),但执行环境和渲染流程完全不同。
  1. 核心特性:服务器端执行
      • 执行位置:组件函数在服务器端执行,而非客户端浏览器。
      • 验证案例(console.log 测试)
        • 操作:在服务器组件中添加 console.log("组件执行中")
        • 客户端结果:打开浏览器开发者工具的 JavaScript 控制台,刷新页面后无法看到该日志
        • 服务器端结果:打开启动 Next.js 开发服务器的终端(如 npm run dev 启动的终端),可清晰看到日志输出,直接证明组件在服务器端执行。
      • 原理:终端中的进程是 Next.js 开发服务器的运行进程,所有服务器组件的执行日志会直接输出到该进程的控制台。
  1. 渲染流程
    1. 服务器端:Next.js 识别 page.js 中的服务器组件,在服务器上执行该组件函数,生成对应的 JSX 代码。
    2. 传输阶段:服务器将生成的 JSX 代码通过网络发送到客户端浏览器。
    3. 客户端渲染:浏览器接收 JSX 代码后,直接进行渲染展示,最终形成用户看到的页面内容。

三、关键逻辑关联

概念
作用
与其他概念的关联
app 文件夹
管理页面与路由结构
包含 page.js、layout.js 等保留文件,是路由与组件的载体
page.js
标识页面入口,定义服务器组件
依赖 app 文件夹的目录结构,组件默认是服务器组件
React 服务器组件
在服务器端执行,生成 JSX 并传输到客户端
需通过 page.js 定义,依赖 Next.js 实现服务器端执行

四、学习要点与注意事项

  1. 学习重点
      • 理解 app 文件夹的路由映射规则(文件路径 = 页面路由),这是 Next.js 文件路由的核心逻辑。
      • 区分服务器组件与普通客户端组件的本质差异:核心是执行环境(服务器 vs 客户端),而非代码编写形式。
  1. 注意事项
      • 服务器组件中不能使用客户端特有的 API(如 window、document 对象),否则会因服务器端无该 API 导致报错。
      • 保留文件名不可自定义,若将 page.js 改为 page1.js,Next.js 会无法识别该页面,导致路由失效。

004 Adding Another Route via the File System

一、核心知识点梳理

(一)Next.js 页面渲染与文件基础

  1. 服务器组件工作流
      • Next.js 项目中存在“服务器组件”,这类组件会先在服务器端完成渲染,转换为 HTML 格式后,再发送到用户浏览器展示
      • 区别于传统 React 客户端渲染,服务器组件能减少客户端资源消耗,提升首屏加载速度
  1. 文件名的关键作用
      • 文件名直接决定文件是否会被 Next.js 识别为“页面”,只有符合规则的文件名(如 page.js),才能被框架处理为可访问的页面资源
      • 示例:根目录下的 page.js 会被默认作为网站首页(localhost:3000),这是 Next.js 约定式路由的核心体现

(二)Next.js 路由体系核心逻辑

  1. 与传统 React 路由的差异
      • 在纯 React 项目中,通常使用 react-router 库配置路由(需手动定义路由规则、关联组件)
      • Next.js (App Router 模式)不依赖第三方路由库,而是通过“文件系统”实现路由映射,即“文件夹/文件结构 = 路由路径”,简化路由配置流程
  1. App 目录的路由核心地位
      • 项目中的 app 目录是路由配置的核心目录,所有需要作为路由的路径,都需在 app 目录下通过“创建文件夹”的方式定义
      • 示例:若需实现 localhost:3000/about 路由,需在 app 目录下创建 about 文件夹,文件夹名称直接对应路由路径中的“about”片段

(三)添加新路由的完整步骤

  1. 步骤1:创建对应路由的文件夹
      • 需求:实现 localhost:3000/about 路由,需在 app 目录下新建 about 文件夹
      • 注意:仅创建文件夹无法实现路由访问,此时访问 localhost:3000/about 会触发 Next.js 默认 404 页面(框架未识别该路径为有效页面)
  1. 步骤2:在文件夹中创建 page.js 文件
      • page.js 是 Next.js 约定的“页面入口文件”,只有在路由文件夹(如 about)中添加该文件,框架才会将该路径识别为有效页面
      • 作用:page.js 中导出的组件,会作为对应路由(如 /about)的页面内容渲染,根目录 app/page.js 对应首页,app/about/page.js 对应 /about 页面
  1. 步骤3:编写 page.js 组件内容
      • 组件定义规则:在 page.js 中导出一个 React 组件函数(函数名可自定义,如 AboutPageIndex 等,不影响功能)
      • 组件内容要求:必须返回 JSX 代码,作为页面最终展示的内容
      • 示例代码:
      • 样式优化:可通过 JSX 内联样式、CSS Modules 等方式美化页面,示例中用 main 标签包裹内容并设置居中样式,确保页面布局规整
  1. 步骤4:验证路由效果
      • 保存文件后,启动 Next.js 开发服务器(npm run dev
      • 在浏览器访问 localhost:3000/about,即可看到 page.js 中定义的“关于我们”页面内容,证明路由配置成功

(四)关键注意事项

  1. page.js 的不可替代性
      • 不可用其他文件名(如 about.jsindex.js)替代 page.js,否则 Next.js 无法识别该文件为页面入口,会导致路由失效
  1. 路由嵌套的扩展逻辑
      • 若需实现多级路由(如 localhost:3000/about/team),可在 about 文件夹下再创建 team 文件夹,并在 team 文件夹中添加 page.js
      • 嵌套逻辑:app/about/team/page.js = localhost:3000/about/team,完全遵循“文件夹结构对应路由路径”的约定
  1. 404 页面的触发场景
      • 未在路由文件夹中添加 page.js 文件
      • 访问的路径在 app 目录下无对应的文件夹结构
      • 文件名不符合 Next.js 页面约定(如未使用 page.js

005 Navigating Between Pages - Wrong & Right Solution - Next.js 页面导航

一、页面导航的核心问题:单页应用特性保持

1.1 常规锚元素(<a>标签)的局限性

  • 功能表现:在起始页添加<a href="/about">关于我们</a>的锚元素,点击可实现页面跳转,但存在关键缺陷。
  • 缺陷特征:点击链接时,浏览器刷新图标会短暂变为十字形,这是浏览器从后端重新下载完整页面的明确信号。
  • 核心问题:该方式会导致应用脱离“单页应用(SPA)”模式——每次跳转都销毁当前页面、加载新页面,与React开发中常见的“客户端动态更新UI”特性相悖,也无法发挥Next.js的技术优势。

二、Next.js 导航的核心优势:兼顾服务端与客户端特性

2.1 双模式渲染逻辑

Next.js的核心价值之一是“融合服务端渲染(SSR)与客户端交互”,导航场景下具体表现为:
访问场景
渲染/处理方式
优势
首次访问(如手动输入URL)
服务器预渲染完整页面,直接返回给客户端
首屏加载快、利于SEO
页面内点击链接导航
保持单页应用状态,通过客户端JavaScript更新UI
无页面刷新、交互流畅

2.2 技术原理

即使是页面内导航,下一页的内容仍会在服务器预渲染,但不会触发全页面重新加载——预渲染后的内容会通过客户端JavaScript“注入”到当前页面,实现“无刷新更新视图”,同时保留服务端渲染的性能与SEO优势。

三、正确方案:使用Next.js 专用<Link>组件

3.1 组件基础使用

  • 导入方式:从Next.js内置模块导入,代码示例:
    • 关键属性:支持href(目标页面路径,同锚元素)、className(样式类)等常规属性,用法与锚元素接近,学习成本低。

    3.2 效果验证与优势

    • 无刷新验证:点击<Link>组件链接时,浏览器刷新图标始终保持不变(不会变为十字形),证明未触发全页面重新加载,始终处于单页应用环境中。
    • 核心优势
        1. 保持单页应用的流畅交互体验,避免页面跳转时的“白屏”或“闪烁”;
        1. 仍基于服务器预渲染内容,兼顾性能与SEO,不牺牲服务端渲染的核心价值;
        1. 符合Next.js“全栈框架”定位,统一服务端与客户端导航逻辑。

    3.3 使用场景限定

    • 适用场景:仅用于网站内部页面导航(如从首页跳转到“关于我们”“产品列表”等自身域名下的页面)。
    • 不适用场景:跳转至外部网站(如从自身项目跳转到B站、百度)时,仍需使用常规锚元素<a>标签。

    四、核心知识点总结

    对比维度
    常规锚元素(<a>标签)
    Next.js <Link>组件
    页面刷新
    触发全页面重新加载(后端下载新页面)
    无刷新,客户端更新UI
    单页应用特性
    破坏SPA模式
    保持SPA模式
    浏览器图标变化
    刷新图标短暂变十字形
    图标无变化
    技术依赖
    原生HTML特性
    Next.js内置组件(需导入next/link
    适用场景
    外部网站跳转
    内部页面导航
    核心原理
    基于HTTP请求重新获取完整页面
    服务端预渲染+客户端动态注入内容

    006 Working with Pages & Layouts - Next.js 15 中 Pages 与 Layouts

    一、核心概念:Page 与 Layout 的定义与分工

    1. 基础定位

    • Page(页面文件):以page.js命名,是 Next.js 中定义页面具体内容的文件,如首页内容、详情页数据展示等,每个路由对应一个page.js,是用户最终看到的“页面主体”。
    • Layout(布局文件):以layout.js命名,是定义页面“外壳”的特殊文件,负责包裹一个或多个页面的通用结构(如导航栏、页脚、公共样式容器等),相当于页面的“框架模板”。

    2. 关键规则

    • 必填性:每个 Next.js 项目(基于 App Router)至少需要一个根布局文件(位于app文件夹根目录),否则项目无法正常渲染。
    • 嵌套性:布局支持嵌套使用。例如在app/about文件夹中添加layout.js,该布局仅作用于about文件夹下的所有页面(包括嵌套子文件夹页面),不影响其他路由页面。
    • 保留文件名page.jslayout.js是 Next.js 的保留文件名,不可自定义(组件名称可自定义,但文件名称固定),框架会自动识别并解析这两类文件。

    二、根布局(Root Layout)的核心结构与作用

    1. 组件结构

    根布局文件需导出一个 React 组件,且必须包含以下核心元素:
    • children Prop:React 中的标准属性,用于“注入”当前活跃页面的内容(即对应路由的page.js组件),是 Layout 与 Page 关联的核心桥梁。
    • htmlbody标签:不同于普通 React 项目(通常不在组件中写这两个标签),Next.js 要求在根布局中定义网站的通用 HTML 骨架,确保服务端渲染(SSR)时生成完整的 HTML 结构。

    2. 元数据(Metadata)配置

    根布局中不直接渲染<head>标签(如标题、描述等元信息),而是通过导出metadata变量实现配置,该变量会作用于布局覆盖的所有页面:
    • 规则metadata是 Next.js 保留变量名,必须是对象类型,框架会自动将其转化为<head>标签内的元标签(如<title><meta name="description">)。
    • 作用范围:根布局的metadata对整个项目生效;嵌套布局的metadata仅对其覆盖的页面生效,且会覆盖根布局中相同字段的配置。

    三、Layout 与 Page 的协作机制

    1. 层级关系

    • Layout 是“包装者”:负责提供公共 UI 结构(如导航栏、页脚),定义页面的整体框架。
    • Page 是“内容提供者”:负责提供当前路由的独有内容(如首页的产品列表、详情页的商品信息),通过children Prop 注入到 Layout 中。

    2. 渲染逻辑

    app/about/page.js(关于页)和app/layout.js(根布局)为例:
    1. 访问/about路由时,Next.js 先解析根布局app/layout.js
    1. 找到/about路由对应的page.js组件;
    1. page.js组件作为children传入根布局的RootLayout组件;
    1. 最终渲染结果:根布局的 HTML 骨架 + 关于页的内容。

    3. 嵌套布局的协作

    app/about文件夹下有独立layout.js(嵌套布局),则渲染逻辑变为:
    • 根布局(app/layout.js)→ 关于页嵌套布局(app/about/layout.js)→ 关于页内容(app/about/page.js);
    • 每个层级的children依次注入下一层级的内容,形成“套娃”式渲染。

    四、关键注意事项与常见问题

    1. 根布局不可省略:即使项目只有一个页面,也必须在app文件夹根目录创建layout.js,否则会报错。
    1. children不可遗漏:布局组件必须渲染children Prop,否则页面内容无法显示。
    1. 元数据冲突解决:嵌套布局的metadata会覆盖根布局的同名字段,若需合并元数据(如根布局定义关键词,嵌套布局定义标题),可使用metadata.basemetadata.extend(Next.js 15 新特性)。
    1. 静态与动态布局:若布局中包含动态数据(如用户信息),需标记为“动态布局”(通过dynamic: true配置),否则会被静态缓存导致数据不更新。

    007 Reserved File Names, Custom Components & How To Organize A NextJS Project - Next.js 项目核心知识点笔记(保留文件名、组件创建与项目组织)

    一、应用目录中的特殊文件

    1. 全局CSS文件
        • 作用:预设基础CSS样式,确保在所有加载页面中生效
        • 使用方式:需导入到layout.js文件,这是Next.js与Vanilla React项目通用的CSS导入方案
    1. 图标文件(icon.png)
        • 特殊规则:在app文件夹中添加名为icon的图片文件(如icon.png),Next.js会自动将其设为浏览器标签页书签图标
        • 优势:无需在layout.js中额外配置,简化图标设置流程

    二、Next.js 保留文件名及功能

    保留文件名
    核心作用
    必要性
    page.js
    定义路由对应的页面内容,是设置路由的核心文件
    必需,无此文件则路由无法访问
    layout.js
    定义路由布局(如公共头部、导航),作用于该目录及子目录所有页面
    必需,构建统一页面框架
    icon.png
    自动作为浏览器书签图标
    可选,按需添加
    • 关键特性:app文件夹通过“文件夹即路由”规则定义路由结构,保留文件名通过固定命名解锁对应功能,无需额外配置

    三、自定义React组件创建与使用

    1. 组件创建规则
        • 文件扩展名:支持.js(Next.js默认)或.jsx,可按需选择
        • 命名建议:因Next.js核心文件(如page.js)均为小写,建议组件文件也用小写(如header.js),避免与系统文件混淆
        • 组件结构:导出常规React函数组件,示例如下:
    1. 组件使用方式
        • 导入路径:在需要使用的文件(如page.js)中导入组件,语法与纯React一致
        • 注意事项:自定义组件不会被Next.js自动识别为页面/布局,需手动导入才能渲染

    四、组件存储与路径优化

    1. 存储位置选择
      1. 存储位置
        配置方式
        适用场景
        app文件夹内(如app/components
        导入路径需指定相对路径,例:import Header from './components/header'
        组件与路由强相关,仅在特定路由内使用
        app文件夹外(如项目根目录components
        利用Next.js路径别名,用@引用项目根目录,例:import Header from '@/components/header'(需jsconfig.json配置别名)
        通用组件(如头部、底部),全项目复用
    • 官方建议:Next.js文档提供多种项目结构方案,可根据团队习惯选择;讲师推荐将组件放在app外,保持app文件夹仅负责路由相关逻辑,降低耦合
    1. 路由访问规则app文件夹内的非路由文件夹(如components),若未包含page.js,访问该文件夹路径(如/components)会返回404错误,Next.js仅识别含page.js的文件夹为有效路由

    五、关键原则总结

    1. 路由核心:page.js是路由生效的必要条件,layout.js是统一布局的核心
    1. 组件复用:自定义组件需手动导入,存储位置需结合复用范围选择,利用路径别名简化导入
    1. 项目规范:建议区分“路由相关文件”(app内)与“通用组件”(app外),保持项目结构清晰

    008 NextJS保留文件名

    重要提示:这些文件名仅在文件夹(或任何子文件夹)内创建时保留。在文件夹之外,这些文件名不会被特别处理。app/app/
    以下是NextJS中保留文件名的列表——你当然会在本节中了解重要的文件名:
    • page.js=> 创建新页面(例如,创建页面)app/about/page.js<your-domain>/about
    • layout.js=> 创建一个新布局,包裹兄弟页面和嵌套页面
    • not-found.js=> “未找到”错误的备用页面(由兄弟页面或嵌套页面或布局抛出)
    • error.js=> 其他错误的备用页面(由兄弟页面、嵌套页面或布局抛出)
    • loading.js=> 在兄弟页面或嵌套页面(或布局)获取数据时显示的备援页面
    • route.js=> 允许您创建API路由(即一个不返回JSX代码而是返回数据的页面,例如JSON格式)
    你还可以在官方文档中找到包含所有支持文件名和详细说明的列表:https://nextjs.org/docs/app/api-reference/file-conventions

    009 Configuring Dynamic Routes & Using Route Parameters - Next.js 15 动态路由

    一、动态路由的核心定位与应用场景

    1. 解决的核心问题

    • 常规路由缺陷:在博客、商品详情等场景中,若为每个内容(如每篇博客、每个商品)单独创建文件夹和页面文件,会导致文件数量暴增、维护成本高、扩展性差(新增内容需手动新建文件)。
    • 动态路由优势:仅需定义一套路由规则,即可自动匹配并渲染不同内容的页面,实现“一次配置,多页复用”,大幅提升开发效率与项目可维护性。

    2. 典型应用场景

    • 内容展示类:博客文章详情页(/blog/[文章ID])、新闻详情页(/news/[新闻ID]
    • 电商类:商品详情页(/product/[商品ID])、分类商品页(/category/[分类ID]
    • 用户相关:用户个人主页(/user/[用户ID]

    二、动态路由的创建步骤(基于 App Router)

    Next.js 15 动态路由的创建依赖于特定命名规则的文件夹,核心是通过“方括号”标识动态占位符,具体步骤如下:

    1. 基础目录结构(以博客应用为例)

    假设项目已创建 app 目录(Next.js 13+ App Router 核心目录),需先搭建如下基础结构:

    2. 关键操作:创建动态路由目录

    • 命名规则:在父路由文件夹(如 blog)下,新建文件夹并以 [占位符名称] 命名(方括号不可省略),占位符名称可自定义(如 [slug][postId][id]),需与后续获取参数时的名称对应。
    • 作用:方括号是 Next.js 识别动态路由的“标识”,告知框架该路径段的值是动态变化的(如 blog/post-1blog/nextjs-guide 均会匹配 [slug] 路由)。

    3. 新建动态页面文件

    [slug] 动态目录下,必须创建 page.js 文件(App Router 中页面的入口文件),该文件导出的组件将作为动态页面的渲染内容。

    三、动态页面的基础搭建与跳转

    动态路由需配合“列表页跳转”和“详情页渲染”实现完整功能,以下为具体代码示例(基于 React 与 Next.js 内置组件):

    1. 博客列表页(app/blog/page.js

    功能:展示所有博客的链接,点击链接跳转到对应详情页,需使用 Next.js 内置的 Link 组件实现路由跳转(替代原生 <a> 标签,避免页面刷新)。

    2. 博客详情页(app/blog/[slug]/page.js

    功能:初始可先渲染静态内容,后续结合路由参数实现“动态渲染不同博客内容”,核心是通过 params 获取动态路由的值。

    3. 路由跳转效果

    • 访问 http://localhost:3000/blog(列表页),可看到两篇博客的链接;
    • 点击“Next.js 15 动态路由入门”,URL 会变为 http://localhost:3000/blog/post-1,并渲染 [slug] 目录下的 page.js 组件;
    • 点击另一篇链接,URL 变为 http://localhost:3000/blog/post-2,页面组件复用,仅需后续通过参数区分内容。

    四、动态路由的核心:获取路由参数(params

    Next.js 会自动为动态页面组件传递一个 props 对象,其中的 params 属性包含了动态路由占位符与对应值的映射,通过解构即可获取参数,实现“动态渲染内容”。

    1. 获取参数的代码示例(app/blog/[slug]/page.js

    2. params 的关键特性

    • 自动生成:无需手动配置,Next.js 根据动态路由目录的占位符名称自动生成 params 的 key;
    • 与 URL 同步:URL 中动态路径段的值变化时,params 对应的 value 也会同步更新,页面重新渲染;
    • 类型安全(可选):若使用 TypeScript,可定义 params 的类型,避免参数名称错误(如 type Params = { slug: string })。

    五、相关扩展功能(后续实践方向)

    视频中提及 Next.js 还有其他与路由配套的核心功能,需在实际项目(如餐饮应用)中进一步实践:
    1. 自定义 404 页面:当访问的动态路由参数不存在(如 blog/invalid-slug)时,可创建 not-found.js 文件,自定义 404 页面内容;
    1. 错误处理:通过 error.js 文件捕获动态页面渲染中的错误(如接口请求失败),实现优雅的错误提示;
    1. 数据获取与修改:结合 Next.js 服务器组件(Server Components),在动态页面中实现服务端数据获取(如从数据库查询博客内容),或通过 action 实现表单提交(如修改博客)。

    010 Onwards to the Main Project The Foodies App

    011 Exercise Your Task
    012 Exercise Solution

    Next.js 15 路由配置与导航

    一、核心知识背景

    1. Next.js 路由机制:基于文件系统的路由(File-based Routing),通过在app文件夹(应用文件夹)中创建文件夹和page.js文件定义路由,文件夹名称对应 URL 中的路径段(path segment),page.js文件导出的 React 组件即为该路由的页面内容
    1. 路由类型:包含静态路由(固定路径)、嵌套路由(父子路径)、动态路由(路径含变量)三类,本次实操围绕这三类路由展开

    二、静态路由配置(核心步骤)

    1. 基础原则

    • 每个静态路由需在app文件夹下创建对应名称的文件夹(路径段)
    • 文件夹内必须创建page.js文件,且需导出一个 React 功能组件(组件名称自定义,但导出是必需步骤)
    • 访问路径:域名/文件夹名称,例如localhost:3000/餐点

    2. 实操案例

    案例1:餐食路由(/餐点

    1. 路径创建:在app文件夹内新建餐点文件夹
    1. 创建页面文件:在餐点文件夹内新建page.js
    1. 编写组件代码:
    1. 访问验证:启动开发服务器后,访问localhost:3000/餐点,页面显示“餐点页面”文本

    案例2:社区路由(/社区

    1. 路径创建:在app文件夹内新建社区文件夹(与餐点文件夹同级)
    1. 创建页面文件:在社区文件夹内新建page.js
    1. 编写组件代码:
    1. 访问验证:访问localhost:3000/社区,页面显示“社区”文本

    三、嵌套路由配置(子路由)

    1. 基础原则

    • 嵌套路由是在父路由文件夹内创建子文件夹(子路径段),形成“父路由/子路由”的访问路径
    • 子文件夹内同样需创建page.js并导出 React 组件,继承父路由的路径层级

    2. 实操案例:分享餐食路由(/餐点/share

    1. 路径创建:在app/餐点文件夹内新建share子文件夹
    1. 创建页面文件:在share文件夹内新建page.js
    1. 编写组件代码:
    1. 访问验证:访问localhost:3000/餐点/share,页面显示“分享餐”文本

    四、动态路由配置(含变量路径)

    1. 基础原则

    • 用于处理路径中需动态变化的部分(如商品ID、用户ID等),通过“方括号包裹文件夹名称”定义动态路径段(例如[餐品slug]
    • 动态路由优先级低于静态路由:若存在/餐点/share(静态)和/餐点/[xxx](动态),访问/餐点/share时优先匹配静态路由,访问/餐点/123则匹配动态路由
    • 访问路径:域名/父路由/动态值,例如localhost:3000/餐点/汉堡123

    2. 实操案例:餐食详情路由(/餐点/[餐品slug]

    1. 路径创建:在app/餐点文件夹内新建[餐品slug]文件夹(方括号不可省略)
    1. 创建页面文件:在[餐品slug]文件夹内新建page.js
    1. 编写组件代码:
    1. 访问验证:
        • 访问localhost:3000/餐点/汉堡123,页面显示“餐食详情”文本
        • 访问localhost:3000/餐点/披萨456,同样匹配该动态路由,显示相同页面(后续可通过获取动态参数实现差异化内容)

    五、页面导航与 Link 组件

    1. Link 组件核心作用

    • 替代传统 HTML 的<a>标签,由 Next.js 提供(需从next/link导入)
    • 优势:保持单页应用(SPA)特性,避免页面全量刷新,提升导航性能
    • 核心属性:href,指定跳转的路由路径

    2. 实操案例:在首页添加导航链接

    1. 编辑首页文件(app/page.js):
    1. 功能验证:点击各链接,可无刷新跳转至对应路由页面,URL 同步更新

    六、项目运行与环境配置

    1. 本地开发环境启动步骤

    1. 安装依赖:在项目根目录执行命令npm install(首次启动或依赖更新时需执行)
    1. 启动开发服务器:执行命令npm run dev(具体命令需参考项目package.json中的脚本配置)
    1. 访问地址:默认地址为localhost:3000,若端口被占用,终端会显示实际可用地址

    2. 关键注意事项

    • 开发服务器需保持运行状态,修改代码后会自动热更新,无需手动重启
    • 若页面未正常显示,需检查:
        1. 文件夹路径是否在app文件夹下
        1. page.js文件是否存在且正确导出组件
        1. 路由路径与Link组件的href属性是否匹配(区分大小写,路径需完全一致)

    七、路由匹配优先级总结

    路由类型
    定义方式
    访问路径示例
    优先级
    静态路由(顶级)
    app/社区
    localhost:3000/社区
    最高
    静态嵌套路由
    app/餐点/share
    localhost:3000/餐点/share
    中高
    动态路由
    app/餐点/[餐品slug]
    localhost:3000/餐点/汉堡123
    最低
    规则:Next.js 优先匹配路径更具体的路由,动态路由仅在无匹配的静态路由时生效
     

    013 Revisiting The Concept Of Layouts

    一、布局基础定位

    1. 本质与作用:布局是Next.js中页面的“包装器”,本质为React组件,用于统一包裹页面内容,实现导航栏、Logo、标题等公共UI的复用,避免在每个页面重复编写相同代码。
    1. 工作文件:核心配置文件为layout.js,根布局(项目根目录下的layout.js)会作用于所有页面,是构建网站公共结构的核心文件。

    二、嵌套布局关键特性

    1. 定义与范围
        • 可创建“嵌套布局”(如meals文件夹下的layout.js),仅对该文件夹下的相关页面生效,不影响其他路由页面。
        • 嵌套布局自身会被根布局包裹,即根布局始终处于“最外层”,所有页面(包括嵌套布局对应的页面)都会继承根布局的公共UI。
    1. 核心实现:children Prop
        • 布局组件需通过React的children特殊Prop,渲染嵌套的页面或子布局内容(类似“容器与内容”的关系)。
        • 示例:在meals布局中,通过<>{/* 布局自身内容 */}{children}</>children会自动填充该布局下的页面内容。
    1. 自动渲染机制:无需在JSX中手动引用布局组件,Next.js会根据文件目录结构自动将布局包裹对应页面(根布局包裹所有页面,嵌套布局包裹对应子页面)。
    1. 效果差异:嵌套布局仅在其对应路由页面生效(如meals布局仅在/meals相关页面显示),在其他页面(如首页)不可见,但根布局的内容(如全局SVG图标)在所有页面均可见。

    014 Adding a Custom Component To A Layout - 自定义组件添加到布局

    一、核心目标

    将页面共享的头部(含logo、导航)拆分为独立自定义组件,实现代码简洁化与复用,确保头部在所有页面生效。

    二、组件文件规划

    1. 文件命名与位置
        • 新建组件文件:命名为main-dash-header.js(命名可自定义)。
        • 存放路径建议:推荐放在app文件夹外,使app文件夹仅负责路由管理;也可放在app/components等目录,路径灵活无强制要求(JavaScript对非页面/布局类组件的文件后缀无严格限制)。
    1. 参考依据:Next.js官方文档提供多种项目结构方案,视频采用“组件与路由分离”的个人偏好方案。

    三、自定义头部组件开发

    1. 组件基础结构
        • 导出React组件:在main-dash-header.js中导出名为MainHeader的常规React组件。
        • 返回根元素:组件返回<header>标签,内部包含“logo区域”和“导航区域”,作为所有页面的共享头部。
    1. Logo区域实现(含可点击跳转)
        • 引入Next.js工具:使用next/linkLink组件包裹logo,实现点击跳转功能,跳转路径设为项目起始页(如/)。
        • 图片导入与使用:从项目根目录的assets文件夹导入logo.png(需先配置项目别名@指向根目录),语法为import logo from '@/assets/logo.png';在<img>标签中,需通过src={logo.src}获取图片路径(Next.js中导入的图片是对象,路径存于src属性),并添加alt文本“一个盘子上的食物”提升可访问性。
        • 搭配文本:在图片旁添加应用名称文本“next level 食品”,构成完整logo。
    1. 导航区域实现
        • 结构层级:在<header>内添加<nav>标签,嵌套<ul>无序列表,列表项<li>中包裹Link组件。
        • 导航链接配置:两个核心链接,分别是指向“食谱浏览页”的/meals路径,和指向“食客社区页”的/community路径。

    四、组件集成到根布局

    1. 导入组件:在根布局文件(如app/layout.js)中,从组件存放路径导入MainHeader组件,示例语法:import MainHeader from '@/components/main-dash-header'(需匹配实际文件路径)。
    1. 使用组件:在布局的children插槽(页面内容渲染区域)上方插入<MainHeader />,确保头部在所有页面内容之上显示。
    1. 效果验证:保存所有文件后刷新页面,头部组件会在所有页面生效(初始无样式,需后续优化,但功能正常)。

    015 Styling NextJS Project Your Options & Using CSS Modules - Next.js 项目样式化方案(CSS Modules 重点)

    一、Next.js 项目样式化整体说明

    Next.js 项目的样式化逻辑与 React 项目类似,提供多种可选方案,核心目标是实现组件样式的灵活控制(全局复用或局部隔离),需根据项目需求(如复杂度、团队协作、样式复用性)选择合适方案。

    二、核心样式化方案详解

    (一)方案1:全局 CSS 文件

    1. 核心特点:样式作用于整个项目所有页面和组件,无局部隔离效果,适合定义全局通用样式(如页面重置、基础字体、通用布局)。
    1. 使用规则
        • 文件命名:无强制规范,可自定义(如 global.cssstyles.css 等),建议遵循项目统一命名规范(如前缀 global-)。
        • 导入方式:必须在 根目录的 layout.js 文件 中导入,示例代码:
          • 生效范围:导入后,文件中定义的所有样式(如 body 样式、.container 类等)可在项目所有页面、组件中直接使用,无需额外导入。
      1. 优缺点
          • 优点:配置简单,适合快速实现全局样式统一;无需额外学习成本,直接编写标准 CSS 即可。
          • 缺点:样式无隔离,易出现“类名冲突”(如不同组件使用相同类名时,后定义的样式会覆盖前者);大型项目中样式维护难度高,难以定位样式来源。

      (二)方案2:Tailwind CSS(流行但课程暂不使用)

      1. 核心特点:轻量级 CSS 框架,基于“实用类优先”理念(如 flextext-red-500p-4),通过组合多个小类快速实现样式,无需编写大量自定义 CSS。
      1. Next.js 集成优势
          • 官方支持:Tailwind 官网提供专门的 Next.js 集成教程,步骤清晰;
          • 初始化便捷:使用 create-next-app 创建 Next.js 项目时,会自动询问是否集成 Tailwind CSS,选择后可自动完成配置(无需手动修改 tailwind.config.jspostcss.config.js)。
      1. 课程不使用的原因
          • 避免代码冗余:使用 Tailwind 需在 JSX 标签中添加大量实用类(如 <div className="flex justify-between items-center p-4 bg-white rounded-lg shadow-md">),会让代码显得冗长,影响核心逻辑(Next.js/React)的展示;
          • 聚焦核心目标:课程重点是讲解 Next.js 和 React 的核心功能(如路由、数据获取、组件通信),而非样式框架的使用,避免分散学习注意力。

      (三)方案3:CSS Modules(课程重点,Next.js 原生支持)

      1. 核心原理:基于标准 CSS,通过“特殊文件命名”让样式仅作用于指定组件,实现“局部样式隔离”。Next.js 会在构建/开发过程中自动处理这类文件,为 CSS 类名添加唯一哈希值(如 .logo 会被编译为 .logo_abc123),彻底避免类名冲突。
      1. 关键特性
          • 原生支持:无需安装额外依赖(如 css-loaderstyle-loader),Next.js 内置处理能力;
          • 局部隔离:样式仅作用于导入该文件的组件,不会污染其他组件;
          • 类型安全(可选):若项目使用 TypeScript,可通过 .module.css.d.ts 生成类型声明,避免写错类名(IDE 会提示错误)。
      1. 完整使用步骤(以“MainHeader 组件”为例)
          • 步骤1:创建 CSS Modules 文件
            • 文件命名规则:必须以 .module.css 结尾(如 MainHeader.module.css),与组件 JS/TS 文件同级存放(建议组件名与 CSS 文件名保持一致,便于维护);
            • 编写样式:按标准 CSS 语法定义类,示例:
            • 步骤2:在组件中导入 CSS Modules 文件
              • 导入方式:通过 import 语句导入,接收一个对象(通常命名为 classes,可自定义),该对象包含 CSS 文件中所有类名的映射;
              • 示例代码:
              • 步骤3:验证样式隔离效果
                • 即使其他组件(如 Footer.jsx)也定义 .header.logo 类,由于 CSS Modules 会生成唯一哈希值,两者样式互不影响;
                • 保存文件后,Next.js 开发服务器会自动刷新,页面中 MainHeader 组件会应用指定样式(如深色背景、白色文字、导航栏布局),其他组件样式不受干扰。
          1. 注意事项
              • 不要在根 layout.js 中导入 CSS Modules 文件:若在根布局导入,会导致样式全局生效,失去局部隔离的意义;
              • 类名使用规则:CSS Modules 中不支持“嵌套类名”(如 .header .logo)的直接引用,需通过“父类 + 子类”的方式在 JSX 中组合(如上述示例中,.nav a 的样式会自动作用于 nav 内部的 a 标签,无需额外处理);
              • 动态类名:若需根据条件切换类名,可使用模板字符串或逻辑判断,示例:

            三、三种方案对比与选择建议

            方案
            核心优势
            核心劣势
            适用场景
            全局 CSS 文件
            配置简单、全局复用
            类名冲突、维护难
            小型项目、全局基础样式(如重置样式、字体)
            Tailwind CSS
            开发效率高、样式统一
            代码冗余、需记实用类
            中大型项目、追求快速迭代、团队风格统一
            CSS Modules
            局部隔离、无类名冲突
            需额外创建文件、配置步骤多
            中大型项目、组件复用率高、避免样式污染
            选择建议:实际开发中可组合使用(如“全局 CSS 定义基础样式 + CSS Modules 实现组件局部样式 + 少量 Tailwind 类快速调整细节”),平衡开发效率与代码可维护性。

            016 Optimizing Images with the NextJS Image Component - Image组件优化图片

            一、Next.js Image组件核心优势

            1. 自动懒加载:无需额外配置,组件会在幕后实现懒加载逻辑,仅当图片进入页面可视区域时才加载,减少初始页面加载资源,提升页面加载速度。
            1. 简化响应式图片设置:省去手动编写复杂响应式图片代码的步骤,组件可自动适配不同视口和设备,降低开发难度。
            1. 自动适配最佳文件格式:能根据用户使用的浏览器,自动选择最适合的图片文件格式(如Chrome浏览器下优先使用webp格式)。webp格式相比png格式,在保证图片质量的前提下,文件体积更小,传输效率更高。

            二、Image组件基础使用步骤

            1. 导入组件:从next/image库中导入Image元素,这是使用该组件的前提,代码示例:import Image from 'next/image'
            1. 设置图片源:不能像常规img元素那样仅设置src属性值,需将图片源设置为导入的图片整体对象。因为在Next.js中,导入图片时会生成一个包含图片大小、格式等有用信息的对象,这些信息是组件实现优化显示的关键,代码示例(假设导入的图片名为sampleImage):<Image src={sampleImage} alt="示例图片" />
            1. 基础效果验证:完成上述设置后刷新页面,图片在视觉上与使用常规img元素时无差异,但实际已通过Image组件实现优化。

            三、组件自动生成的优化属性及作用

            属性
            作用
            loading="lazy"
            开启懒加载功能,确保图片仅在进入可视区域时才加载,避免初始加载过多资源拖慢页面速度
            自动推断的widthheight
            根据导入图片的原始信息,自动填充宽度和高度属性,若有特殊需求,也可手动覆盖这些值
            srcset
            生成包含不同尺寸图片地址的集合,浏览器会根据当前设备的视口大小,自动选择合适尺寸的图片加载,既保证图片显示效果,又避免加载过大尺寸图片造成资源浪费

            四、priority属性的使用场景与效果

            1. 使用场景:适用于页面加载时始终处于可视区域的图片(如页面顶部的Banner图、首屏关键图片)。这类图片若使用懒加载,不仅无法节省资源,还可能因加载延迟导致内容偏移或闪烁。
            1. 添加方法:在Image组件中直接添加priority属性,代码示例:<Image src={sampleImage} alt="首屏图片" priority />
            1. 效果:添加该属性后,页面显示效果无变化,但会告知Next.js和浏览器优先加载该图片,提升图片加载速度,同时消除控制台中因未设置该属性而出现的警告信息。

            五、扩展探索方向

            1. 第三方图片加载配置:当需要从第三方网站加载图片时,需额外配置图片域名白名单(在next.config.js中通过images.domains配置),确保组件能正常加载外部图片。
            1. 高级尺寸控制:可通过widthheight属性手动指定图片尺寸,或结合fill属性与CSS样式,实现图片在容器内的自适应填充(需注意配合objectFit属性控制图片缩放方式)。
            1. 图片加载状态自定义:利用placeholder属性(可选值为blurempty),为图片添加加载占位效果(如模糊占位图),提升用户体验,其中blur占位图需配合blurDataURL提供模糊图片的Base64编码。
             

            017 Using More Custom Components

            一、自定义组件提取与优化

            1. 目标:拆分复用组件,提升项目可维护性

            将根布局中“头部背景”相关代码抽离为独立组件,同时结合CSS Modules优化样式管理,最终规整组件文件结构,便于后续开发与导航。

            2. 步骤1:创建“主要头部背景”自定义组件

            (1)新建组件文件

            在项目的components文件夹中,创建名为main-header-background.js的文件(命名遵循“功能+组件类型”原则,便于识别)。

            (2)编写组件逻辑

            • 从根布局(如layout.js)中,复制与“头部背景”相关的DOM结构代码(如包含背景样式的divsvg等元素)。
            • main-header-background.js中,导出一个名为“主要头部背景”的React组件,组件返回值为上述复制的DOM结构代码。

            3. 步骤2:CSS样式迁移至CSS Modules(避免样式冲突)

            (1)新建CSS Modules文件

            main-header-background.js文件同级目录下,创建main-header-background.module.css文件(CSS Modules文件命名需与对应组件文件一致,后缀为.module.css)。

            (2)迁移并调整样式代码

            • 从项目全局CSS文件(如globals.css)中,剪切与“头部背景”相关的样式:
              • header-background类的样式(如背景色、尺寸、定位等);
              • 与头部背景关联的svg选择器样式(如svg的填充色、大小等)。
            • 调整svg选择器:由于CSS Modules作用域隔离,需将原svg选择器修改为“针对header-background类内部的svg”,确保样式仅作用于当前组件内的svg元素,避免影响其他组件。

            (3)在组件中导入并使用CSS Modules类

            • main-header-background.js中,从main-header-background.module.css导入样式对象(通常命名为styles)。
            • 替换组件中的className:由于header-background是CSS类名(含连字符),直接作为JavaScript对象属性会报错,需使用方括号表示法访问样式对象的属性。

            4. 步骤3:调整组件使用位置(符合逻辑关联)

            (1)移除根布局中的组件引用

            删除根布局文件(如layout.js)中对“主要头部背景”组件的导入语句和使用代码,避免组件层级逻辑混乱。

            (2)在“主头部组件”中引入

            • 找到项目中的“主头部组件”文件(如main-header.js),导入“主要头部背景”组件。
            • 通过React片段(<>...</>),将“主要头部背景”组件作为“主头部组件”的兄弟元素渲染,确保页面结构逻辑合理(头部背景与头部功能强关联,应归为同一层级)。

            5. 步骤4:优化组件文件结构(便于管理)

            (1)创建组件子文件夹

            components文件夹内,新建名为“主要-标题”(或main-header,命名与功能匹配)的子文件夹,用于集中存放所有与“主头部”相关的文件。

            (2)移动文件并更新导入路径

            • main-header.jsmain-header-background.jsmain-header-background.module.css等与“主头部”相关的文件,移动到“主要-标题”子文件夹中。
            • 检查并更新项目中所有引用这些文件的导入路径(部分开发工具如VS Code会自动更新,需手动验证确保路径正确),避免因文件移动导致模块导入失败。

            三、关键原理与注意事项

            1. CSS Modules核心作用

            • 作用域隔离:CSS Modules会将类名编译为唯一哈希值(如header-background编译为header-background__abc123),避免不同组件间的样式冲突,尤其适合多人协作或大型项目。
            • 命名规范:CSS类名推荐使用“kebab-case”(连字符命名),配合JavaScript方括号表示法访问,兼顾语义化与语法正确性。

            2. 组件拆分原则

            • 单一职责:一个组件只负责一个功能(如“主要头部背景”仅处理背景渲染,不包含导航、登录等其他逻辑),便于后续维护和复用。
            • 逻辑关联:组件存放位置应与功能关联强相关(如头部背景与主头部组件放在同一子文件夹),避免文件散放导致查找困难。

            3. 路径更新验证

            文件移动后,需通过运行项目、查看控制台报错(如“Module not found”),逐一确认导入路径是否正确,确保页面功能正常运行(无样式丢失、组件不渲染等问题)。
             

            018 Populating The Starting Page Content - 起始页面内容填充

            一、核心操作文件定位

            1. 目标文件:必须操作应用程序文件夹内的主路由页面js文件,而非任何嵌套的页面js文件。
            1. 定位依据:该文件对应项目的“起始页面”——即访问项目基础地址(无后续路径片段)时,默认渲染的页面,是用户首次打开应用看到的核心入口。

            二、页面结构与语法规则

            1. JSX语法限制:兄弟JSX元素(如相邻的<h1><div>)不能直接并列,必须用片段(Fragment) 包裹(如<>...</><Fragment>`...</Fragment>),否则会触发语法错误,导致页面无法正常渲染。
            1. 页面内容分层规划
                • 第一层(页面特定标题):非导航栏标题,用于介绍当前页面功能(如“Foodies 首页 - 探索全球美食”),需单独设计样式以区分导航标题。
                • 第二层(核心内容区):包含两大模块,分别是“图片幻灯片”和“营销与功能引导区”,需按顺序排列并通过容器div划分区域。

            三、样式管理:CSS Modules 应用

            1. 样式文件基础:使用配套的page.module.css文件,该文件是Next.js项目中页面级样式的标准配置,支持样式作用域隔离(避免样式污染)。
            1. 样式使用步骤
                • 第一步:从page.module.css文件中导入样式类(如import styles from './page.module.css')。
                • 第二步:为页面元素绑定类名,实现精准样式控制,示例如下:
                  • 为页面标题容器添加定位类:<header className={styles.pageHeader}>(假设pageHeader在CSS模块中定义了margin、padding等定位样式)。
                  • 为幻灯片容器添加标识类:<div className={styles.slideshow}>(后续将基于该类实现图片自动切换逻辑)。
            1. 关键特性:Next.js将页面文件(如page.js)视为特殊组件,因此支持像普通React组件一样使用CSS Modules,无需额外配置。

            四、页面元素设计与实现

            (一)图片幻灯片区域

            1. 容器定义:创建className="slideshow"的div,作为幻灯片的外层容器,后续将在此容器内嵌入自动切换的食品类图片(如汉堡、沙拉、甜点等,用于视觉吸引)。
            1. 后续规划:本课时仅完成容器搭建,幻灯片的图片加载、自动切换逻辑将在后续课程中实现。

            (二)Hero 营销引导区域

            1. 容器结构:在幻灯片容器下方创建新div,内部嵌套两个子div,其中第一个子div绑定hero类(className={styles.hero}),用于承载营销文本和功能链接。
            1. 内容填充
                • 标题(h1):使用吸引用户的文案,如“Next Level Food for Next Level Foodies”(为美食爱好者打造的进阶体验),突出项目定位。
                • 描述段落(p):补充核心价值,如“品尝和分享来自世界各地的食物”,清晰传达页面功能。
                • 功能链接(Link组件)
                  • 链接1:文本“加入社区”,指向/community路由(社区页面),需提前导入Next.js的Link组件(import Link from 'next/link'),示例:<Link href="/community">加入社区</Link>
                  • 链接2:文本“探索餐点”,指向餐点列表页面(如/meals),实现核心功能引导,示例:<Link href="/meals">探索餐点</Link>

            (三)主要区域:占位符文本使用

            1. 文本来源:占位符文本已包含在配套的page.js文件中(与当前演示文件一致,且新增了主要区域的额外内容)。
            1. 使用方式
                • 方式1:仅复制占位符文本片段,粘贴到主要区域的容器div中(适合仅需补充基础内容的场景)。
                • 方式2:直接复制整个配套page.js文件内容,替换本地文件(适合快速同步演示进度,避免手动编写遗漏)。

            五、效果验证与后续任务

            1. 当前效果:完成上述操作后,页面将呈现“标题+营销引导+占位文本”的基础结构,视觉效果优于初始空白页面,且各区域样式隔离、布局规整。
            1. 后续核心任务:在slideshow容器中实现图片幻灯片功能,包括:
                • 加载多张食品图片(需处理图片尺寸适配,可结合Next.js的Image组件优化加载速度)。
                • 实现图片自动切换逻辑(如使用setInterval定时切换图片,或集成第三方轮播库如swiper)。

            019 Preparing an Image Slideshow - 图片幻灯片组件开发

            一、核心目标

            在 Next.js 15 与 React 项目中,开发一个能自动浏览 assets 文件夹内食物图像的图片幻灯片组件,同时保证项目文件结构简洁、代码逻辑清晰。

            二、组件开发步骤

            (一)创建文件结构

            1. 新建子文件夹:在项目根组件文件夹中,新增名为 images 的子文件夹,用于存放幻灯片相关组件文件,目的是分类管理文件,避免根目录文件杂乱。
            1. 创建核心文件
                • images 子文件夹内,创建 image-slideshow.js 文件,作为幻灯片组件的核心逻辑文件。
                • 同时创建 image-slideshow.module.css 文件,用于为幻灯片组件编写样式,且需将该 CSS 文件放在 image-slideshow.js 文件同级目录下,确保样式文件能被正确引入。
            1. 文件内容复用:可直接将课程提供的 image-slideshow.js 文件内容粘贴到自己创建的同名文件中,减少重复编码工作,保证代码基础正确性。

            (二)image-slideshow.js 代码逻辑解析

            1. 基础结构与图像渲染

            • 核心标记输出:文件输出一个基础 div 容器,在该容器内部,通过循环遍历 images 数组,为数组中的每一张图像生成一个 Next.js 图像组件(next/image),实现多图像的批量渲染。
            • 图像来源与配置
              • 图像素材均来自项目的 assets 文件夹,且特指文件夹内的食物类图像,需提前将所需食物图像放入该文件夹。
              • 定义 images 数组,数组内存储所有需要在幻灯片中展示的图像资源,同时为每张图像设置 alt 文本,提升页面可访问性,符合前端开发规范。

            2. 自动轮播核心功能(React 钩子应用)

            • 状态管理(useState):使用 React 内置的 useState 钩子,定义并控制“当前可见图像索引”的状态,该状态决定了当前幻灯片中显示哪一张图像,是实现轮播的基础数据支撑。
            • 定时更新(useEffect + setInterval)
              • useEffect 钩子内部,通过 setInterval 函数设置定时任务,每 5 秒执行一次图像索引更新操作,具体逻辑为将当前图像索引递增,当索引达到数组长度时重置为 0,实现图像循环浏览。
              • useEffect 钩子的依赖项设置为空数组([]),确保定时任务仅在组件挂载时初始化一次,避免重复创建定时器导致轮播速度异常。
            • 组件卸载清理:在 useEffect 钩子中返回一个清理函数,该函数在组件卸载时执行 clearInterval 操作,清除之前创建的定时器,防止组件卸载后定时器仍在运行,造成内存泄漏。
            • 技术特性说明:此处使用的 useStateuseEffect 均为 React 标准钩子,无特殊自定义 JS 逻辑,与原生 JavaScript 中的定时、状态控制逻辑原理一致,降低技术理解门槛。

            (三)组件引入与错误处理

            1. 组件引入步骤

            • 定位挂载位置:在需要展示幻灯片的页面中,找到标注为“幻灯片”的 div 容器,该容器为幻灯片组件的挂载点。
            • 执行组件导入:在页面对应的 JS/JSX 文件中,通过 import 语句导入 image-slideshow.js 中的幻灯片组件,再在挂载点 div 内部渲染该组件,完成组件与页面的整合。

            2. 常见错误及原因

            • 错误现象:直接预览网站时,会触发报错,提示“导入的组件需使用 useState,只能在客户端组件中使用,但其所有父组件均未标记为 use client,属于服务器组件”。
            • 错误本质:Next.js 15 中存在服务器组件(Server Components)和客户端组件(Client Components)的区分,useState 等 React 状态相关钩子仅能在客户端组件中使用。若幻灯片组件的所有父组件均未通过添加 'use client' 指令标记为客户端组件,父组件默认是服务器组件,在服务器组件中引入依赖客户端钩子的子组件,就会触发上述错误。

            四、关键技术点总结

            1. Next.js 组件拆分思想:通过创建独立子文件夹和组件文件,实现功能模块与代码文件的一一对应,符合“单一职责”原则,提升项目可维护性,这是 Next.js 项目开发中常用的文件组织方式。
            1. React 钩子组合应用useState(状态控制)与 useEffect(副作用处理)的组合,是实现“定时更新UI”类功能(如轮播、倒计时)的经典方案,需掌握钩子的依赖项设置、清理函数编写等细节,避免内存泄漏和逻辑异常。
            1. Next.js 客户端/服务器组件区分:明确两种组件的使用边界——客户端组件可使用 React 状态钩子、事件处理等交互相关功能,需通过 'use client' 声明;服务器组件主要用于数据获取、静态内容渲染,不可使用客户端相关 API,这是 Next.js 15 开发中的核心概念,直接影响组件开发与整合的正确性。
            1. Next.js 图像组件优势:使用 next/image 组件替代原生 <img> 标签,可自动实现图像优化(如自动压缩、适配不同分辨率设备)、懒加载等功能,提升页面性能和用户体验,是 Next.js 图像处理的推荐方案。

            020 React Server Components vs Client Components - When To Use What

            一、组件类型区分的底层逻辑

            1.1 技术归属与框架依赖

            • React 原生特性:React 本身内置“服务器组件(RSC)”与“客户端组件(CC)”的区分,但该特性默认处于“未解锁”状态,需依赖特定框架的构建流程和项目结构才能激活。
            • 框架差异
              • 传统 React 项目(如 Create React App 创建的项目):仅支持客户端组件,所有代码均在浏览器(客户端)运行,React 在此场景下是“纯粹的客户端库”。
              • Next.js 项目:作为全栈框架,具备后端执行能力,默认解锁 RSC 特性,是 RSC 落地的核心载体。

            1.2 Next.js 中的默认规则

            • 默认组件类型:Next.js 项目中,所有 React 组件(包括页面组件、布局组件、通用业务组件,如头部组件、幻灯片容器组件等)默认均为“服务器组件”,仅在服务器端完成渲染。
            • 渲染流程特殊性:服务器组件的函数逻辑不在浏览器中执行,仅将最终渲染完成的 HTML 代码发送给客户端;即使是单页应用(SPA)模式下的后续导航(如点击链接跳转页面),组件仍会在服务器后台重新渲染,而非客户端本地生成内容。

            二、React 服务器组件(RSC)核心特性

            2.1 执行环境与调试验证

            • 执行位置:组件函数在服务器端运行,客户端仅接收渲染结果(HTML)。
              • 调试示例:在服务器组件(如“主标题组件”“餐点页面组件”)中添加 console.log,浏览器开发者工具不会显示日志,日志会输出在启动 Next.js 开发服务器的终端中。
            • 适用场景:仅用于“展示静态/半静态内容”,无需客户端交互或动态状态的场景,例如:
              • 页面标题、静态文本描述、无需交互的图片展示(非轮播/切换类)。
              • 从服务器数据库直接获取数据并渲染的列表(无需客户端筛选/排序)。

            2.2 核心优势

            优势维度
            具体说明
            对比传统客户端组件
            性能优化
            减少客户端需下载的 JavaScript 代码体积,降低浏览器解析和执行压力,提升页面加载速度
            传统客户端组件需下载完整组件逻辑代码,体积更大,加载耗时更长
            SEO 友好
            服务器直接输出包含完整内容的 HTML,搜索引擎爬虫可直接抓取页面内容
            传统客户端组件项目(如纯 CRA 项目)页面源代码为空,内容由客户端 JS 动态生成,爬虫难以抓取

            三、React 客户端组件(CC)核心特性

            3.1 适用场景:依赖客户端能力的功能

            客户端组件需在客户端渲染,仅适用于包含“客户端专属功能”的场景,核心场景包括:
            • 使用 React 客户端钩子:如 useState(管理组件状态)、useEffect(处理副作用,如定时器、数据监听)。
              • 示例:图像轮播组件(需用 useState 管理当前显示图片索引,useEffect 设置 5 秒切换间隔),服务器端无需也无法执行定时器逻辑。
            • 绑定用户交互事件:如 onClick(点击事件)、onChange(输入框变化事件)等,需等待用户操作并在客户端响应。
              • 示例:按钮点击提交表单、输入框实时校验,均需客户端代码监听并处理事件。
            • 依赖浏览器 API:如 windowdocument 对象操作(如获取窗口尺寸、操作 DOM),服务器端无此类 API。

            3.2 声明方式:use client 指令

            • 激活规则:Next.js 中默认组件为服务器组件,若需将组件标记为客户端组件,必须在组件文件顶部第一行添加 use client 指令。
              • 语法示例:
              • 作用:添加指令后,组件可正常使用客户端功能,避免 Next.js 因“服务器组件使用客户端 API”抛出错误。

              四、关键实践总结

              4.1 组件类型选择原则

              组件类型
              选择依据
              典型场景
              服务器组件
              1. 仅展示内容,无客户端交互;2. 无需动态状态;3. 数据直接从服务器获取且无需客户端处理
              页面静态标题、无交互的文章内容、服务器端渲染的商品列表(仅展示)
              客户端组件
              1. 需管理动态状态(useState);2. 需处理副作用(useEffect);3. 需绑定用户交互事件;4. 依赖浏览器 API
              表单组件、图像轮播、按钮点击反馈、输入框实时搜索

              4.2 Next.js 中的核心注意事项

                  1. use client 指令位置:必须在组件文件顶部第一行,若放在导入语句后会导致报错。
                  1. 组件嵌套规则:服务器组件可嵌套客户端组件(如页面布局组件(RSC)中引入轮播组件(CC)),但客户端组件不可嵌套服务器组件。
                  1. 错误排查:若组件使用 useState/onClick 等客户端功能却未添加 use client,Next.js 会抛出“服务器组件无法使用客户端 API”的错误,需优先检查指令是否缺失。

              021 Using Client Components Efficiently

              一、核心场景:社区页面搭建与导航优化

              (一)社区页面快速填充

              1. 页面定位:社区页面为演示项目的辅助页面,无需复杂逻辑,仅用假内容占位(核心功能聚焦于餐点相关页面)。
              1. 文件替换与配置
                  • 提供配套的pjs文件(社区页面逻辑文件),需替换本地项目中对应的社区页面JS文件。
                  • 配套page.module.css文件(样式文件),需放置在社区页面CSS文件同级目录,确保样式生效。
              1. 组件逻辑:社区页面导出的Community组件仅负责渲染占位文本(如说明性文字)和静态图片,无交互逻辑。

              二、关键问题:导航链接活跃状态实现

              (一)需求与初始方案

              1. 需求:当前导航栏无法高亮显示“当前活跃页面”(如在社区页面时,“社区”链接无特殊样式),需通过条件添加CSS类实现高亮。
              1. 初始技术准备
                  • 样式基础:主头部模块CSS文件中已定义.active类(用于设置活跃链接样式,如颜色、下划线等)。
                  • 路径判断:通过“当前路径名”(域名后的URL部分)判断活跃状态,规则如下:
                    • 餐食相关页面:若路径以/meals开头(含嵌套页面,如/meals/share),则“浏览菜肴”链接高亮。
                    • 社区页面:若路径严格等于/community(无嵌套页面),则“社区”链接高亮。
              1. 路径获取工具:使用Next.js的usePathname钩子(从next/navigation导入),可直接获取当前页面的路径名(如/community/meals)。

              (二)初始方案的问题:客户端组件限制

              1. 错误原因usePathname钩子仅支持在客户端组件(Client Components) 中使用,若在服务器组件(Server Components)的头部组件中直接调用,会触发Next.js报错。
              1. 临时解决思路:在头部组件顶部添加'use client'指令,将整个头部转为客户端组件,可消除错误,但会失去服务器组件的优势(如服务器渲染、减少客户端JS体积)。

              三、优化方案:高效拆分客户端/服务器组件

              (一)核心原则

              Next.js开发中,应尽量将'use client'指令下放到组件树最底层,仅将“必须依赖客户端API(如usePathnameuseState)的部分”转为客户端组件,其余部分保留为服务器组件,以最大化利用服务器渲染的性能优势。

              (二)具体实现:拆分NavLink组件

              1. 新建组件文件
                  • 新建NavLink.js(组件逻辑)和NavLink.module.css(组件专属样式),独立于主头部组件。
              1. NavLink组件逻辑(客户端组件)
                      1. 声明客户端组件:顶部添加'use client'指令。
                      1. 导入依赖:usePathname(获取路径)、Link(Next.js导航组件)、classes(从NavLink.module.css导入样式)。
                      1. 接收Props:
                    • href:链接目标路径(如/community/meals)。
                    • children:链接显示文本(如“社区”、“浏览菜肴”)。
                    • ref:可选,用于DOM引用。
                      1. 活跃状态判断:
                      1. 动态添加样式:通过模板字面量拼接类名,活跃时添加.active类,否则仅保留基础链接样式:
              1. 主头部组件改造(服务器组件)
                  • 删除原头部组件中的'use client'指令和usePathname相关代码,恢复为服务器组件。
                  • 导入新建的NavLink组件,替换原有的<Link>标签,传入对应hrefchildren

                四、样式迁移与适配

                (一)样式文件迁移

                1. 问题:原头部组件的.link(基础链接样式)和.active(活跃样式)定义在主头部CSS文件中,NavLink组件无法直接引用,会导致classes.linkclasses.active未定义错误。
                1. 解决步骤
                    • 从主头部模块CSS文件中,复制link类(基础链接样式,如字体、间距)和active类(活跃样式)的代码。
                    • 将复制的样式代码粘贴到NavLink.module.css文件中,确保NavLink组件可通过classes对象访问。
                1. 选择器适配:若原样式依赖父级选择器(如.nav a),需调整为NavLink组件内的独立选择器(如直接定义.link.active),避免样式冲突。

                五、最终效果与价值

                (一)功能效果

                • 导航栏可正确高亮当前活跃页面(如在/meals/share页面时,“浏览菜肴”链接高亮;在/community页面时,“社区”链接高亮)。
                • NavLink组件为客户端组件(需加载少量JS处理路径判断和样式切换),主头部其余部分(如Logo、静态文本)仍为服务器组件,保持服务器渲染优势。

                (二)技术价值

                1. 性能优化:减少客户端JS体积(仅加载必要的交互逻辑),提升页面加载速度和首屏渲染效率。
                1. 组件复用NavLink组件可复用在项目其他导航场景(如侧边栏、底部导航),统一活跃状态逻辑。
                1. 符合Next.js最佳实践:严格区分客户端/服务器组件边界,最大化利用框架特性,避免“过度客户端化”导致的性能问题。

                六、关键知识点总结

                知识点
                核心内容
                usePathname钩子
                next/navigation导入,客户端组件专属,用于获取当前页面路径名。
                客户端组件标识
                组件顶部添加'use client'指令,仅作用于当前组件及子组件,不影响父组件。
                组件拆分原则
                仅将“依赖客户端API/交互”的部分拆为客户端组件,其余保留为服务器组件。
                动态样式拼接
                使用模板字面量(${})根据条件拼接CSS类名,实现“活跃/非活跃”样式切换。
                Next.js样式模块
                组件专属CSS文件(如NavLink.module.css),通过import classes from './xxx.module.css'导入,避免样式冲突。
                🗒️

                JavaScript 模板字符串与变量引用知识点

                1. 模板字符串(Template Literals)

                语法特征

                • 使用反引号(`)包围
                • 可以包含 ${} 嵌入表达式
                • 支持多行字符串

                使用场景

                当需要拼接多个字符串或变量时使用

                2. 普通变量引用

                语法特征

                • 直接使用变量名
                • 不需要任何特殊符号包围

                使用场景

                当只需要单个完整的变量值时使用

                3. 实际应用对比

                条件渲染中的选择

                React 中的典型应用

                4. 记忆技巧

                场景
                语法
                原因
                需要拼接多个值
                `${var1} ${var2}`
                模板字符串支持嵌入表达式
                单个完整值
                var1
                直接引用变量,无需额外语法

                5. 注意事项

                1. 模板字符串必须用反引号,不能用单引号或双引号
                1. ${} 内可以放任何 JavaScript 表达式
                1. 普通变量引用更简洁,性能也略好
                1. 选择哪种方式取决于是否需要字符串拼接

                6. 常见错误

                这个知识点在 React 开发中特别常用,尤其是在动态添加 CSS 类名时。
                 

                022 Outputting Meals Data & Images With Unknown Dimension - 餐点页开发

                一、页面基础架构与样式管理

                1.1 页面结构设计

                • 核心组成:餐点页分为「标题区域」和「主要内容区域」两部分,标题区域负责信息引导与用户操作入口,主要内容区域用于展示餐点列表数据。
                • 结构逻辑:先搭建页面骨架,再逐步填充内容与功能,确保组件化拆分(如标题区域可独立为组件,此处暂保留在根组件以简化结构)。

                1.2 CSS 模块导入与使用

                • 专属样式文件:为餐点页准备两类 CSS 模块文件,分别对应不同组件样式需求:
                  • meals-grid.module.css:用于餐点网格组件(meals-grid.js),控制餐点列表的网格布局样式。
                  • 餐点项目模块 CSS 文件:用于餐点项目组件(meals-item.js),控制单个餐点卡片的样式。
                • 导入语法:通过 Next.js 支持的 CSS 模块特殊导入语法(如 import styles from './meals-grid.module.css')导入样式文件,可直接访问文件中定义的 CSS 类,避免样式冲突。
                • 样式应用:为标题区域的 h1 标签、主要内容区域的容器、无序列表等元素添加对应样式类(如标题类、主要内容类、餐点列表类),确保页面样式统一且可维护。

                二、标题区域功能实现

                2.1 内容与样式设计

                • 标题元素:使用 h1 标签展示核心标题“美味的饭菜被创建”,其中嵌套 span 标签并添加 highlight 类,通过该类实现标题局部特殊样式(如颜色、字体权重突出)。
                • 引导文本h1 下方添加普通段落作为占位文本,示例内容为“选择你喜欢的食谱并自己烹饪,这既简单又有趣”,用于向用户传递页面用途,文本可根据实际需求修改。
                • 行动呼吁(CTA):再添加一个带 cta 类的段落,内部嵌入 Next.js 的 Link 组件(需提前导入),链接路径设为 /餐食/分享,文字内容为“分享你最喜欢的食谱”,作为用户分享餐食的入口,链接指向后续将开发的分享页面。

                2.2 组件化可选方案

                • 独立组件拆分:若需提高复用性,可将标题区域单独提取为组件(如 MealsHeader.js),但需注意同步迁移对应的 CSS 样式类,确保样式不丢失。
                • 当前选择:为简化初期开发,暂将标题区域保留在餐点页根组件中,后续可根据项目复杂度调整。

                三、餐点列表组件开发(核心组件)

                3.1 餐点网格组件(MealsGrid)

                • 组件定位:负责接收餐点数据,以网格形式批量渲染餐点项目,是连接数据源与单个餐点展示的核心桥梁。
                • 创建路径:在根组件文件夹下新建 meals 子文件夹(用于存放所有餐点相关组件),在该文件夹中创建 meals-grid.js 文件。
                • 核心逻辑
                    1. 组件定义:导出名为 MealsGrid 的组件函数,通过 props 接收 meals(餐点数组)作为数据源。
                    1. 列表渲染:使用无序列表(ul)作为容器,通过 map 方法遍历 meals 数组,为每个餐点生成一个列表项(li)。
                    1. 键值设置:每个 likey 属性设为餐点的 id(确保每个餐点唯一标识,优化 React 渲染性能)。
                    1. 样式绑定:为 ul 标签添加从 meals-grid.module.css 导入的样式类(如 styles.meals),控制网格布局。

                3.2 餐点项目组件(MealsItem)

                • 组件定位:负责单个餐点的结构化展示,包含餐点图片、名称、详情链接等信息,是用户交互的核心单元。
                • 核心特性
                    1. 动态详情链接:内部嵌入 Link 组件,路径通过动态段构建(如 /meals/[mealId]),[mealId] 为餐点的唯一标识(如 idslug),实现点击餐点卡片跳转到对应详情页的功能。
                    1. 未知尺寸图片渲染
                        • 问题背景:餐点图片来自数据库(未来支持用户上传),存储在项目 public 文件夹中,构建时无法获取图片宽高(传统本地导入图片可由 Next.js 自动检测尺寸),直接使用 Image 组件会报错。
                        • 解决方案:为 Image 组件添加 fill 属性,该属性让图片自动填充父级容器的可用空间,无需手动设置宽高;同时需配合 CSS 样式(如给父容器设置固定宽高比、溢出隐藏),确保图片显示美观且不拉伸变形。
                    1. 属性透传:通过 {...meal} 语法,将 MealsGrid 组件传递的单个餐点对象(meal)的所有属性(如 idnameimageUrl 等)透传给 MealsItem,简化属性传递逻辑,前提是餐点对象的属性名与 MealsItem 所需 props 名一致。

                四、数据与组件关联

                4.1 数据传递逻辑

                • 数据源现状:开发初期暂未接入数据库,故在餐点页根组件中,将传递给 MealsGridmeals 设为空数组(meals = []),此时页面仅显示标题区域和分享链接,不展示任何餐点。
                • 未来对接:后续需将 meals 数据从数据库中获取(如通过 Next.js 服务端函数、Prisma 等 ORM 工具),只需修改数据源,无需调整 MealsGridMealsItem 的关联逻辑,体现组件化开发的灵活性。

                4.2 组件嵌套关系

                • 层级结构:餐点页根组件 → MealsGrid 组件(接收 meals 数据)→ 遍历生成 li 列表项 → 每个 li 内部嵌入 MealsItem 组件(透传单个 meal 属性),形成“根组件-网格组件-项目组件”的清晰层级,便于维护与扩展。

                023 Setting Up A SQLite Database

                核心目标:为菜单网格组件搭建本地数据库,实现假菜单数据存储,后续可扩展至用户共享菜单存储

                一、核心操作步骤

                (一)环境准备:安装数据库依赖

                1. 停止当前开发服务
                    • 操作指令:在终端执行 ctrl + c,终止正在运行的 Next.js 开发服务器,避免依赖安装冲突
                    • 原理:Node.js 项目中,依赖安装需在无服务占用相关资源的环境下进行,确保包文件正常写入 node_modules 目录
                1. 安装 SQLite 交互依赖
                    • 安装指令:npm install better-sqlite3
                    • 依赖作用:better-sqlite3 是轻量级 SQLite 数据库交互库,支持同步/异步操作,性能优于传统 sqlite3 包,能高效实现本地数据库读写

                (二)数据库选型:为何选择 SQLite

                优势维度
                具体说明
                部署门槛
                无需搭建独立数据库服务器(如 MySQL、PostgreSQL 的服务端进程),开箱即用
                存储形式
                数据以单一文件(如 meals.db)存储,便于项目迁移、备份与版本控制
                适配场景
                适合开发阶段快速验证、小型应用数据存储,后续可平滑过渡至分布式数据库
                技术栈兼容
                与 Next.js 全栈开发模式契合,支持在 Server Components/Server Actions 中直接操作

                (三)数据库初始化:编写 init_db.js 文件

                1. 文件作用与位置
                    • 存储路径:需放在项目根目录,确保 Node.js 能直接读取并执行
                    • 核心功能:自动初始化数据库(不存在则创建,存在则复用)+ 填充假数据,避免手动编写 SQL 初始化脚本
                1. 核心代码逻辑拆解
                  1. 字段设计说明
                      • id:自增主键,确保每条数据唯一标识,便于后续查询/修改
                      • slug:URL 友好的唯一字符串(如将 "Spicy Beef Noodles" 转为 "spicy-beef-noodles"),用于动态路由(如 /meals/[slug]
                      • image:存储图片路径而非二进制文件,降低数据库体积,便于 CDN 优化
                      • creator_email:用于后续用户关联(如验证创作者身份、发送通知)

                  (四)执行初始化:生成数据库文件

                  1. 执行指令:在项目根目录终端运行 node init_db.js
                  1. 执行结果验证
                      • 成功标志:根目录下生成 meals.db 文件(文件大小随数据量变化,初始假数据一般为几 KB)
                      • 常见问题排查:若未生成文件,检查 init_db.js 路径是否正确、依赖是否安装成功(可重新执行 npm install better-sqlite3

                  (五)后续开发计划

                  1. 数据加载方向:回到菜单页面,通过 Next.js 的 Server Components 或 Server Actions 从 meals.db 中查询数据,渲染菜单列表
                  1. 功能扩展预期:后续可基于该数据库实现用户提交菜单(写入数据库)、菜单搜索/筛选(执行 SQL 查询)等功能

                  二、关键注意事项

                  1. 文件路径规范init_db.js 必须放在项目根目录,否则数据库文件路径会错误(如放在 src 目录下会生成 src/meals.db,后续读取需调整路径)
                  1. 数据去重策略:创建表时 slug 设为 UNIQUE,插入数据用 INSERT OR IGNORE,避免重复执行脚本导致数据冗余
                  1. 开发/生产环境差异:SQLite 适合开发与小型生产场景,若项目用户量增长,需迁移至 PostgreSQL(Vercel 推荐)或 MongoDB,可通过 Prisma ORM 实现平滑迁移
                  1. 安全提示:生产环境中,需避免直接暴露 creator_email 等敏感字段,可通过 Server Actions 过滤数据后再返回客户端

                  024 Fetching Data By Leveraging NextJS & Fullstack Capabilities - 全栈数据获取

                  一、核心差异:Next.js vs 传统React数据获取

                  对比维度
                  传统React应用
                  Next.js应用
                  架构模式
                  前后端分离,需单独搭建后端服务
                  全栈融合,无需独立后端
                  组件默认类型
                  客户端组件(需在浏览器执行)
                  服务器组件(默认仅在服务器执行,除非使用客户端特性如useState
                  数据获取方式
                  依赖useEffect钩子 + fetch/axios发送HTTP请求,间接访问数据库
                  服务器组件内直接连接数据库,无需HTTP请求,操作更安全(敏感信息不暴露到客户端)
                  数据获取时机
                  客户端渲染后(可能出现“白屏加载”“数据闪烁”)
                  服务器端渲染/静态生成时(首屏数据直接嵌入HTML,优化用户体验)

                  二、数据获取实操:文件结构与代码实现

                  1. 项目文件结构设计(核心:代码分离)

                  为避免组件代码冗余,按“功能模块”拆分文件,标准结构如下:

                  2. 核心文件代码实现(以“获取餐点数据”为例)

                  (1)li/meals.js:数据库操作封装

                  作用:统一管理数据库连接、SQL执行逻辑,供页面组件调用

                  (2)app/meals/page.js:服务器组件调用数据

                  Next.js中app目录下的page.js默认是服务器组件,可直接调用数据库函数:

                  三、关键特性与注意事项

                  1. 服务器组件核心特性(数据获取的关键)

                  • 无客户端暴露风险:数据库连接信息、SQL逻辑仅在服务器执行,客户端无法获取,安全性更高。
                  • 无需状态管理:无需useState存储数据,服务器直接将数据嵌入HTML返回,避免“数据-UI同步”问题。
                  • 异步适配友好:服务器组件函数支持async/await,可直接等待异步数据函数,简化代码。

                  2. 项目运行与调试要点

                  • 依赖安装:使用better-sqlite3前需先安装依赖:npm install better-sqlite3,否则会报错。
                  • 开发服务器重启:修改meals.js等工具文件后,需重启Next.js开发服务器(npm run dev),否则修改不生效。
                  • 静态资源路径:数据库中存储的图片路径需指向public文件夹(如/images/meals/spaghetti.jpg),组件中通过src={imagePath}直接引用(无需额外配置)。

                  3. 视频中提及的“已知问题”(避坑提示)

                  • 样式错误:视频录制时存在“多余标题”的样式问题,已在配套附件中修复,实际开发中需注意CSS作用域(如使用module.css避免样式污染)。
                  • 餐点详情页占位:当前仅实现“列表数据获取”,导航到详情页(如/meals/[id])时暂不显示内容,需后续补充“按ID查询数据”逻辑(使用get()方法执行SELECT * FROM meals WHERE id = ?)。

                  四、后续延伸方向

                  1. 加载状态处理:因已在getMeals()中模拟耗时操作,后续可通过Next.js的Suspense组件实现“加载中”提示(如骨架屏)。
                  1. 错误处理:添加try/catch捕获数据库查询错误,避免页面崩溃(如try { const meals = await getMeals() } catch (err) { return <div>数据加载失败</div> })。
                  1. 动态路由数据:针对详情页(/meals/[id]),需通过params参数获取id,再调用“按ID查餐点”的函数(封装getMealById(id))。

                  025 Adding A Loading Page

                  一、核心问题:页面加载的用户体验痛点

                  1. 加载延迟现象

                  • 在“饭菜页面”场景中,由于从数据库获取饭菜数据的函数被人为添加了延迟(默认2秒,可延长至5秒),导致页面重新加载时,用户需等待数秒才能看到内容,期间屏幕无任何反馈。
                  • 典型场景:从起始页刷新后导航到“浏览菜单”页(页面未被缓存)时,用户无法判断导航请求是否成功,易产生困惑。

                  2. 特殊情况:缓存带来的“体验差异”

                  • Next.js默认开启激进缓存机制:会缓存用户访问过的所有页面及对应数据。
                  • 缓存生效场景:当用户从其他页面返回已访问过的“餐食页面”时,页面会直接从缓存加载,无需等待延迟时间,几乎即时显示。
                  • 缓存失效场景:仅当用户主动刷新页面,或离开页面后重新进入(非从其他页面跳转)时,页面才会重新创建,需等待数据加载延迟。

                  二、解决方案:添加Loading Page(加载页面)

                  1. 核心原理:利用Next.js保留文件名机制

                  • Next.js中,loading.js是与layout.js(布局)、page.js(页面)同级的保留文件名文件,具有特殊生效规则:
                    • loading.js所在目录的“页面/嵌套页面/布局”正在加载数据时,loading.js导出的组件会自动作为“备用内容(fallback)”显示。
                    • 直到目标页面/布局的数据加载完成,loading.js的内容才会被替换为实际页面内容。

                  2. 具体实现步骤(含代码逻辑)

                  步骤1:创建loading.js文件

                  • 在目标页面(如“餐食页面”)所在的文件夹中,新建loading.js文件。
                  • 在文件中导出一个React组件,用于展示加载提示,示例代码:

                    步骤2:添加样式(使用CSS Modules)

                    • 准备loading.module.css文件(课程提供附件,可直接导入使用),定义加载提示的样式,示例:
                      • loading.js中导入样式并应用到提示文本,示例代码:

                        步骤3:验证效果

                        • 保存文件后,重新加载项目:
                          • 场景1:刷新“餐食页面”,加载延迟期间会显示“正在获取餐点...”文本,数据加载完成后自动替换为餐食列表。
                          • 场景2:从起始页导航到“浏览菜单”页(未缓存),会即时显示加载提示,解决“无反馈”问题。

                        四、关键知识点总结

                        知识点
                        细节
                        作用
                        Next.js缓存机制
                        缓存访问过的页面+数据,跳转返回时从缓存加载,刷新/重新进入时失效
                        提升重复访问的页面加载速度,但首次/刷新访问仍需处理延迟
                        loading.js保留文件
                        目录级生效,数据加载时显示fallback内容,加载完成后替换
                        解决“加载无反馈”问题,提升用户体验
                        CSS Modules样式
                        为加载提示添加局部样式,避免样式冲突
                        保证加载页面样式的独立性和可维护性

                        五、注意事项与扩展

                        1. loading.js的生效范围:仅对其所在目录及子目录的页面/布局生效,若需全局加载提示,需在项目根目录(app目录)创建loading.js
                        1. 加载内容自定义:除文本提示外,可添加加载动画(如Spin组件)、图标等,进一步优化视觉体验。
                        1. 与Suspense的配合:若需更细粒度的加载控制(如页面中某部分组件加载),可结合React Suspense组件使用,loading.js更适合“整页加载”场景。
                         

                        026 Using Suspense & Streamed Responses For Granular Loading State Management - 利用Suspense和流式响应实现精细化加载状态管理

                        一、传统加载方案的问题

                        1. 页面加载体验缺陷

                        • 传统加载方式中,加载文本会占据整个屏幕,即使页面存在不依赖加载数据的元素(如“餐食页面”的标题),也无法提前显示,导致用户等待期间看不到任何有效内容,体验不佳。

                        2. 加载文件复用性与适用性问题

                        • 临时解决方案:若将页面头部代码及关联CSS复制到loading.js文件中,虽能实现“标题提前显示”的效果,但不符合代码复用和整洁性原则。
                        • 嵌套布局冲突loading.js文件会默认作用于所有嵌套页面和布局,若某些嵌套场景中不需要在数据加载时显示头部(如部分子页面无统一头部设计),该方案会引发逻辑冲突。

                        二、文件结构调整(废弃传统加载文件)

                        1. 失效传统加载文件

                        • 无需删除原loading.js文件,只需修改文件名(如改为loading-dash-out),使其不再符合Next.js对“加载文件”的命名规则(Next.js仅识别特定命名的加载文件),从而失去默认加载作用。
                        • 保留修改后的文件可作为备选方案,便于后续对比或回退。

                        2. 规范文件存放位置

                        • 原加载文件若存放在主应用目录,虽能作用于嵌套页面(如“餐食页面”),但从项目结构规范性角度,建议将其移至对应功能文件夹(如“餐食文件夹”),即使当前已废弃该文件,也能保持目录与功能的对应关系。

                        三、精细化加载方案:Suspense + 异步组件

                        1. 核心思路

                        • 将页面中“数据获取”逻辑与“UI渲染”逻辑拆分:把耗时的数据获取操作(如加载餐食数据)封装到独立的异步组件中,页面本身仅负责UI组合,通过React的Suspense组件控制该异步组件的加载状态,实现“非依赖数据元素提前渲染、依赖数据元素加载时显示回退内容”的效果。

                        2. 具体实现步骤

                        (1)创建异步数据组件

                        • 在“餐食页面”的JS文件中,定义独立的异步组件(如Meals组件),该组件默认是Next.js的服务器组件(无需额外声明,服务器组件天然支持异步操作)。
                        • 将原页面中的数据获取代码(如请求餐食列表接口、读取数据库数据等)迁移到Meals组件中,由该组件负责获取并返回数据,最终渲染数据对应的UI(如餐食列表)。
                        • 移除“餐食页面”本身的async标识(因数据获取逻辑已迁移到子组件,页面无需再异步等待数据)。

                        (2)导入并使用Suspense组件

                        • 导入方式:从react库中直接导入Suspense组件(无需额外安装依赖,React内置该组件)。
                        • 作用机制Suspense组件用于包裹需要等待数据的异步组件(如Meals),当异步组件正在获取数据时,Suspense会显示预设的“回退内容”(如加载文本);当数据获取完成,异步组件渲染完成后,Suspense会自动替换为组件的实际UI。
                        • 代码结构示例

                          (3)复用加载样式

                          • 将原loading.module.css文件中的样式代码复制到当前页面的CSS模块(如page.module.css)中,确保“加载回退内容”(如加载文本)的样式正常生效,无需额外维护多个加载样式文件。

                          四、流式响应(Streamed Responses)的优势

                          1. 渲染机制

                          • Next.js会优先渲染页面中“不依赖数据”的内容(如标题),将其先返回给浏览器显示;同时异步获取数据,数据获取完成后,通过流式传输的方式,将异步组件(如Meals)的渲染结果“增量注入”到页面中,无需刷新整个页面。
                          • 对比传统“全页面等待加载”,流式响应能让用户更快看到部分有效内容,减少“空白屏幕”等待时间,显著提升用户体验。

                          2. 与传统加载文件的区别

                          对比维度
                          传统loading.js文件
                          Suspense + 异步组件
                          作用范围
                          作用于整个页面及嵌套布局
                          仅作用于被包裹的异步组件(精细化控制)
                          灵活性
                          无法单独控制部分元素的加载状态
                          可针对不同数据模块设置不同回退内容
                          实现原理
                          Next.js幕后自动包裹页面内容
                          手动控制加载状态,逻辑更透明
                          适用场景
                          页面整体依赖数据,无提前渲染元素
                          页面存在部分不依赖数据的元素(如标题、导航)

                          五、关键知识点总结

                          1. Next.js加载文件规则:仅特定命名的文件(如loading.js)会被识别为默认加载文件,修改文件名可快速失效该功能。
                          1. 服务器组件特性:Next.js的服务器组件默认支持异步操作,适合封装数据获取逻辑,无需额外处理异步状态(如useEffect)。
                          1. Suspense核心能力:React内置组件,用于管理异步组件的加载状态,通过fallback属性定义加载时的回退UI,数据就绪后自动切换到实际组件。
                          1. 流式响应优势:Next.js支持页面部分渲染 + 增量流式更新,优先显示非依赖数据元素,提升用户感知加载速度。
                          1. 核心目标:通过“拆分数据与UI、精细化控制加载范围”,解决传统加载方案中“全页面等待”的问题,优化用户体验。

                          027 Handling Errors

                          Next.js 15 错误处理(Error Handling)详细笔记

                          一、错误处理的核心必要性与适用场景

                          1. 为什么需要错误处理

                          • 项目开发中,页面或组件可能因多种原因生成错误,若不处理会导致用户体验差、问题排查困难,因此需专门的错误处理机制保障应用稳定性。
                          • 示例场景:加载数据失败(如远程数据库离线时,无法获取所需数据;SQLite数据库因本地存储特性,离线概率极低,可暂不重点考虑此类场景)。

                          2. 错误模拟方法

                          • 操作路径:进入项目的lip文件夹,找到meals.js文件。
                          • 模拟代码:在meals.js中添加throw new Error("加载餐点失败")语句,人为触发错误以测试后续错误处理效果(后续需注释该代码,避免影响正常功能)。

                          三、无自定义错误处理时的页面表现

                          1. 操作前提:删除项目中已有的error.js文件。
                          1. 页面加载流程与错误展示
                              • 重新加载页面后,会先尝试加载数据。
                              • 约5秒后,页面出现错误并展示开发版本错误屏幕(生产版本错误屏幕外观不同)。
                          1. 开发版错误屏幕的作用:展示详细错误信息,帮助开发者快速定位问题根源,便于在生产环境部署前修复错误;生产版错误屏幕会隐藏敏感错误信息,仅展示友好的用户提示。

                          四、自定义错误处理的实现(基于error.js文件)

                          1. error.js文件的核心作用

                          • Next.js 框架特性:当项目中存在error.js文件时,Next.js会在错误发生时自动渲染该文件中定义的组件,实现错误页面的自定义展示。
                          • 作用范围规则:
                            • error.js放在某文件夹下,仅处理该文件夹下页面及所有嵌套页面/布局产生的错误。
                            • 若在应用根级别添加error.js,可捕获整个应用所有页面的错误(可根据项目需求选择处理范围,示例中选择“深入一级”,仅替换网站部分内容,避免整个网站被错误屏幕覆盖)。

                          2. 错误组件的样式与内容配置

                          (1)基础结构与样式引入

                          (2)错误信息的获取与展示

                          • Props 特性:Next.js 会向error.js中的组件传递error道具(props),该道具包含错误相关详细信息。
                          • 信息安全机制:Next.js 会自动隐藏实际错误消息(如数据库连接地址、内部代码逻辑等),防止敏感信息暴露给普通用户。
                          • 自定义消息示例:若无需展示精细错误(如无错误代码等特殊标识),可输出通用提示,示例代码如下:

                          五、错误组件的关键要求:必须是客户端组件

                          1. 为什么需要声明为客户端组件

                          • Next.js 设计逻辑:错误可能在客户端(如用户交互过程中)或服务器渲染页面后发生,error.js组件需同时捕获两类错误,因此必须是客户端组件。

                          2. 声明方法与效果

                          • 声明代码:在error.js文件最顶部添加'use client'指令(必须放在文件首行,否则Next.js无法识别)。
                          • 修复效果:添加指令后,重新加载页面,之前因“非客户端组件”导致的额外错误会消失,可正常展示自定义的错误页面(后续可根据设计需求调整错误页面的样式、文案等)。

                          六、后续清理与注意事项

                          1. 代码清理:测试完成后,需注释掉lip/meals.jsthrow new Error("加载餐点失败")的代码,避免影响项目正常功能。
                          1. 使用建议
                              • 根级别error.js适合处理全局通用错误(如404、500等),文件夹级error.js适合处理模块专属错误(如“餐点数据加载失败”仅在餐点相关页面展示)。
                              • 错误消息需兼顾“用户友好”与“开发排查”,可在开发环境展示详细错误,生产环境展示简化提示(Next.js 会自动区分环境,无需额外配置)。

                          028 Handling Not Found States

                          一、核心场景:无效URL触发的404问题

                          1. 触发条件:当用户输入应用不支持的URL路径(如示例中的“我-餐”)时,会触发“未找到页面”状态
                          1. 默认行为:系统自动显示默认404页面,若对默认样式和内容满意,无需额外配置

                          二、自定义404页面的核心配置

                          1. 文件创建规则

                          • 支持在任意应用文件夹中添加“未找到”相关JS文件(无固定命名限制,核心是导出指定函数)
                          • 生效优先级:添加的文件会自动覆盖其兄弟文件夹嵌套子文件夹的404页面规则
                          • 全局捕获:若将文件添加到根级别文件夹,可捕获应用内所有路径的“未找到”错误;若需针对特定模块定制,可嵌套到对应子文件夹

                          2. 代码实现步骤

                          • 关键依赖:需确保“未找到”类名在全局CSS文件中已配置样式,保证页面美观性和一致性
                          • 生效验证:代码添加后,访问无效URL即可看到自定义404页面,替代默认页面
                           

                          029 Loading & Rendering Meal Details via Dynamic Routes & Route Parameters

                          一、核心目标

                          在 Next.js 15(基于 App Router)与 React 项目中,实现通过动态路由+路由参数加载并渲染餐食详情页面,完成从页面结构搭建、样式配置到数据库数据获取与渲染的全流程。

                          二、前置准备与文件结构

                          1. 关键文件定位

                          • 页面组件文件:位于餐食文件夹/动态部分文件夹/page.js,是输出餐食详情的核心组件(Next.js App Router 中,page.js为路由默认入口组件)。
                          • 样式文件:需在page.js同级目录创建页面名称.module.css(如mealDetail.module.css),用于组件样式隔离(CSS Modules 特性,避免样式污染)。

                          三、页面 UI 搭建与样式配置

                          1. 样式导入与使用规则

                          • 从 CSS Modules 文件导入样式类,语法:import styles from './mealDetail.module.css'
                          • 为元素添加样式时,通过className={styles.类名}绑定,类名需使用驼峰命名法(如headertitleText,而非短横线命名)。

                          2. 核心 UI 结构拆解

                          (1)标题区域(Header)

                          包含「餐食图片+餐食标题+创作者信息+餐食总结」,结构如下:

                          (2)主要内容区(Instructions)

                          输出餐食制作步骤(HTML 格式),需处理「HTML 原生渲染」与「XSS 风险」:
                          • XSS 风险提示dangerouslySetInnerHTML会直接渲染传入的 HTML,若内容未经过滤(如用户输入的恶意脚本),可能导致跨站脚本攻击。实际项目中,需对meal.instructions做 HTML 净化(如使用DOMPurify库)。
                          • 换行符处理:数据库存储的文本换行符(\\n)在 HTML 中会被忽略,需用replace(/\\n/g, '<br />')替换为换行标签,保证格式正确。

                          四、数据库数据获取(安全查询)

                          1. 编写数据查询函数(lib/meals.js)

                          在项目lib文件夹(存放工具函数/数据库逻辑)的meals.js中,导出getMeal函数,用于通过「餐食 slug」查询单条餐食数据:
                          • SQL 注入防护:使用prepare预编译语句+占位符,数据库库(如better-sqlite3)会自动对参数转义,避免恶意 SQL 语句注入。
                          • 函数返回值:直接返回查询结果(对象形式,含titleimagecreator等字段),而非 Promise(初始阶段简化逻辑,后续可优化为异步+Suspense)。

                          2. 数据库字段关联

                          需确保数据库meals表包含以下关键字段(与前端渲染对应):
                          数据库字段名
                          前端使用场景
                          slug
                          餐食唯一标识(路由参数)
                          title
                          餐食标题
                          image
                          餐食图片 URL
                          creator
                          创作者名称
                          creator_email
                          创作者邮箱(前端用 creatorEmail)
                          summary
                          餐食简短总结
                          instructions
                          餐食制作步骤(HTML 文本)

                          五、动态路由与路由参数获取

                          1. 配置动态路由(App Router 规则)

                          • 在 App Router 中,动态路由通过「方括号包裹文件夹名」实现,格式:app/meals/[mealSlug]/page.js
                            • [mealSlug]为动态路由段,mealSlug是参数名(可自定义,如[id]),URL 中对应部分会被解析为该参数值(如/meals/spicy-noodles,则mealSlug = 'spicy-noodles')。

                          2. 获取路由参数(params 属性)

                          Next.js 会自动为page.js组件传递params属性,包含动态路由段的键值对,需通过解构获取:
                          🗒️
                          在 Next.js 15 中, params 属性已经更改为 Promise,需要进行相应的处理。
                           

                          六、关键问题与解决方案

                          问题场景
                          解决方案
                          图片尺寸未知,无法适配
                          使用 Next.js Image 组件的fill属性,配合父容器position: relative实现填充
                          渲染 HTML 内容导致 XSS
                          1. 对输入内容做 HTML 净化(如DOMPurify);2. 避免渲染不可信用户输入
                          文本换行符不生效
                          replace(/\\n/g, '<br />')替换换行符为 HTML 换行标签
                          SQL 注入风险
                          使用预编译语句+占位符(prepare+?),避免直接拼接 SQL 字符串
                          getMeal返回 Promise 导致无数据
                          初始阶段移除async关键字;后续可结合Suspense处理异步加载(如Suspense fallback={<Loading />}

                          030 Throwing Not Found Errors For Individual Meals - 处理单个餐点“未找到”错误

                          一、核心问题:现有错误处理的局限性

                          1. 场景复现:当用户尝试访问不存在的餐点(如示例中的“伟大的bolo”)时,项目会显示错误页面,但该页面本质是“访问未定义指令”“无法加载餐点数据”的基础回退页面,并非针对“餐点不存在”的专属反馈。
                          1. 关键痛点:技术层面的错误提示与用户实际需求不匹配——用户需要明确知道“请求的餐点找不到”,而非笼统的“系统错误”,基础回退页面无法传递精准信息,影响用户体验。

                          二、解决方案:使用Next.js内置notFound函数

                          (一)函数核心作用

                          1. 功能定义:notFound函数是Next.js从“next/navigation”模块导出的特殊工具函数,用于主动触发“未找到”状态。
                          1. 执行逻辑:调用该函数后,会立即停止当前组件的渲染流程,自动查找并显示项目中“最近层级”的“未找到”页面(404页面)或错误处理页面。
                              • 示例:若项目在根目录和餐点文件夹下均有“未找到”页面,访问不存在的餐点时,会优先显示餐点文件夹下的专属页面(更近层级)。

                          (二)实现步骤

                          1. 导入函数:在处理餐点数据的组件中,从Next.js导航模块导入notFound函数,代码示例:import { notFound } from 'next/navigation'
                          1. 添加判断逻辑:在加载餐点数据后,增加“餐点是否存在”的判断条件:
                              • 检查条件:餐点数据未定义(meal === undefined)、餐点数据为空(!meal)等。
                              • 触发函数:若满足“餐点未找到”条件,直接调用notFound(),终止当前组件并跳转至“未找到”页面。

                          四、体验优化:创建餐点专属“未找到”页面

                          (一)核心价值

                          突破通用错误页面的局限性,为“找不到餐点”场景定制专属内容,让用户快速理解问题(如明确告知“请求的餐点数据不存在”),而非接收模糊的系统错误提示。

                          (二)实现方法

                          1. 文件路径配置:在项目的“餐点相关文件夹”(如app/meals/)内,创建名为not-found.js(或not-found.tsx,TypeScript项目)的文件。
                              • 注意:Next.js通过文件名识别“未找到”页面,需严格遵循not-found的命名规则。
                          1. 定制页面内容:在not-found.js文件中编写专属UI,示例内容可包括:
                              • 明确提示文本:“抱歉,我们找不到您请求的餐点页面或餐点数据”。
                              • 引导操作:返回餐点列表页的链接、搜索框等,帮助用户快速回到有效操作路径。
                          1. 效果验证:重新加载“不存在的餐点页面”,此时会显示meals文件夹下not-found.js对应的内容,而非根目录的通用404页面。

                          五、关键原理与注意事项

                          1. Next.js路由优先级规则:“未找到”页面遵循“就近匹配”原则——层级越靠近当前访问路由的not-found文件,优先级越高(如app/meals/not-found.js优先于app/not-found.js)。
                          1. 组件执行中断特性notFound()调用后,其之后的代码不会执行,需确保在调用前完成必要的资源清理(如取消数据请求),避免内存泄漏。
                          1. 与普通错误处理的区别
                              • 普通错误(如网络异常、数据解析失败):需用error.js处理,展示“系统错误”类提示。
                              • “未找到”错误(如资源不存在):用notFound()+not-found.js处理,展示“资源不存在”类提示,二者场景不可混淆。

                          六、应用场景扩展

                          除“餐点页面”外,该方案可复用于Next.js项目中所有“动态资源不存在”的场景,如:
                          • 电商项目:找不到指定商品(app/products/[id]/not-found.js)。
                          • 博客项目:找不到指定文章(app/posts/[slug]/not-found.js)。
                          • 用户中心:找不到指定用户资料(app/users/[username]/not-found.js)。
                           
                           

                          031 Getting Started with the Share Meal Form

                          一、开发背景与目标

                          1. 现有功能基础:已实现餐点加载、餐点详细信息查看功能
                          1. 核心开发目标:新增“用户添加自定义餐点”功能,通过搭建“Share Meal Form”(共享餐点表单)实现

                          二、工作文件与环境准备

                          1. 工作目录定位:操作核心目录为“共享文件夹→餐点文件夹”,该目录下的JS文件负责渲染当前页面内容
                          1. 提供的预制文件
                              • 共享餐点页面JS文件:包含可直接使用的表单组件,需替换用户原有共享页面JS文件
                              • 页面CSS文件:内置表单页面所需样式,确保界面展示效果
                          1. 文件生效操作:添加预制文件后保存所有内容,刷新访问页面即可看到表单

                          三、Share Meal Form 结构设计

                          表单组成部分
                          功能说明
                          标题区域
                          带有占位文本,用于明确表单用途(如“添加共享餐点”)
                          核心输入区
                          包含多个带标签的输入框,用于收集餐点基础信息(如名称、描述、价格等)
                          特殊输入组件
                          图像选择器(Image Picker),支持用户选择并上传餐点图片,为后续功能模块

                          四、开发进度与规划

                          1. 当前表单状态:表单界面已可展示,但无提交功能(未编写提交处理代码)
                          1. 后续开发优先级
                              • 第一步:完善图像选择器功能(实现图片选择、预览、上传逻辑)
                              • 第二步:编写表单提交处理代码(实现数据校验、提交到后端/数据库等功能)

                          032 Getting Started with a Custom Image Picker Input Component - 自定义图像选择器组件开发

                          一、组件开发背景与目标

                          • 应用场景:为饮食相关表单提供图像上传功能,支持用户向表格添加/上传图像,在表单提交时完成图像数据传递
                          • 核心需求:实现基础文件选择功能、优化默认交互样式、支持图像预览(计划功能),且组件仅用于饮食相关模块,需保证可配置性与复用性

                          二、组件文件结构与基础配置

                          1. 文件创建与路径规划

                          • 文件位置:在components文件夹下的meals(餐点)子文件夹中,创建两个核心文件:
                            • image-picker.js:组件逻辑代码文件,因仅用于饮食表单,与业务模块绑定存放
                            • image-picker.module.css:组件专属样式文件,通过CSS Modules实现样式隔离,避免全局污染
                          • 导入样式:在image-picker.js顶部导入样式文件,确保组件可使用定义的样式类,代码示例:

                            2. 组件基础结构搭建

                            • 组件导出:定义并导出ImagePicker组件函数,函数需返回JSX标记并处理图像选择交互,基础结构如下:

                              三、核心功能实现:标签与文件输入配置

                              1. 可配置标签设计

                              • Props接收文本:通过props接收标签文本,提升组件灵活性,支持不同表单场景下的文本自定义,代码示例:
                                • 关联输入元素:使用htmlFor属性将标签与输入元素的id绑定,实现点击标签聚焦输入框的原生交互

                                2. 文件输入元素配置(核心)

                                (1)基础属性设置

                                • 输入类型与样式容器:在类为classes.controls的div中嵌套type="file"的输入元素,代码示例:
                                  • 关键属性说明
                                    • accept:限制仅接受image/pngimage/jpeg格式,过滤非图像文件,提升安全性与用户体验
                                    • name:设为image,后续表单提交时用于提取上传文件数据,是数据传递的关键标识
                                    • className={classes.input}:用于后续隐藏默认输入框样式

                                  (2)提升组件可配置性

                                  • 动态生成ID与Name:将name作为props接收,动态生成idhtmlFor值,支持同一页面多个图像选择器实例,优化代码如下:

                                    四、组件集成与初步使用

                                    1. 在表单中导入并使用

                                    • 导入组件:在page.js(表单所在页面)中导入ImagePicker组件,代码示例:
                                      • 组件调用:在表单中传入labelname属性,实现基础图像选择功能,代码示例:
                                        • 初始效果:此时组件可正常点击打开文件选择窗口,但默认输入框样式粗糙,无图像预览功能

                                        五、交互与样式优化:自定义按钮与隐藏默认输入

                                        1. 隐藏默认输入框

                                        • CSS样式控制:在image-picker.module.css中定义input类,通过样式使其不可见(仍保留在DOM中,不影响功能),示例:

                                          2. 添加自定义按钮并绑定交互

                                          (1)创建自定义按钮

                                          • 按钮属性设置:在输入元素后添加类型为button的自定义按钮(避免默认submit类型触发表单提交),代码示例:

                                            (2)实现按钮触发输入点击(客户端交互核心)

                                            • 标记客户组件:在image-picker.js顶部添加'use client'指令,因涉及useRef和点击事件,需运行在客户端,代码示例:
                                              • 使用useRef获取DOM元素:创建imageInput ref并绑定到输入元素,通过ref触发输入框点击事件,完整代码:
                                                • 核心原理:通过自定义按钮触发隐藏输入框的click()方法,既保留原生文件选择功能,又实现了UI样式的完全自定义

                                                六、后续功能规划

                                                • 图像预览功能:选择图像后即时在组件中显示预览图,提升用户交互体验,需监听输入框的change事件,通过FileReader读取文件并渲染预览
                                                • 进一步优化:可添加文件大小限制、预览图删除/替换功能,完善表单提交时的图像数据校验
                                                🗒️
                                                在React中,ref(reference)是一个特殊属性,用于直接访问DOM元素或组件实例。让我详细解释一下input元素的ref属性:

                                                ref的基本概念

                                                ref是React提供的一种机制,允许你:
                                                1. 直接访问DOM元素
                                                1. 在不需要重新渲染的情况下存储可变值
                                                1. 访问子组件的方法或属性

                                                在input元素上使用ref的具体作用

                                                在当前代码中:

                                                为什么需要在这里使用ref

                                                1. 触发文件选择对话框
                                                    • 原生的<input type="file">元素有一个click()方法,可以打开文件选择对话框
                                                    • 但在这个组件中,input元素是隐藏的(通过CSS样式),用户无法直接点击它
                                                    • 通过ref,我们可以在自定义按钮的点击事件中编程式触发input的点击
                                                1. 绕过React的事件系统
                                                    • 有时需要直接调用DOM元素的原生方法,而不是通过React的事件系统
                                                    • ref提供了直接访问DOM元素的途径
                                                1. 获取input的值
                                                    • 虽然在这个例子中没有使用,但通过ref也可以直接获取input的值:

                                                ref与state的区别

                                                特性
                                                ref
                                                state
                                                更新触发重新渲染
                                                读取方式
                                                ref.current
                                                直接访问state变量
                                                适用场景
                                                访问DOM、存储不需要触发渲染的值
                                                需要触发UI更新的数据

                                                其他常见的input ref用途

                                                总结来说,在这个图片选择器组件中,inputref主要用于编程式触发文件选择对话框,这是实现自定义文件选择界面的关键部分。

                                                033 Adding an Image Preview to the Picker - 图片选择器预览功能实现笔记

                                                一、核心功能与技术背景

                                                1. 功能目标:为图片选择器组件添加实时预览功能,用户选中图片后立即在UI中展示预览效果,无需等待表单提交
                                                1. 技术前提
                                                    • 组件需为客户端组件(已完成转换,无需额外修改),因需使用状态钩子管理交互状态
                                                    • 依赖Next.js 15环境,核心用到Next.js的Image组件优化图片渲染
                                                    • 基于React状态管理与事件处理机制,需熟悉useState钩子与事件对象操作

                                                二、关键技术步骤拆解

                                                (一)状态定义:管理选中图片数据

                                                1. 状态声明:使用useState钩子定义选中图片状态,存储图片的Data URL(用于预览)
                                                  1. 状态作用:作为预览区域渲染的核心依据,连接“图片选择”与“UI展示”的桥梁

                                                  (二)事件处理:捕获图片选择行为

                                                  1. 绑定事件:为文件输入框(<input type="file" />)添加onChange事件,关联handleImageChange处理函数
                                                    1. 处理函数实现
                                                        • 获取选中文件:通过事件对象event.target.files获取选中文件列表(数组形式),因仅允许单文件选择,取索引[0]获取目标文件
                                                        • 边界检查:判断文件是否存在,若用户取消选择或未选文件,直接返回避免报错

                                                    (三)文件转换:生成预览可用的Data URL

                                                    1. 核心原理:通过JavaScript内置的FileReader类,将本地选中的图片文件转换为Data URL(Base64编码字符串),该字符串可直接作为<img>标签的src属性值
                                                    1. 具体实现步骤
                                                      1. 关键注意点
                                                          • readAsDataURL为异步方法,不直接返回结果,需通过onload回调获取
                                                          • Data URL包含图片完整编码,可直接用于预览,但体积较大,仅适合预览场景(不建议长期存储)

                                                      (四)预览区域:条件渲染UI

                                                      1. 结构设计:添加preview容器,根据pickedImage状态实现“无图提示”与“图片预览”的条件切换
                                                      1. Next.js Image组件使用
                                                          • next/image导入组件,优化图片加载(自动压缩、懒加载等)
                                                          • 核心属性配置:
                                                            • src:绑定pickedImage(Data URL)
                                                            • alt:设置可访问性文本(如“用户选择的图片”)
                                                            • fill:自动填充父容器,适配未知图片尺寸(需配合父容器CSS定位)
                                                      1. 完整代码示例

                                                        三、功能验证与扩展提示

                                                        1. 验证方式
                                                            • 点击文件选择器按钮,选择本地图片文件
                                                            • 观察预览区域:是否从“无图提示”切换为选中图片的预览效果
                                                        1. 后续扩展
                                                            • 表单提交:此时可基于pickedImage状态或原始selectedFile对象,将图片数据提交至后端(如上传至服务器存储)
                                                            • 优化增强:可添加图片格式校验(如仅允许JPG/PNG)、文件大小限制、预览图删除/替换功能

                                                        四、核心知识点总结

                                                        知识点
                                                        作用
                                                        关键注意点
                                                        useState钩子
                                                        管理选中图片状态
                                                        初始值设为null,明确“无选中图片”的初始状态
                                                        FileReader
                                                        本地文件转Data URL
                                                        异步执行,需通过onload回调获取结果
                                                        Next.js Image组件
                                                        优化图片预览渲染
                                                        需设置父容器定位(如relative),配合fill属性使用
                                                        事件对象event.target.files
                                                        获取选中文件列表
                                                        单文件选择取索引[0],需做边界检查避免报错
                                                        条件渲染
                                                        切换“无图提示”与“图片预览”
                                                        基于pickedImage是否为null判断

                                                        034 Improving the Image Picker Component

                                                        There are two improvements you could / should make to that "Image Picker" component:你可以/应该对那个“图像选择器”组件做两处改进:
                                                        1) Reset the previewed image if no image was selected:1) 如果未选择图像,重置预览图像:
                                                        Add set setPickedImage(null); to the if (!file) block:在if (!file)代码块中添加setPickedImage(null);
                                                        2) Add the required prop to the (hidden) <input> element:2) 向(隐藏的)<input>元素添加required属性:
                                                        This ensures that the <form> can't be submitted without an image being selected.这确保了在未选择图片的情况下,<form>无法提交。
                                                         

                                                        035 Introducing & Using Server Actions for Handling Form Submissions

                                                        一、核心概念:Server Actions 是什么

                                                        • 定义:React 服务器组件特性之一,需依赖 Next.js 等框架解锁,是仅在服务器端执行的异步函数,用于处理表单提交等数据交互场景。
                                                        • 与 Client Components 区别:通过 useServer 指令(函数内部添加)声明,而非 useClient 指令(文件顶部添加,用于声明客户端组件),确保函数仅在服务器运行,避免客户端暴露敏感逻辑。

                                                        二、传统表单处理 vs Server Actions 对比

                                                        对比维度
                                                        传统 React 表单处理
                                                        Next.js Server Actions
                                                        数据交互
                                                        需手动阻止浏览器默认行为,收集数据后调用 fetch/axios 发送请求
                                                        无需手动发送请求,Next.js 自动创建请求触发函数
                                                        执行环境
                                                        提交逻辑(含数据处理)可能在客户端暴露
                                                        函数仅在服务器端执行,安全性更高
                                                        代码复杂度
                                                        需手写请求封装、错误处理、数据格式转换
                                                        直接关联表单 action 属性,简化代码流程

                                                        三、Server Actions 核心使用步骤

                                                        1. 创建 Server Action 函数

                                                        • 必须添加 async 关键字(异步执行特性)。
                                                        • 函数内部添加 useServer 指令(声明为服务器操作)。
                                                        • 示例(以“共享餐点”表单为例):

                                                          2. 关联表单与 Server Action

                                                          • 表单的 action 属性直接绑定 Server Action 函数(而非传统 URL 路径)。
                                                          • 示例:

                                                            3. 提取表单数据(FormData 对象)

                                                            • 表单提交时,Server Action 函数会 自动接收 formData 参数(包含所有输入数据)。
                                                            • 通过 formData.get('输入框name') 提取指定字段数据,支持文本、文件等类型。
                                                            • 示例:

                                                              四、关键特性与优势

                                                              1. 自动阻止页面刷新:表单提交时不会触发浏览器默认的页面重载,提升用户体验(类似 SPA 交互)。
                                                              1. 服务器端安全执行:敏感逻辑(如数据验证、数据库操作)在服务器端完成,避免客户端暴露核心代码。
                                                              1. 简化数据交互:无需手动封装请求、处理请求头/响应格式,Next.js 自动完成前后端通信。
                                                              1. 支持文件上传:通过 formData.get('文件输入框name') 直接获取上传文件,后续可处理存储(如文件系统、云存储)。

                                                              五、注意事项

                                                              1. 输入框必须设置 name 属性formData 对象通过 name 字段识别并提取数据,未设置则无法获取。
                                                              1. 日志查看位置:Server Action 中的 console.log 输出在 启动开发服务器的终端(服务器端),而非浏览器开发者工具控制台。
                                                              1. 框架依赖:普通 React 应用无法使用该特性,必须基于 Next.js 等支持 React 服务器组件的框架。
                                                               

                                                              036 Storing Server Actions in Separate Files - 服务器操作(Server Actions)分离存储

                                                              一、核心背景:组件内定义Server Actions的限制

                                                              1. 适用场景局限:仅能在非客户端组件中直接定义Server Actions,若组件未添加“use client”指令,可正常使用。
                                                              1. 客户端组件冲突:若组件因使用客户端专属功能(如交互状态、浏览器API)添加“use client”成为客户端组件,直接在其中定义Server Actions会触发报错,提示“不允许在客户端组件文件中拥有服务器操作”。

                                                              二、分离存储的核心原因

                                                              原因类型
                                                              具体说明
                                                              解决技术冲突
                                                              规避客户端组件与Server Actions共存的报错问题,避免后续组件功能扩展(如添加交互)时的潜在故障
                                                              优化代码组织
                                                              分离服务器侧表单提交逻辑与JSX视图代码,提升代码可读性、可维护性,符合“关注点分离”开发原则

                                                              三、单独文件存储的关键规则

                                                              1. 文件命名:无强制要求,可自定义(如actions.jsserver-actions.js等)。
                                                              1. 核心指令:必须在文件最顶部添加"use server"指令,添加后该文件内所有定义的函数均会被Next.js识别为Server Actions(无需在单个函数内重复添加指令)。

                                                              四、实操步骤(以“共享餐点”功能为例)

                                                              1. 创建单独文件:在项目根目录新建文件(如actions.js)。
                                                              1. 迁移Server Action函数:从原组件文件(如共享餐点页面)中剪切目标Server Action函数(如shareMeal),粘贴到新建文件中,并通过export导出函数(确保外部可导入)。
                                                              1. 清理原组件代码:删除原组件文件中该函数内的"use server"指令(避免重复定义)。
                                                              1. 导入使用:在需要调用该Server Action的组件(如表单组件)中,通过import引入函数,直接作为表单action属性使用(如<form action={shareMeal}>)。

                                                              五、分离存储的优势与原理

                                                              1. 核心优势:支持客户端组件使用Server Actions,导入后即使组件添加“use client”成为客户端组件,也不会报错。
                                                              1. 底层原理:Next.js构建过程无法自动分离同一文件中的客户端/服务器侧代码,可能导致服务器代码泄露到客户端(引发安全风险);而从单独文件导入Server Actions,可明确区分代码执行环境,避免混淆。

                                                              六、后续规划

                                                              完成Server Actions分离存储后,将进一步实现:
                                                              • 餐点数据持久化存储到数据库
                                                              • 上传图像文件到文件系统

                                                              037 Creating a Slug & Sanitizing User Input for XSS Protection - Slug生成与XSS防护

                                                              一、核心功能需求

                                                              在餐点数据存储功能开发中,需实现两个关键目标:一是为每个餐点生成唯一标识的Slug(用于数据库存储与URL路径),二是对用户输入内容进行安全处理,防范跨站脚本(XSS)攻击,避免用户提交的有害代码在页面渲染时执行。

                                                              二、工具包安装与作用

                                                              工具包名称
                                                              用途
                                                              核心原因
                                                              slugify
                                                              生成Slug
                                                              无法从用户表单获取Slug,需根据餐点标题自动生成,确保URL友好、唯一
                                                              xss
                                                              净化用户输入
                                                              用户提交的餐点制作指令会以HTML形式在菜单详情页输出,需过滤有害脚本标签(如<script>),防止XSS攻击
                                                              安装操作:停止开发服务器,执行安装命令(如npm install slugify xss),安装完成后重启服务器。

                                                              三、关键代码实现步骤

                                                              1. 导入工具包

                                                              在处理餐点数据的meals.js文件中,导入两个工具包:

                                                              2. 生成Slug(基于餐点标题)

                                                              • 调用sluggii函数,传入餐点标题meal.title
                                                              • 通过配置对象设置lower: true,强制Slug所有字符为小写,保证格式统一;
                                                              • 直接为餐点对象添加slug属性,示例如下:

                                                                3. 净化用户输入(餐点制作指令)

                                                                • 使用xss函数处理meal.instructions,自动过滤有害HTML标签与属性;
                                                                • 用净化后的内容覆盖原指令属性,确保存储与渲染的内容安全:
                                                                  🗒️
                                                                  slugify 是一个用于创建 URL 友好字符串(称为 slug)的库。在这个代码中,它被用于将餐点标题转换为适合在 URL 中使用的格式。
                                                                  具体来说,在 saveMeal 函数中:
                                                                  这行代码的作用是:
                                                                  1. 接收餐点的标题(如 "Delicious Pasta Carbonara")
                                                                  1. 将其转换为 slug 格式(如 "delicious-pasta-carbonara")
                                                                  1. `{ lower: true } 选项确保所有字符都是小写
                                                                  slugify 会:
                                                                  • 将空格转换为连字符 (-)
                                                                  • 移除或替换特殊字符
                                                                  • 通常会移除多余的标点符号
                                                                  • 确保结果是一个有效的 URL 片段
                                                                  这样做的好处是:
                                                                  • 创建更美观、更易读的 URL
                                                                  • 提高 SEO(搜索引擎优化)
                                                                  • 确保 URL 中不会包含特殊字符导致问题
                                                                  • 为每个餐点创建一个唯一的标识符,可用于在网站中导航到特定餐点页面
                                                                  在这个应用中,slug 被存储在数据库中,并用于在 getMeal 函数中查找特定的餐点。
                                                                   

                                                                  038 Storing Uploaded Images & Storing Data in the Database - 图片存储与数据库数据存储

                                                                  一、核心存储原则

                                                                  • 图片存储位置:优先存储在文件系统(公共文件夹),而非数据库。数据库不适合存储文件,会影响性能;公共文件夹中图像可公开访问,能正常在前端渲染。
                                                                  • 数据存储逻辑:数据库仅存储图片在文件系统中的路径,不存储图片文件本身,减少数据库负担。

                                                                  二、图片处理关键步骤

                                                                  1. 获取图片扩展名

                                                                  • 从表单提交的图像对象(meal.image)中,访问其name属性(浏览器自动生成,包含原始文件名)。
                                                                  • 通过split('.').pop()方法分割文件名,提取最后一个元素作为扩展名(如JPEG、PNG)。
                                                                  • 注意:需正确访问meal.image.name,避免因路径错误导致无法获取扩展名。

                                                                  2. 生成唯一文件名

                                                                  • 不使用用户上传的原始文件名,防止重复覆盖。
                                                                  • 采用slug + 扩展名的格式(通过JavaScript字符串模板字面量生成),例如${slug}.${extension},确保文件名唯一。

                                                                  3. 写入文件系统(公共文件夹)

                                                                  • 依赖工具:使用Node.js的fs(文件系统)模块。
                                                                  • 核心方法:调用fs.createWriteStream()创建写入流,需指定目标路径(public/images/${唯一文件名})。
                                                                  • 数据转换
                                                                      1. 调用图像对象的arrayBuffer()方法获取数组缓冲(该方法返回Promise,需用await等待解析)。
                                                                      1. 将数组缓冲转换为普通缓冲(Buffer.from(数组缓冲)),满足流写入的数据格式要求。
                                                                  • 错误处理:在write()方法的回调函数中检查error参数,若存在错误则抛出“保存图片失败”异常。

                                                                  4. 路径处理

                                                                  • 数据库存储的图片路径需删除“public”前缀,因前端请求图像时会自动指向公共文件夹(公共文件夹内容等效于服务器根目录),例如存储/images/文件名而非/public/images/文件名

                                                                  三、数据库数据存储

                                                                  1. 构建SQL语句

                                                                  • 插入字段:向meals表插入title(标题)、summary(摘要)、description(说明)、creator(创作者)、creator_email(创作者邮箱)、image(图片路径)、slug(标识),id字段自动填充。
                                                                  • 安全原则:避免直接注入值(防止SQL注入),使用占位符或ORM(如Better SQLite3)支持的命名参数语法,按字段顺序传递餐点对象提取数据。

                                                                  2. 执行数据插入

                                                                  • 调用db.prepare( SQL语句 ).run( 餐点对象 ),借助ORM自动匹配字段与对象属性,完成数据存储。

                                                                  四、服务器操作与用户体验优化

                                                                  1. 调用保存函数

                                                                  • 在服务器操作中,用await saveMeal(meal)替代控制台输出,saveMeal函数需定义为async(因包含异步操作如文件写入、数据库插入)。

                                                                  2. 功能验证

                                                                  • 启动开发服务器,进入“分享餐点”页面提交数据+图片:
                                                                      1. 无报错情况下,检查public/images文件夹是否存在上传的图片。
                                                                      1. 浏览餐点列表页面,确认新增餐点是否显示,验证功能生效。

                                                                  3. 重定向优化

                                                                  • 导入next/navigationredirect函数,在餐点保存成功后,调用redirect('/meals')将用户跳转至餐点列表页,提升交互体验。

                                                                  五、关键注意事项

                                                                  • 文件名唯一性:必须生成唯一文件名,避免不同用户上传同名图片导致覆盖。
                                                                  • 路径正确性:数据库存储路径需剔除“public”前缀,确保前端能正确请求图像。
                                                                  • 异步处理:所有涉及文件写入、数据库操作、arrayBuffer()的步骤,需用async/await处理异步逻辑,避免数据丢失或报错。
                                                                  • 安全防护:使用参数化SQL语句,防止SQL注入;验证用户上传的图片格式,避免恶意文件上传。
                                                                   
                                                                  🗒️
                                                                  Node.js 的文件系统模块导入:
                                                                  让我解释一下:
                                                                  1. fs 是 Node.js 内置的文件系统模块(File System),用于处理文件和目录操作
                                                                  1. node: 前缀表示这是一个 Node.js 内置模块的显式导入方式。这是 Node.js 推荐的导入方式,用于明确区分内置模块和第三方包
                                                                  1. 在这个文件中,fs 模块被用于 saveMeal 函数中保存上传的图片:
                                                                    1. 具体来说,这里使用了 fs.createWriteStream() 方法创建一个可写流,将上传的图片数据写入到服务器的 public/images/ 目录中
                                                                    这种导入方式比直接写 import fs from 'fs'; 更明确,可以避免与同名的第三方包产生混淆,是现代 Node.js 项目中的推荐做法。

                                                                    039 Managing the Form Submission Status with useFormStatus - 用useFormStatus管理表单提交状态

                                                                    一、核心需求背景

                                                                    在Next.js项目的表单提交场景(如保存餐点数据)中,用户点击提交后需等待请求完成才能跳转页面,期间缺乏视觉反馈,需通过优化按钮状态(如显示“提交中”、禁用按钮)提升用户体验。

                                                                    二、关键技术:useFormStatus钩子

                                                                    1. 定位与导入

                                                                    • 归属:React提供的表单状态管理钩子,在Next.js环境中适配性更强
                                                                    • 导入路径:需从react-dom导入,而非reactnext.js,语法为import { useFormStatus } from 'react-dom'

                                                                    2. 核心属性

                                                                    调用钩子后返回status对象,核心属性为pending
                                                                    • 取值:布尔值(true/false
                                                                    • 含义:true表示表单请求正在进行中,false表示无活跃请求

                                                                    三、使用前提条件

                                                                    1. 客户端组件环境

                                                                    • 必须在客户端组件中使用,需在组件文件顶部添加'use client'指令,否则会报错
                                                                    • 原因:钩子用于实时更新客户端UI(如按钮文本、禁用状态),依赖客户端渲染能力

                                                                    2. 组件层级要求

                                                                    • 若需获取某一表单的提交状态,useFormStatus必须位于该表单内部的子组件中,否则无法正确关联目标表单状态

                                                                    四、最佳实践:组件拆分方案

                                                                    1. 拆分目的

                                                                    避免为实现按钮状态更新,将整个页面转为客户端组件(减少客户端渲染范围,优化性能)

                                                                    2. 具体步骤

                                                                    1. 新建组件:在项目组件目录(如/components/餐食/)中创建独立组件(如MealFormSubmit.jsx
                                                                    1. 配置组件:在新组件中添加'use client'指令,导入并调用useFormStatus
                                                                    1. 实现逻辑
                                                                        • 通过对象解构获取pending属性:const { pending } = useFormStatus()
                                                                        • 条件渲染按钮文本:pending ? '提交中' : '分享餐'
                                                                        • 控制按钮禁用状态:disabled={pending}(提交时禁用,避免重复点击)
                                                                    1. 替换原按钮:在原表单中,用新建的MealFormSubmit组件替换普通按钮

                                                                    五、效果验证

                                                                    • 提交测试:输入数据点击按钮,按钮实时变为“提交中”并禁用
                                                                    • 结果反馈:请求完成后自动重定向页面,新数据正常显示,用户可清晰感知提交进度
                                                                     
                                                                     

                                                                    040 Adding Server-Side Input Validation

                                                                    一、核心结论:客户端验证≠安全,必须补充服务器端验证

                                                                    1. 客户端验证的局限性
                                                                        • 作用:依赖HTML原生required属性,可拦截空表单提交,触发浏览器自动错误提示,提升基础用户体验
                                                                        • 风险:易被绕过(如通过浏览器开发工具删除required属性),恶意用户可直接向服务器提交无效数据,导致后端接收非法值
                                                                    1. 服务器端验证的必要性:作为数据安全的“最后防线”,无论客户端是否验证,服务器都需独立校验输入,确保数据合法性

                                                                    二、服务器端验证实现步骤(以“共享餐点”表单为例)

                                                                    1. 核心验证逻辑设计

                                                                    • 原则:保持简洁高效,优先覆盖关键字段,复杂场景可引入第三方验证库(如Zod、Yup)
                                                                    • 通用辅助函数:创建isInvalidText工具函数,统一判断文本有效性

                                                                      2. 字段级验证规则

                                                                      表单字段
                                                                      验证规则
                                                                      具体判断逻辑
                                                                      餐名(title)
                                                                      非空校验
                                                                      isInvalidText(meal.title)
                                                                      餐点摘要(summary)
                                                                      非空校验
                                                                      isInvalidText(meal.summary)
                                                                      创作者指令
                                                                      非空校验
                                                                      isInvalidText(meal.creator.instructions)
                                                                      创作者邮箱
                                                                      格式+非空校验
                                                                      `isInvalidText(meal.creator.email)
                                                                      餐点图片
                                                                      存在性+有效性校验
                                                                      `!image

                                                                      3. 验证失败处理

                                                                      • 初始方案:验证不通过时抛出“输入无效”错误,触发错误页面跳转
                                                                      • 问题:错误页面信息误导(需自定义专属页面,如“无法创建餐点”),且会清空用户已填内容,用户体验差

                                                                      三、用户体验优化方向(后续实现目标)

                                                                      • 核心需求:避免页面跳转,保留用户输入
                                                                      • 实现思路:验证失败后不抛出错误,而是在当前表单页的顶部/底部显示具体错误提示(如“邮箱格式不正确”“请上传有效图片”),引导用户针对性修改

                                                                      041 Using useFormState()

                                                                      In the next lecture, we'll use a React DOM Hook called useFormState.在下一讲中,我们将使用一个名为useFormState的React DOM钩子。
                                                                      For some unknown reason, the React team decided to suddenly change this Hook's name to useActionState. In addition, it now must be imported from react instead of react-dom.出于某些未知原因,React 团队决定突然将这个 Hook 的名称改为 useActionState。此外,现在它必须从 react 而不是 react-dom 中导入。
                                                                      So when I write this code in the next lecture所以当我在下一讲中编写这段代码时
                                                                      you should write this code (and then use useActionState() instead of useFormState() in any other place where I use it):你应该编写这段代码(然后在我使用它的任何其他地方用useActionState()代替useFormState()):

                                                                      042 Working with Server Action Responses & useFormState

                                                                      一、核心主题

                                                                      在 Next.js 15 与 React 项目中,通过 Server Action(服务器操作)返回自定义响应,并结合 useFormState 钩子优雅处理表单状态与验证错误,实现客户端与服务器的状态同步。

                                                                      二、Server Action 响应规则

                                                                      1. 响应形式:Server Action 不仅支持重定向、传递错误,还可返回自定义响应对象,用于传递表单验证结果、提示信息等。
                                                                      1. 序列化要求:返回的响应对象必须是可序列化对象,支持的数据类型包括字符串、数字、嵌套对象、嵌套数组;不可包含方法(客户端接收时会丢失)。
                                                                      1. 自定义形状:对象结构可按需定义,例如常用格式为 { message: "无效输入提示" },便于客户端统一解析。

                                                                      三、useFormState 钩子核心用法

                                                                      1. 基础说明

                                                                      • 作用:管理依赖 Server Action 提交的表单状态,同步服务器响应与客户端组件状态(类似 useState,但关联服务器操作)。
                                                                      • 导入方式:需从 react-dom 导入(非 React 核心库)。
                                                                      • 使用场景:表单提交后需展示服务器返回的验证错误、成功提示等场景。

                                                                      2. 参数配置

                                                                      参数位置
                                                                      参数类型
                                                                      说明
                                                                      第一个
                                                                      Server Action
                                                                      表单提交时触发的服务器操作(如处理“提交表单”“分享数据”的函数)
                                                                      第二个
                                                                      初始状态值
                                                                      未触发 Server Action 时的默认状态,建议与响应对象形状一致(如 { message: null }

                                                                      3. 返回值

                                                                      返回含两个元素的数组,结构与 useState 类似:
                                                                      • 第一个元素:当前状态(Server Action 返回的最新响应 / 初始状态)。
                                                                      • 第二个元素:Form Action 函数,需绑定到表单的 action 属性,使 useFormState 接管表单状态管理。

                                                                      四、关键实现步骤

                                                                      1. 配置 Server Action
                                                                          • 调整参数:需接收两个参数,第一个为“之前的状态”(初始状态或历史响应),第二个为“表单提交数据”(原提交数据从第一个参数变为第二个)。
                                                                          • 示例:async function shareMeal(prevState, formData) { /* 处理逻辑 */ }
                                                                      1. 客户端组件设置
                                                                          • 添加指令:在使用 useFormState 的组件文件顶部添加 'use client',因该钩子需在客户端执行状态更新。
                                                                          • 绑定 Form Action:将 useFormState 返回的第二个元素(Form Action)设为表单的 action 属性,示例:
                                                                        1. 展示服务器响应
                                                                            • 通过 state 读取服务器返回的响应,例如判断 state.message 存在时,在表单中显示错误提示:

                                                                          五、实操示例

                                                                          1. 场景:提交“分享餐点”表单,若未填写邮箱(删除 required 属性),服务器返回错误提示。
                                                                          1. 效果:点击提交后,useFormState 接收 Server Action 返回的 { message: "请输入邮箱" },并在表单中渲染红色错误文本。
                                                                          1. 优化点:可自定义错误消息内容(如“邮箱为必填项,请补充”),或通过 CSS 增强视觉突出度。

                                                                          043 Building For Production & Understanding NextJS Caching - 生产环境构建与缓存机制

                                                                          一、核心主题

                                                                          Next.js 应用从开发环境到生产环境的部署流程,以及生产环境中缓存机制的原理、影响与验证方式

                                                                          二、生产环境构建流程

                                                                          1. 脚本切换逻辑

                                                                          环境
                                                                          核心操作
                                                                          目的
                                                                          开发环境
                                                                          使用开发服务器
                                                                          实时热更新、方便调试,无需预构建
                                                                          生产环境
                                                                          1. 终端进入项目文件夹 2. 执行 npm run build 3. 执行 npm start
                                                                          1. 生成优化后的可部署项目包 2. 启动生产服务器(非开发服务器)

                                                                          2. 生产服务器特性

                                                                          • 运行地址:仍为 localhost:3000,与开发环境一致
                                                                          • 核心优势:加载速度更快,基于优化后的代码提升用户体验
                                                                          • 关键区别:不支持开发环境的实时热更新,依赖预构建产物

                                                                          三、生产环境缓存引发的异常现象

                                                                          异常表现
                                                                          具体场景
                                                                          数据更新不生效
                                                                          添加新餐点(如“美味的布拉斯卡”)并提交后,页面未显示新数据,仅保留构建时的旧数据
                                                                          自定义延迟消失
                                                                          开发环境中添加的 5 秒页面加载延迟,在生产环境中完全消失,页面秒开

                                                                          四、Next.js 缓存核心原理

                                                                          1. 预渲染机制(Prerendering)

                                                                          • 触发时机:执行 npm run build 时自动触发
                                                                          • 作用范围:仅预渲染非动态页面(可静态生成的页面,如餐点列表页)
                                                                          • 数据处理:页面所需数据在构建阶段已完成获取和渲染,生成静态 HTML 文件

                                                                          2. 缓存逻辑

                                                                          • 缓存对象:预渲染生成的静态 HTML 页面
                                                                          • 核心目的:让首个访客无需等待数据请求和页面渲染,直接加载静态文件,提升首屏速度
                                                                          • 本质问题:缓存页面不会重新执行数据获取代码,导致后续数据更新无法同步

                                                                          3. 缓存机制验证方法

                                                                          1. meals 组件中添加控制台日志(如 console.log('获取餐点数据')
                                                                          1. 重新执行 npm run build 构建项目
                                                                          1. 启动生产服务器(npm start)并刷新餐点页面
                                                                          1. 验证结果:控制台无日志输出,证明数据获取代码未执行,页面使用缓存的预渲染产物

                                                                          044 Triggering Cache Revalidations - 缓存重新验证(Cache Revalidation)

                                                                          一、核心问题:Next.js 过度缓存

                                                                          在数据更新场景(如添加新菜品)中,Next.js 可能因缓存未及时更新,导致页面展示旧数据,需通过「缓存重新验证」主动丢弃过期缓存。

                                                                          二、关键工具:revalidatePath 函数

                                                                          1. 函数作用

                                                                          Next.js 内置的 revalidatePath 函数,用于指定路由路径,触发该路径下缓存的重新验证(即丢弃相关缓存,后续访问时重新生成页面数据)。

                                                                          2. 基础用法

                                                                          • 场景:当某路径页面(如 /菜品)依赖的数据更新时,调用该函数强制重新验证。 示例:添加新菜品后,调用 revalidatePath('/菜品'),Next.js 会重新验证 /菜品 路径的缓存。
                                                                          • 默认规则:仅重新验证指定路径本身,不包含嵌套路径(如 /菜品/详情 需单独处理)。

                                                                          三、revalidatePath 参数配置(第二个参数)

                                                                          参数值
                                                                          作用
                                                                          适用场景
                                                                          page(默认)
                                                                          仅重新验证指定路径的单个页面
                                                                          仅该页面依赖更新数据,嵌套页面无关联
                                                                          layout
                                                                          重新验证指定路径的「布局」及布局包裹的所有嵌套页面
                                                                          布局或嵌套页面均依赖更新数据(如 /菜品 布局下的所有子页面都依赖菜品数据)

                                                                          四、不同场景的缓存验证策略

                                                                          需求场景
                                                                          实现方式
                                                                          仅重新验证单个路径(如 /菜品
                                                                          revalidatePath('/菜品')(默认 page 模式)
                                                                          重新验证所有嵌套页面(如 /菜品 下的子页面)
                                                                          revalidatePath('/菜品', 'layout')
                                                                          重新验证整个网站所有页面
                                                                          revalidatePath('/', 'layout')(目标路径设为根路径,模式设为 layout

                                                                          五、实践效果与验证

                                                                          1. 构建与缓存特性:运行 npm run build 后,页面仍会预生成并缓存,但数据更新时会触发部分缓存清除。
                                                                          1. 验证信号:启动开发服务器添加新菜品,提交后出现以下现象说明功能生效:
                                                                              • 页面加载时间变长(因重新加载所有菜品数据);
                                                                              • 新添加的菜品正常显示(证明数据更新);
                                                                              • 控制台输出预期日志(进一步确认缓存重新验证触发)。
                                                                          1. 注意事项:视频中提及「图像丢失」问题,属于额外 UI 问题,需单独处理,与缓存验证逻辑无关。

                                                                          045 Don't Store Files Locally On The Filesystem! - 文件存储

                                                                          一、核心问题:本地文件存储导致的图片缺失

                                                                          1. 问题现象:在项目中添加新餐点时,新餐点对应的图片会出现缺失
                                                                          1. 根本原因:图片被存储在项目的 public/images 文件夹中,该存储方式存在环境兼容性问题

                                                                          二、开发环境与生产环境的文件存储差异

                                                                          环境
                                                                          文件存储行为
                                                                          关键限制
                                                                          开发环境
                                                                          public 文件夹中的文件可正常访问,图片能正常加载
                                                                          无明显限制,仅用于开发调试
                                                                          生产环境
                                                                          1. 部署时 public 文件夹内容会被复制到 .next 文件夹(含缓存页面等,供 Next.js 生产服务器使用)<br>2. 部署后向 public 新增的文件会被生产服务器忽略<br>3. 无需部署 public 文件夹,Next.js 生产环境不依赖该文件夹
                                                                          无法动态更新文件,新增文件无法被识别

                                                                          三、官方解决方案:使用外部文件存储服务

                                                                          1. 官方建议来源:Next.js 官方文档明确说明
                                                                          1. 核心结论:运行时生成的文件(如用户上传的图片、动态新增的资源)不应存储在本地文件系统,需使用外部文件存储服务
                                                                          1. 推荐工具:AWS S3 等专业文件存储服务(支持海量文件存储、高可用性,适配生产环境需求)

                                                                          046 Bonus Storing Uploaded Images In The Cloud (AWS S3)

                                                                          As explained in the previous lecture, storing uploaded files (or any other files that are generated at runtime) on the local filesystem is not a great idea - because those files will simply not be available in the running NextJS applications.正如上一讲中所解释的,将上传的文件(或任何在运行时生成的其他文件)存储在本地文件系统上并不是一个好主意——因为这些文件在运行的NextJS应用程序中根本无法使用。
                                                                          Instead, it's recommended that you store such files (e.g., uploaded images) via some cloud file storage - like AWS S3.相反,建议您通过一些云文件存储(例如 亚马逊S3)来存储此类文件(如上传的图片)。
                                                                          AWS S3 is a service provided by AWS which allows you to store and serve (depending on its configuration) files. You can get started with this service for free but you should check out its pricing page to avoid any unwanted surprises.AWS S3是AWS提供的一项服务,允许你存储和提供(取决于其配置)文件。你可以免费开始使用这项服务,但你应该查看其定价页面,以避免任何意外情况。
                                                                          In this lecture, I'll explain how you could use AWS S3 to store uploaded users images & serve them on the NextJS website.在本次讲座中,我将讲解如何使用AWS S3存储用户上传的图片,并在NextJS网站上展示这些图片。
                                                                          1) Create an AWS account 1) 创建一个AWS账户
                                                                          In order to use AWS S3, you need an AWS account. You can create one here.要使用AWS S3,您需要一个AWS账户。您可以在此处创建一个。
                                                                          2) Create a S3 bucket 2) 创建一个S3存储桶
                                                                          Once you created an account (and you logged in), you should navigate to the S3 console to create a so-called "bucket".一旦你创建了账户(并且已登录),就应该导航至S3控制台来创建一个所谓的“存储桶”。
                                                                          "Buckets" are containers that can be used to store files (side-note: you can store any files - not just images).“存储桶”是可用于存储文件的容器(旁注:您可以存储任何文件,不仅仅是图像)。
                                                                          Every bucket must have a globally unique name, hence you should become creative. You could, for example, use a name like <your-name>-nextjs-demo-users-image.每个存储桶都必须有一个全球唯一的名称,因此你需要发挥创意。例如,你可以使用像<你的名字>-nextjs-demo-users-image这样的名称。
                                                                          I'll use maxschwarzmueller-nextjs-demo-users-image in this example here.我将在此示例中使用maxschwarzmueller-nextjs-demo-users-image
                                                                          When creating the bucket, you can confirm all the default settings - the name's the only thing you should set.创建存储桶时,你可以确认所有默认设置——名称是唯一需要你设置的东西。
                                                                          3) Upload the dummy image files 3) 上传虚拟图像文件
                                                                          Now that the bucket was created, you can already add some files to it => The dummy images that were previously stored locally in the public/images folder.既然存储桶已经创建,你就可以向其中添加一些文件了,这些文件就是之前本地存储在public/images文件夹中的虚拟图像。
                                                                          To do that, select your created bucket and click the "Upload" button. Then drag & drop those images into the box and confirm the upload.要做到这一点,请选择你创建的存储桶并点击“上传”按钮。然后将这些图片拖放到框中并确认上传。
                                                                          notion image
                                                                          Thereafter, all those images should be in the bucket:此后,所有这些图像都应在存储桶中:
                                                                          notion image
                                                                          4) Configure the bucket for serving the images4) 配置存储桶以提供图像服务
                                                                          Now that you uploaded those dummy images, it's time to configure the bucket such that the images can be loaded from the NextJS website.既然你已经上传了那些虚拟图片,现在是时候配置存储桶了,这样图片就能从NextJS网站加载出来。
                                                                          Because, by default, this is not possible! By default, S3 buckets are "locked down" and the files in there are secure & not accessible by anyone else.因为,默认情况下,这是不可能的!默认情况下,S3存储桶处于“锁定”状态,其中的文件是安全的,其他人无法访问。
                                                                          But for our purposes here, we must update the bucket settings to make sure the images can be viewed by everyone.但就我们此处的目的而言,我们必须更新存储桶设置,以确保所有人都能查看这些图像。
                                                                          To do that, as a first step, click on the "Permissions" tab and "Edit" the "Block public access" setting:要做到这一点,第一步是点击“权限”标签,然后“编辑”“阻止公共访问”设置:
                                                                          notion image
                                                                          Then, disable the "Block all public access" checkbox (and with it, all other checkboxes) and select "Save Changes".然后,取消勾选“阻止所有公共访问”复选框(以及随之而来的所有其他复选框),并选择“保存更改”。
                                                                          Type "confirm" into the confirmation overlay once it pops up.一旦确认覆盖层弹出,请在其中输入“confirm”。
                                                                          That's not all though - as a next (and final step), you must add a so-called "Bucket Policy". That's an AWS-specific policy document that allows you to manage the permissions of the objects stored in the bucket.不过这还不是全部——作为下一步(也是最后一步),你必须添加一个所谓的“存储桶策略”。这是一份特定于AWS的策略文档,允许你管理存储在存储桶中的对象的权限。
                                                                          You can add such a "Bucket Policy" right below the "Block all public access" area, still on the "Permissions" tab:你可以在“阻止所有公共访问”区域的正下方添加这样一个“存储桶策略”,该区域仍位于“权限”标签页中:
                                                                          Click "Edit" and insert the following bucket policy into the box:点击“编辑”并将以下存储桶策略插入到框中:
                                                                          Replace DOC-EXAMPLE-BUCKET with your bucket name (maxschwarzmueller-nextjs-demo-users-image in my case).将DOC-EXAMPLE-BUCKET替换为你的存储桶名称(在我的案例中是maxschwarzmueller-nextjs-demo-users-image)。
                                                                          Then, click "Save Changes". 然后,点击“保存更改”。
                                                                          Now the bucket is configure to grant access to all objects inside of it to anyone who has a URL pointing to one of those objects.现在,该存储桶已配置为向任何拥有指向其中某个对象的URL的人授予对桶内所有对象的访问权限。
                                                                          Therefore, you should now of course not add any files into the bucket that you don't want to share with the world!因此,你现在当然不应该往这个存储桶里添加任何你不想与全世界共享的文件!
                                                                          To test if everything works, click on one of the images you uploaded (in the bucket).要测试一切是否正常,请点击你上传的其中一张图片(在存储桶中)。
                                                                          Then click on the "Object URL" - if opening it works (and you can see the image), you configured everything as needed.然后点击“对象URL”——如果能成功打开(并且你可以看到图片),说明你已按要求完成了所有配置。
                                                                          notion image
                                                                          5) Update the NextJS code to use those S3 images5) 更新NextJS代码以使用这些S3图像
                                                                          Now that the images are stored + served via S3, it's time to also load them from there in your NextJS app.既然图片已经通过S3存储和提供服务,现在是时候在你的NextJS应用中也从那里加载它们了。
                                                                          As a first step, you can delete the public/images folder (so that an empty public/ folder remains).第一步,你可以删除public/images文件夹(这样会留下一个空的public/文件夹)。
                                                                          Now, if you also delete the .next folder in the NextJS project and you then visit localhost:3000/meals, you should see a bunch of meals without images.现在,如果你也删除了NextJS项目中的.next文件夹,然后访问localhost:3000/meals,你应该会看到许多没有图片的餐食。
                                                                          To bring them back, as a first step, edit the database data by updating the initdb.js file: Change all the image property values from image: '/images/burger.jpg', to image: 'burger.jpg' (and do that for all meals).要恢复它们,第一步是通过更新initdb.js文件来编辑数据库数据:将所有图像属性值从image: '/images/burger.jpg'更改为image: 'burger.jpg'(并对所有餐品执行此操作)。
                                                                          Alternatively, you find an updated initdb.js file attached.或者,你会发现附件中有一个更新后的initdb.js文件。
                                                                          Next, go to the components/meals/meal-item.js file (which contains the MealItem component) and update the <Image> src:接下来,前往components/meals/meal-item.js文件(该文件包含MealItem组件)并更新<Image>src
                                                                          Of course, use your S3 URL / bucket name!当然,请使用您的S3 URL / 存储桶名称!
                                                                          The new src value is a string that contains the S3 URL to your bucket objects (i.e., the URL you previously clicked for testing purposes - without the image file name at the end). The actual image name that should be loaded is then dynamically inserted via ${image}.新的src值是一个字符串,其中包含指向您存储桶对象的S3 URL(即您之前为测试目的点击的URL,末尾不包含图像文件名)。然后,应加载的实际图像名称会通过${image}动态插入。
                                                                          Note: This will only work if the images stored in the S3 bucket have the names referenced in the initdb.js file!注意:只有当存储在S3存储桶中的图像具有initdb.js文件中引用的名称时,此功能才会生效!
                                                                          You should also update the app/meals/[mealSlug]/page.js file and make sure that the image on this page is also fetched from S3:你还应该更新app/meals/[mealSlug]/page.js文件,并确保该页面上的图片也从S3获取:
                                                                          Of course, use your S3 URL / bucket name!当然,请使用您的S3 URL / 存储桶名称!
                                                                          Now, to reset the database data, you should delete your meals.db file (i.e., delete the SQLite database file) and re-run node initdb.js to re-initialize it (with the updated image values).现在,要重置数据库数据,你需要删除meals.db文件(即删除SQLite数据库文件),然后重新运行node initdb.js以重新初始化它(包含更新后的图像值)。
                                                                          If you do that, and you then restart the development server (npm run dev), you'll notice that you now get an error when visiting the /meals page:如果你这样做,然后重启开发服务器(npm run dev),你会注意到在访问/meals页面时会出现一个错误:
                                                                          Error: Invalid src prop (https://maxschwarzmueller-nextjs-demo-users-image.s3.amazonaws.com/burger.jpg) on `next/image`, hostname "maxschwarzmueller-nextjs-demo-users-image.s3.amazonaws.com" is not configured under images in your `next.config.js`
                                                                          6) Allowing S3 as an image source 6) 允许S3作为图像源
                                                                          You get this error because, by default, NextJS does not allow external URLs when using the <Image> component.你遇到这个错误是因为,默认情况下,NextJS在使用<Image>组件时不允许外部URL。
                                                                          You explicitly have to allow such a URL in order to get rid of this error.你必须明确允许这样的URL才能消除此错误。
                                                                          That's done by editing the next.config.js file:这可以通过编辑next.config.js file来完成:
                                                                          Of course, use your S3 URL / bucket name!当然,请使用您的S3 URL / 存储桶名称!
                                                                          This remotePatterns config allows this specific S3 URL as a valid source for images.这个remotePatterns配置允许这个特定的S3 URL作为有效的图片来源。
                                                                          With the config file updated + saved, you should now be able to visit /meals and see all those images again.更新并保存配置文件后,你现在应该可以访问/meals并再次看到所有那些图片了。
                                                                          7) Storing uploaded images on S3 7) 将上传的图片存储在S3上
                                                                          Now that we can see those dummy images again, it's finally time to also "forward" user-generated (i.e., uploaded) images to S3.既然我们能再次看到那些虚拟图片了,现在终于可以把用户生成(即上传的)图片“转发”到S3了。
                                                                          This can be done with help of a package provided by AWS - the @aws-sdk/client-s3 package. This package provides functionalities that allow you to interact with S3 - e.g., to store files in a specific bucket.这可以借助AWS提供的一个包来完成,即@aws-sdk/client-s3包。该包提供了允许你与S3进行交互的功能,例如将文件存储在特定的存储桶中。
                                                                          Install that package via npm install @aws-sdk/client-s3.通过npm install @aws-sdk/client-s3安装该包。
                                                                          Then, go to your lib/meals.js file and import the AWS S3 SDK (at the top of the file):然后,前往你的lib/meals.js文件并导入AWS S3 SDK(在文件顶部):
                                                                          Next, initialize it by adding this line (e.g., right above the line where the db object is created):接下来,通过添加此行来初始化它(例如,就在创建db对象的那一行上方):
                                                                          Almost there! 就快完成了!
                                                                          Now, edit the saveMeal() function and remove all code that was related to storing the image on the local file system.现在,编辑saveMeal()函数,并删除所有与在本地文件系统上存储图像相关的代码。
                                                                          Instead, add this code: 相反,请添加以下代码:
                                                                          Of course, use your S3 URL / bucket name!当然,请使用您的S3 URL / 存储桶名称!
                                                                          Also make sure to save the image filename under meal.image:此外,请确保将图像文件名保存在meal.image下:
                                                                          The final saveMeal() function should look like this:最终的saveMeal()函数应该如下所示:
                                                                          8) Granting the NextJS backend AWS access permissions8) 授予NextJS后端AWS访问权限
                                                                          Now, there's just one last, yet very important, step missing: Granting your NextJS app S3 access permissions.现在,只剩下最后一个但非常重要的步骤缺失了:授予你的NextJS应用S3访问权限。
                                                                          We did configure S3 to serve the bucket content to everyone.我们确实将S3配置为向所有人提供存储桶内容。
                                                                          But we did not (and should not!) configure it to allow everyone to write to the bucket or change the bucket contents.但我们没有(也不应该!)将其配置为允许所有人写入存储桶或更改存储桶内容。
                                                                          But that's what our NextJS app (via the S3 AWS SDK) now tries to do!但这正是我们的NextJS应用(通过S3 AWS SDK)现在要尝试做的事情!
                                                                          To grant our app appropriate permissions, you must set up AWS access keys for your app.要授予我们的应用程序适当的权限,您必须为您的应用程序设置AWS访问密钥。
                                                                          This is done by adding a .env.local file to your root NextJS project. This file will automatically be read by NextJS and the environment variables configured in there will be made available to the backend (!) part of your app.这可以通过在你的 NextJS 根项目中添加一个 .env.local 文件来实现。NextJS 会自动读取该文件,其中配置的环境变量将可用于你的应用的后端(!)部分。
                                                                          You can learn more about setting up environment variables for NextJS apps here: https://nextjs.org/docs/app/building-your-application/configuring/environment-variables.你可以在这里了解更多关于为NextJS应用设置环境变量的信息:https://nextjs.org/docs/app/building-your-application/configuring/environment-variables
                                                                          In this .env.local file, you must add two key-value pairs:在这个.env.local文件中,你必须添加两个键值对:
                                                                          You get those access keys from inside the AWS console (in the browser). You can get them by clicking on your account name (in the top right corner of the AWS console) and then "Security Credentials".你可以从AWS控制台(浏览器中)获取这些访问密钥。具体操作是点击AWS控制台右上角的账户名称,然后选择“安全凭证”。
                                                                          Scroll down to the "Access Keys" area and create a new Access Key. Copy & paste the values into your .env.local file and never share these keys with anyone! Don't commit them to Git or anything like that!向下滚动到“访问密钥”区域并创建一个新的访问密钥。将这些值复制并粘贴到您的.env.local文件中,并且绝对不要与任何人分享这些密钥!不要将它们提交到Git或类似的地方!
                                                                          With all that done, finally, you should be able to create new meals, upload images and see them on /meals. Even in production! Because now, the images are stored on S3!完成所有这些操作后,你最终应该能够创建新的菜品、上传图片,并在/meals上查看它们。即使在生产环境中也是如此!因为现在,图片是存储在S3上的!
                                                                          You find the finished, adjusted code attached to this lecture. Please note that the .env.local file is not included - you must add it (and use your own credentials) if you want to run the attached code.本次课程附带了完成且经过调整的代码。请注意,.env.local文件未包含在内——如果您想运行附带的代码,必须自行添加该文件(并使用您自己的凭据)。

                                                                          047 Adding Static Metadata - 元数据配置

                                                                          一、Next.js 元数据核心知识

                                                                          1. 元数据基础概念

                                                                          • 定义:通过导出 metadata 常量配置的页面元数据,非随机变量,是 Next.js 专门识别的配置项,用于定义页面基础信息。
                                                                          • 作用
                                                                            • 供搜索引擎爬虫抓取,优化 SEO(搜索引擎优化)。
                                                                            • 在 X(原 Twitter)、Facebook 等平台分享页面链接时,展示预设的标题、描述等内容,提升分享效果。
                                                                          • 参考文档:官方文档包含 metadata 对象可配置的所有字段详情,视频中附相关文档链接,可进一步拓展学习。

                                                                          2. 静态元数据配置

                                                                          (1)配置规则

                                                                          • 继承与优先级
                                                                            • 根布局中配置的元数据,会自动应用到其包裹的所有页面,确保每页有基础元数据。
                                                                            • 页面自身指定的元数据优先级 > 嵌套布局元数据优先级 > 根布局元数据优先级,若页面有独立配置则覆盖上级配置。
                                                                          • 配置方式:在目标页面/布局文件中导出 metadata 常量,设置 title(标题)和 description(描述)等字段。

                                                                          (2)示例:“所有餐点页面”配置

                                                                          • 效果验证:刷新页面后,浏览器标签栏显示“所有餐点”;查看页面源代码,可找到 description 元标签,其 content 属性值为配置的描述文本。

                                                                          3. 动态页面元数据问题

                                                                          • 核心痛点:动态页面(如内容依赖加载的具体数据,例:单个餐点详情页,标题需显示餐点名称)无法通过静态 metadata 常量直接配置,需后续学习动态元数据的专属配置方案。

                                                                          048 Adding Dynamic Metadata - 动态页面元数据配置

                                                                          一、核心配置方式

                                                                          1. 与静态元数据的区别:静态页面通过导出metadata常量/变量配置元数据,动态页面需导出异步函数生成元数据(Next.js会优先查找静态元数据,未找到时执行该异步函数)
                                                                          1. 函数核心作用:接收与页面组件相同的props数据,最终返回一个元数据对象(包含标题、描述等信息)

                                                                          二、动态元数据生成流程

                                                                          1. 数据获取:函数通过props获取数据,包含parent键的对象,可通过meal slug传递给get meal方法,获取目标餐食(示例场景)的具体数据
                                                                          1. 元数据构造:基于获取的餐食数据,构造并返回元数据对象,示例格式如下:
                                                                            1. 效果验证:配置完成后,访问动态页面可在浏览器标签页标题中看到对应页面的动态标题(如具体餐食名称)

                                                                            三、关键问题与解决方案

                                                                            问题场景
                                                                            原因分析
                                                                            解决方案
                                                                            输入无效slug出现错误
                                                                            元数据优先生成,meal未定义
                                                                            generateMetadata内检查meal是否存在,若不存在则调用notFound()函数
                                                                            未显示404页面
                                                                            未处理元数据生成失败场景
                                                                            通过notFound()函数触发Next.js默认404页面渲染

                                                                            四、核心注意事项

                                                                            1. 函数调用规则:必须按Next.js规范定义异步函数(名称与格式需符合框架查找逻辑),否则框架无法识别执行
                                                                            1. 数据依赖顺序:元数据生成优先级高于页面内容渲染,需确保get meal等数据获取方法稳定,避免因数据未返回导致meal未定义
                                                                            1. 错误边界处理generateMetadata函数内部需主动判断数据有效性(如if (!meal) notFound()),否则无效请求会直接抛出错误而非显示404

                                                                            049 Module Summary - 基于应用路由器

                                                                            一、路由系统

                                                                            1. 文件系统路由

                                                                            • 核心逻辑:通过文件/文件夹结构定义路由,无需手动配置路由规则
                                                                            • 关键文件:
                                                                              • page.js:定义路由对应的页面组件,是路由生效的核心文件
                                                                              • layout.js:定义嵌套路由的共享布局(如导航栏、页脚),子路由自动继承
                                                                              • 特殊功能文件:
                                                                                • error.js:捕获当前路由段及子路由的错误,自定义错误页面
                                                                                • not-found.js:处理路由匹配失败(404)场景
                                                                                • loading.js:快速实现路由级加载状态,也可结合Suspense使用

                                                                            2. 动态路由

                                                                            • 适用场景:路径段事先未知的场景(如用户详情页/users/[id]、商品页/products/[slug]
                                                                            • 实现方式:文件夹或文件命名为[参数名](如[mealId]),通过params参数获取动态值
                                                                            • 示例:加载“个人餐”页面时,无需提前确定餐品数量,通过动态路由适配不同餐品ID

                                                                            二、组件类型与渲染机制

                                                                            1. 服务器组件(默认)

                                                                            • 执行位置:服务器端渲染,无需发送JS到客户端
                                                                            • 核心优势:
                                                                              • 直接在组件内获取数据,无需useEffect+后端接口请求
                                                                              • 减少客户端JS体积,提升首屏加载速度
                                                                            • 注意事项:所有组件默认是服务器组件,无法使用客户端API(如useStatewindow

                                                                            2. 客户端组件

                                                                            • 转换方式:在文件顶部添加'use client'指令,将文件及内部组件转为客户端组件
                                                                            • 适用场景:
                                                                              • 需要使用React状态(useStateuseReducer
                                                                              • 需要处理用户交互(onClick、表单提交等事件)
                                                                              • 需要调用浏览器API(windowdocument
                                                                            • 最佳实践:最小化客户端组件范围,仅将需交互的部分拆分为客户端组件

                                                                            三、数据处理

                                                                            1. 数据获取(服务器组件)

                                                                            • 实现方式:在服务器组件中直接编写异步函数获取数据
                                                                            • 示例:
                                                                              • 优势:避免客户端请求开销,数据获取与渲染在服务器端完成

                                                                              2. 服务器操作(Server Actions)

                                                                              • 定义方式:在文件顶部添加'use server'指令,或给函数添加该指令
                                                                              • 核心用途:
                                                                                • 处理表单提交(通过form标签的action属性直接绑定)
                                                                                • 执行服务端逻辑(如数据库写入、文件上传)
                                                                              • 配套工具:
                                                                                • useFormState(React DOM提供):处理服务器操作返回的响应,更新UI(如显示错误信息)
                                                                                • useFormStatus:获取表单提交状态(如pendingerror),实现提交中按钮禁用、加载动画等

                                                                              四、缓存与数据一致性

                                                                              1. 缓存特性

                                                                              • 默认行为:Next.js 对服务器组件渲染结果、数据请求结果进行激进缓存
                                                                              • 关键问题:开发环境缓存机制与生产环境不同,可能出现“开发正常、生产数据消失”的情况,需在生产模式测试

                                                                              2. 缓存更新

                                                                              • 核心方法:调用revalidatePath函数,主动失效指定路径的缓存
                                                                              • 适用场景:数据修改后(如新增/删除/更新数据),确保页面显示最新内容
                                                                              • 示例:提交表单新增餐品后,调用revalidatePath('/meals'),刷新餐品列表页缓存

                                                                              五、加载状态与错误处理

                                                                              1. 精细加载控制

                                                                              • Suspense组件:包裹需要延迟加载的内容,自定义加载 fallback(如骨架屏)
                                                                              • 优势:相比loading.js,可实现组件级的加载控制,更灵活

                                                                              2. 错误边界

                                                                              • 路由级错误处理:通过error.js捕获路由范围内的错误,隔离错误影响
                                                                              • 组件级错误处理:可结合React原生ErrorBoundary组件,处理客户端组件错误

                                                                              六、页面元数据

                                                                              1. 静态元数据

                                                                              • 定义方式:在page.jslayout.js中导出metadata对象
                                                                              • 示例:

                                                                                2. 动态元数据

                                                                                • 适用场景:元数据依赖动态参数(如商品详情页标题含商品名)
                                                                                • 实现方式:导出generateMetadata异步函数,通过params获取动态参数
                                                                                • 示例:

                                                                                  📎 参考文章

                                                                                  • 一些引用
                                                                                  • 引用文章
                                                                                   
                                                                                  💡
                                                                                  欢迎您在底部评论区留言,一起交流~
                                                                                  上一篇
                                                                                  04 - Routing & Page Rendering - Deep Dive
                                                                                  下一篇
                                                                                  01 NextJs - Optional React Refresher
                                                                                  Loading...
                                                                                  0%